From 2ac49e003cc001cdda6a850d14f7e61fa14fdd33 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Tue, 1 Sep 2026 22:34:09 +0200 Subject: [PATCH 01/19] fix(libdatadog): bound active agentless request buffers (#238) Agentless data-pipeline requests bypass the tracer active request budget, so a slow intake can retain unbounded request bodies. Preserve the 64 MiB process-wide bound and release bytes on completion or cancellation. (cherry picked from commit 380c8a66f7645e7554fcd47183c598dfb1da6752) --- .../libdatadog/lib/agentless-transport.js | 11 +++ packages/libdatadog/test/transport.test.js | 71 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/packages/libdatadog/lib/agentless-transport.js b/packages/libdatadog/lib/agentless-transport.js index 79e15423..1174b55f 100644 --- a/packages/libdatadog/lib/agentless-transport.js +++ b/packages/libdatadog/lib/agentless-transport.js @@ -1,5 +1,9 @@ 'use strict' +const maxActiveBufferSize = 16 * 1024 * 1024 + +let activeBufferSize = 0 + function createHostTransport () { const requests = new Map() const timers = new Map() @@ -8,6 +12,11 @@ function createHostTransport () { const target = new URL(url) const client = target.protocol === 'https:' ? require('node:https') : require('node:http') const headers = Object.fromEntries(headerList.map(({ name, value }) => [name, value])) + const bodySize = body.byteLength + + if (activeBufferSize + bodySize > maxActiveBufferSize) { + return Promise.reject(new Error('Maximum active agentless request buffer size reached: payload is discarded.')) + } return new Promise((resolve, reject) => { let settled = false @@ -15,6 +24,7 @@ function createHostTransport () { if (settled) return settled = true requests.delete(id) + activeBufferSize -= bodySize callback(value) } const outgoing = client.request(target, { @@ -31,6 +41,7 @@ function createHostTransport () { body: Buffer.concat(chunks), })) }) + activeBufferSize += bodySize requests.set(id, { cancel: () => { diff --git a/packages/libdatadog/test/transport.test.js b/packages/libdatadog/test/transport.test.js index 6e55df6f..0018396f 100644 --- a/packages/libdatadog/test/transport.test.js +++ b/packages/libdatadog/test/transport.test.js @@ -72,6 +72,77 @@ test('host transport cancels an active request', async () => { } }) +test('host transport bounds active request buffers', async () => { + const transport = createHostTransport() + let requestCount = 0 + let resolveFirstRequest + const firstRequestReceived = new Promise((resolve) => { + resolveFirstRequest = resolve + }) + /** + * @param {import('node:http').IncomingMessage} request + * @param {import('node:http').ServerResponse} response + */ + const server = http.createServer((request, response) => { + requestCount++ + if (requestCount === 1) { + request.resume() + resolveFirstRequest() + return + } + request.once('end', () => response.end()) + request.resume() + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const atLimitBody = Buffer.alloc(16 * 1024 * 1024) + const firstRequest = transport.request({ + id: 4, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: atLimitBody, + }) + const firstRequestCanceled = assert.rejects(firstRequest, /request was cancelled/) + + try { + await firstRequestReceived + await assert.rejects(transport.request({ + id: 5, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: Buffer.alloc(1), + }), /Maximum active agentless request buffer size reached/) + + transport.cancelRequest(4) + await firstRequestCanceled + + const response = await transport.request({ + id: 6, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: atLimitBody, + }) + assert.strictEqual(response.status, 200) + + const nextResponse = await transport.request({ + id: 7, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: Buffer.alloc(1), + }) + assert.strictEqual(nextResponse.status, 200) + } finally { + transport.cancelRequest(4) + await firstRequestCanceled + await new Promise(resolve => server.close(resolve)) + } +}) + test('host transport cancels a pending timer', async () => { const transport = createHostTransport() const sleep = transport.sleep(3, 60_000) From 44a0299986aca606be78fdc9e654ec9ad363dd36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:34:22 +0200 Subject: [PATCH 02/19] chore(deps): bump the patch-updates group across 1 directory with 2 updates (#212) Bumps the patch-updates group with 2 updates in the / directory: [futures](https://github.com/rust-lang/futures-rs) and [serde](https://github.com/serde-rs/serde). Updates `futures` from 0.3.32 to 0.3.34 - [Release notes](https://github.com/rust-lang/futures-rs/releases) - [Changelog](https://github.com/rust-lang/futures-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.32...0.3.34) Updates `serde` from 1.0.228 to 1.0.229 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.228...v1.0.229) --- updated-dependencies: - dependency-name: futures dependency-version: 0.3.34 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: serde dependency-version: 1.0.229 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit f9d55fb1f2f59f9bc644f9f9712c52d761013fb3) --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab6b5614..9741ae22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,9 +605,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -636,9 +636,9 @@ checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -653,13 +653,13 @@ checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] @@ -676,9 +676,9 @@ checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -2639,9 +2639,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2679,22 +2679,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] From a2e47f865b5e8dcd3c7cba0929c5dc18b4022175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:49:09 +0200 Subject: [PATCH 03/19] chore(deps-dev): bump eslint-plugin-n from 17.24.0 to 18.2.2 (#175) Bumps [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) from 17.24.0 to 18.2.2. - [Release notes](https://github.com/eslint-community/eslint-plugin-n/releases) - [Changelog](https://github.com/eslint-community/eslint-plugin-n/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint-community/eslint-plugin-n/compare/v17.24.0...v18.2.2) --- updated-dependencies: - dependency-name: eslint-plugin-n dependency-version: 18.2.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit c13eeb369f123c163585a538e4a6bb080b5e7c3e) --- package.json | 2 +- yarn.lock | 18 +++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 1445cccc..4aee6741 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@stylistic/eslint-plugin": "^5.9.0", "eslint": "^10.6.0", "eslint-plugin-import-x": "^4.17.1", - "eslint-plugin-n": "^17.24.0", + "eslint-plugin-n": "^18.2.2", "eslint-plugin-unicorn": "^63.0.0", "globals": "^17.7.0" } diff --git a/yarn.lock b/yarn.lock index d8f55992..d538f4d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -425,10 +425,10 @@ eslint-plugin-import-x@^4.17.1: stable-hash-x "^0.2.0" unrs-resolver "^1.9.2" -eslint-plugin-n@^17.24.0: - version "17.24.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz#b66fa05f7a6c1ba16768f0921b8974147dddd060" - integrity sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw== +eslint-plugin-n@^18.2.2: + version "18.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-18.2.2.tgz#743c7931831472f3378724cc32ffe3917c734819" + integrity sha512-gOO0lIqwEjZ750kv9/SptCWArUoAZXJoBr0vYWTO2dCBxctHUXlBIigiC8xuxxr/NKqgIT6Ehz1xRcilj8a5cA== dependencies: "@eslint-community/eslint-utils" "^4.5.0" enhanced-resolve "^5.17.1" @@ -438,7 +438,6 @@ eslint-plugin-n@^17.24.0: globrex "^0.1.2" ignore "^5.3.2" semver "^7.6.3" - ts-declaration-location "^1.0.6" eslint-plugin-unicorn@^63.0.0: version "63.0.0" @@ -801,7 +800,7 @@ picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^4.0.2, picomatch@^4.0.3: +picomatch@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== @@ -870,13 +869,6 @@ tapable@^2.3.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== -ts-declaration-location@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz#d4068fe9975828b3b453b3ab112b4711d8267688" - integrity sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA== - dependencies: - picomatch "^4.0.2" - tslib@^2.4.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" From 9e1aa41b3cb7a03b9febd78f8735333735190c7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:49:19 +0200 Subject: [PATCH 04/19] chore(deps-dev): bump the minor-updates group across 1 directory with 2 updates (#190) Bumps the minor-updates group with 2 updates in the / directory: [eslint](https://github.com/eslint/eslint) and [globals](https://github.com/sindresorhus/globals). Updates `eslint` from 10.6.0 to 10.9.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.6.0...v10.9.1) Updates `globals` from 17.7.0 to 17.11.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](https://github.com/sindresorhus/globals/compare/v17.7.0...v17.11.0) --- updated-dependencies: - dependency-name: eslint dependency-version: 10.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: globals dependency-version: 17.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-updates ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit fc69f10ee3bcee385fab9b1cb0e8b01b226870ae) --- package.json | 4 ++-- yarn.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 4aee6741..132f0568 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,10 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@stylistic/eslint-plugin": "^5.9.0", - "eslint": "^10.6.0", + "eslint": "^10.9.1", "eslint-plugin-import-x": "^4.17.1", "eslint-plugin-n": "^18.2.2", "eslint-plugin-unicorn": "^63.0.0", - "globals": "^17.7.0" + "globals": "^17.11.0" } } diff --git a/yarn.lock b/yarn.lock index d538f4d4..8efd81a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,10 +50,10 @@ debug "^4.3.1" minimatch "^10.2.4" -"@eslint/config-helpers@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.6.0.tgz#ef9a36881d39dfd5dbeac22b0da997fabfb08b03" - integrity sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA== +"@eslint/config-helpers@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.7.0.tgz#09ee4aa07b73f059ec2d4c74bf4b2ff02b322377" + integrity sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw== dependencies: "@eslint/core" "^1.2.1" @@ -280,7 +280,7 @@ baseline-browser-mapping@^2.9.0: resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9" integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== -brace-expansion@^5.0.2: +brace-expansion@^5.0.8: version "5.0.9" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf" integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg== @@ -486,15 +486,15 @@ eslint-visitor-keys@^5.0.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -eslint@^10.6.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.6.0.tgz#e1b4059c582be950c7088c9b55f984738b243c27" - integrity sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg== +eslint@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.9.1.tgz#409da5c41a5536d5a849f8555a18ca7ef1eb963b" + integrity sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A== dependencies: "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.2" "@eslint/config-array" "^0.23.5" - "@eslint/config-helpers" "^0.6.0" + "@eslint/config-helpers" "^0.7.0" "@eslint/core" "^1.2.1" "@eslint/plugin-kit" "^0.7.2" "@humanfs/node" "^0.16.6" @@ -518,7 +518,7 @@ eslint@^10.6.0: imurmurhash "^0.1.4" is-glob "^4.0.0" json-stable-stringify-without-jsonify "^1.0.1" - minimatch "^10.2.4" + minimatch "^10.2.5" natural-compare "^1.4.0" optionator "^0.9.3" @@ -636,10 +636,10 @@ globals@^16.4.0: resolved "https://registry.yarnpkg.com/globals/-/globals-16.5.0.tgz#ccf1594a437b97653b2be13ed4d8f5c9f850cac1" integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ== -globals@^17.7.0: - version "17.7.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-17.7.0.tgz#553d55090b4dde8209ec2da42580d6e7e7d8b10d" - integrity sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg== +globals@^17.11.0: + version "17.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.11.0.tgz#d643485bb30220d7751e511cf4f68c73d3870d87" + integrity sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw== globrex@^0.1.2: version "0.1.2" @@ -732,12 +732,12 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -minimatch@^10.2.4, "minimatch@^9.0.3 || ^10.1.2": - version "10.2.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" - integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== +minimatch@^10.2.4, minimatch@^10.2.5, "minimatch@^9.0.3 || ^10.1.2": + version "10.2.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef" + integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== dependencies: - brace-expansion "^5.0.2" + brace-expansion "^5.0.8" ms@^2.1.3: version "2.1.3" From 4af3770e3c91cc59b8ba6380403b1f23750771e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:52:46 +0200 Subject: [PATCH 05/19] chore(deps): bump body-parser from 1.20.4 to 2.3.0 in /test/crashtracker (#140) Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.4 to 2.3.0. - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.4...v2.3.0) --- updated-dependencies: - dependency-name: body-parser dependency-version: 2.3.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit c881997393cdda4df8efec8f05f5f33add8ca8b6) --- test/crashtracker/package-lock.json | 478 +++++++--------------------- test/crashtracker/package.json | 2 +- test/crashtracker/yarn.lock | 95 +----- 3 files changed, 121 insertions(+), 454 deletions(-) diff --git a/test/crashtracker/package-lock.json b/test/crashtracker/package-lock.json index 92fc554d..7267e4ce 100644 --- a/test/crashtracker/package-lock.json +++ b/test/crashtracker/package-lock.json @@ -6,7 +6,7 @@ "": { "dependencies": { "@datadog/segfaultify": "^0.1.1", - "body-parser": "^1.20.3", + "body-parser": "^2.3.0", "express": "^5.2.1" } }, @@ -32,22 +32,21 @@ "node": ">= 0.6" } }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -57,28 +56,17 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bytes": { @@ -160,12 +148,20 @@ } }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/depd": { @@ -177,16 +173,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -235,9 +221,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -304,177 +290,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -496,29 +311,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -608,9 +400,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -640,15 +432,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -682,12 +478,16 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -703,30 +503,34 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/negotiator": { @@ -815,12 +619,13 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -839,18 +644,18 @@ } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/router": { @@ -869,29 +674,6 @@ "node": ">= 18" } }, - "node_modules/router/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/router/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -924,54 +706,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -998,14 +732,14 @@ "license": "ISC" }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -1017,13 +751,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -1088,16 +822,34 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/unpipe": { diff --git a/test/crashtracker/package.json b/test/crashtracker/package.json index 4a3cd149..e6dfdd45 100644 --- a/test/crashtracker/package.json +++ b/test/crashtracker/package.json @@ -3,7 +3,7 @@ "main": "index.js", "dependencies": { "@datadog/segfaultify": "^0.1.1", - "body-parser": "^1.20.6", + "body-parser": "^2.3.0", "express": "^5.2.1" } } diff --git a/test/crashtracker/yarn.lock b/test/crashtracker/yarn.lock index 04aafa13..127b99eb 100644 --- a/test/crashtracker/yarn.lock +++ b/test/crashtracker/yarn.lock @@ -17,25 +17,7 @@ accepts@^2.0.0: mime-types "^3.0.0" negotiator "^1.0.0" -body-parser@^1.20.6: - version "1.20.6" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76" - integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g== - dependencies: - bytes "~3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "~1.2.0" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - on-finished "~2.4.1" - qs "~6.15.1" - raw-body "~2.5.3" - type-is "~1.6.18" - unpipe "~1.0.0" - -body-parser@^2.2.1: +body-parser@^2.2.1, body-parser@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437" integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw== @@ -76,7 +58,7 @@ content-disposition@^1.0.0: resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17" integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g== -content-type@^1.0.5, content-type@~1.0.5: +content-type@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -96,13 +78,6 @@ cookie@^0.7.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^4.4.0, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" @@ -110,16 +85,11 @@ debug@^4.4.0, debug@^4.4.3: dependencies: ms "^2.1.3" -depd@2.0.0, depd@^2.0.0, depd@~2.0.0: +depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -destroy@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -286,13 +256,6 @@ iconv-lite@^0.7.2, iconv-lite@~0.7.0: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -iconv-lite@~0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" @@ -313,11 +276,6 @@ math-intrinsics@^1.1.0: resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - media-typer@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" @@ -328,11 +286,6 @@ merge-descriptors@^2.0.0: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - mime-db@^1.54.0: version "1.54.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" @@ -345,18 +298,6 @@ mime-types@^3.0.0, mime-types@^3.0.2: dependencies: mime-db "^1.54.0" -mime-types@~2.1.24: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -377,7 +318,7 @@ object-inspect@^1.13.3, object-inspect@^1.13.4: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== -on-finished@^2.4.1, on-finished@~2.4.1: +on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -417,14 +358,6 @@ qs@^6.14.0, qs@^6.15.2: es-define-property "^1.0.1" side-channel "^1.1.1" -qs@~6.15.1: - version "6.15.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b" - integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A== - dependencies: - es-define-property "^1.0.1" - side-channel "^1.1.1" - range-parser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -440,16 +373,6 @@ raw-body@^3.0.2: iconv-lite "~0.7.0" unpipe "~1.0.0" -raw-body@~2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" - integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== - dependencies: - bytes "~3.1.2" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - unpipe "~1.0.0" - router@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" @@ -461,7 +384,7 @@ router@^2.2.0: parseurl "^1.3.3" path-to-regexp "^8.0.0" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -557,14 +480,6 @@ type-is@^2.0.1, type-is@^2.1.0: media-typer "^1.1.0" mime-types "^3.0.0" -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" From 4a94257e7c0ef325f41f0f36b7c41b5322d2c532 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 Sep 2026 17:41:50 +0200 Subject: [PATCH 06/19] perf(capabilities): remove redundant HTTP buffer copies (#242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Across two fresh Node 22.23.2 / V8 12.4 processes, direct Buffer reuse reduced 46-byte response conversion from 20.9–21.0 ns to 6.1–6.2 ns. Parsing the existing request-head Buffer reduced 71.2–71.8 ns to 32.5–32.8 ns in the same seven-trial benchmark. (cherry picked from commit 12991d874a444ea1e6bcbd0dfa878c76cdd5c9a9) --- crates/capabilities/src/http_transport.js | 16 ++++++++-------- test/http_transport.js | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/capabilities/src/http_transport.js b/crates/capabilities/src/http_transport.js index 0fd60257..53f91501 100644 --- a/crates/capabilities/src/http_transport.js +++ b/crates/capabilities/src/http_transport.js @@ -113,8 +113,11 @@ function applyEntityHeaders (headView, entity = getEntityHeaders()) { // `req._header`: that internal is undocumented and Bun's `node:http` ignores it, // so under Bun the request went out as `POST /` with no headers and the agent // dropped it. Header names/values are ASCII (latin1 round-trips losslessly). -function parseRequestHead (headBuf) { - const head = Buffer.from(headBuf).toString('latin1') +/** + * @param {Buffer} headBuffer + */ +function parseRequestHead (headBuffer) { + const head = headBuffer.toString('latin1') const term = head.indexOf('\r\n\r\n') const lines = (term === -1 ? head : head.slice(0, term)).split('\r\n') // Request line: `METHOD request-target HTTP/1.1` (no spaces in the target). @@ -230,12 +233,9 @@ module.exports.httpRequest = function (host, port, isHttps, socketPath, connecti resolve([ res.statusCode, res.rawHeaders, - // Copy the exact body bytes. `body` is a Buffer from Buffer.concat, - // which for small payloads is a view into Node's shared pool, so - // `body.buffer` is the whole pool — slicing by offset/length (via - // the Uint8Array(typedArray) copy ctor) is required to avoid - // handing the Rust side unrelated pooled memory. - new Uint8Array(body), + // Buffer is a Uint8Array with exact byteOffset and byteLength. + // Rust copies it into Bytes before this response is released. + body, ]) }) }) diff --git a/test/http_transport.js b/test/http_transport.js index 7333d8e8..821c171a 100644 --- a/test/http_transport.js +++ b/test/http_transport.js @@ -15,9 +15,8 @@ const fs = require('node:fs') const transport = require('../crates/capabilities/src/http_transport') -// Distinctive, multi-byte body so the pooled-buffer slicing in httpRequest -// (the reason for `new Uint8Array(body)` over `body.buffer`) is exercised: -// a small Buffer.concat result lands at a non-zero offset in Node's shared pool. +// Distinctive, multi-byte body so the pooled Buffer bounds in httpRequest are exercised. +// A small Buffer.concat result can start at a non-zero offset in Node's shared pool. const RESPONSE_BODY = '{"rate_by_service":{"service:test,env:":0.5}}' function fakeWasmMemory (headBytes) { From de1be883ab3c3fe41fe986eb6204d27cdfe99b9a Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 Sep 2026 18:15:57 +0200 Subject: [PATCH 07/19] chore(deps-dev): bump eslint-plugin-unicorn to 74 (#240) * chore(deps-dev): bump eslint-plugin-unicorn to 71.1.0 The updated recommended preset adds opinionated checks that conflict with Node 18 support and established runtime patterns. Keep its correctness checks enabled and align the deliberate exceptions with dd-trace-js. Refs: https://github.com/DataDog/libdatadog-nodejs/pull/162 * chore(deps-dev): update eslint-plugin-unicorn to 74.0.0 The latest recommended preset adds style and parsing diagnostics. Align the project exceptions with dd-trace-js and keep the remaining checks enabled. (cherry picked from commit 7bc0b3cbda5b86557632ecf8967e9ebbfaf5c63d) --- crates/capabilities/src/http_transport.js | 5 +- eslint.config.js | 25 ++ package.json | 2 +- packages/libdatadog/scripts/inline-wasm.js | 2 +- packages/libdatadog/test/exporter.test.js | 2 +- packages/libdatadog/test/package.test.js | 10 +- test/http_transport.js | 1 + test/pipeline.js | 14 +- test/wasm/sketches/index.js | 2 +- yarn.lock | 268 ++++++++++++++------- 10 files changed, 235 insertions(+), 96 deletions(-) diff --git a/crates/capabilities/src/http_transport.js b/crates/capabilities/src/http_transport.js index 53f91501..0adb7822 100644 --- a/crates/capabilities/src/http_transport.js +++ b/crates/capabilities/src/http_transport.js @@ -24,7 +24,8 @@ let storage = f => f() // The second alternative is the PCF / Garden regexp; no suffix ($) to avoid // matching pod UIDs. See // https://github.com/DataDog/datadog-agent/blob/7.40.x/pkg/util/cgroups/reader.go#L50 -const uuidSource = String.raw`[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$` +const uuidSource = '[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|' + + '[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$' const containerSource = '[0-9a-f]{64}' const taskSource = String.raw`[0-9a-f]{32}-\d+` const lineReg = /^(\d+):([^:]*):(.+)$/m @@ -121,7 +122,7 @@ function parseRequestHead (headBuffer) { const term = head.indexOf('\r\n\r\n') const lines = (term === -1 ? head : head.slice(0, term)).split('\r\n') // Request line: `METHOD request-target HTTP/1.1` (no spaces in the target). - const [method, path] = lines[0].split(' ') + const [method, path] = lines[0].split(' ', 2) const headers = {} for (let i = 1; i < lines.length; i++) { const colon = lines[i].indexOf(':') diff --git a/eslint.config.js b/eslint.config.js index daf48dea..05b8ed8d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -39,8 +39,33 @@ module.exports = [ }], 'n/no-process-exit': 'off', // Duplicate of unicorn/no-process-exit 'prefer-const': 'error', + + // Match the deliberate exceptions in dd-trace-js. The remaining recommended rules stay enabled. + 'unicorn/consistent-boolean-name': 'off', // Would rename public API and config booleans + 'unicorn/filename-case': ['error', { + case: 'kebabCase', + // The WASM package name must match the Rust crate name. + ignore: ['^library_config$'], + }], + 'unicorn/name-replacements': 'off', // Naming churn + 'unicorn/no-break-in-nested-loop': 'off', // Conflicts with performance-oriented loops + 'unicorn/no-global-object-property-assignment': 'off', // Needed for intentional global initialization + 'unicorn/no-negated-array-predicate': 'off', // Predicate inversion is harder to read + 'unicorn/no-return-array-push': 'off', // Questionable benefit + 'unicorn/no-this-outside-of-class': 'off', // Object methods and callback APIs can bind `this` + 'unicorn/no-top-level-assignment-in-function': 'off', // Module-level singletons are assigned from functions + 'unicorn/no-undeclared-class-members': 'off', // Field declarations can change object shape + 'unicorn/prefer-await': 'off', // Production code uses callbacks and synchronous patterns + 'unicorn/prefer-minimal-ternary': 'off', // Conflicts with restricted syntax in consumers 'unicorn/prefer-module': 'off', // We use CJS + 'unicorn/prefer-number-is-safe-integer': 'off', // Number.isInteger() can be intentional + 'unicorn/prefer-private-class-fields': 'off', // Existing underscore fields can cross module boundaries + 'unicorn/prefer-promise-with-resolvers': 'off', // Promise.withResolvers() requires Node.js 22 + 'unicorn/prefer-simple-condition-first': 'off', // Needs a short-circuit behavior audit + 'unicorn/prefer-then-catch': 'off', // Rejection handlers broaden rejection boundaries + 'unicorn/prefer-unicode-code-point-escapes': 'off', // Questionable benefit 'unicorn/prevent-abbreviations': 'off', + 'unicorn/single-line-block-comment-style': 'off', // Preserve compact JSDoc typedefs }, }, { diff --git a/package.json b/package.json index 132f0568..cd4eef95 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint": "^10.9.1", "eslint-plugin-import-x": "^4.17.1", "eslint-plugin-n": "^18.2.2", - "eslint-plugin-unicorn": "^63.0.0", + "eslint-plugin-unicorn": "^74.0.0", "globals": "^17.11.0" } } diff --git a/packages/libdatadog/scripts/inline-wasm.js b/packages/libdatadog/scripts/inline-wasm.js index fe164f11..ea1634b8 100644 --- a/packages/libdatadog/scripts/inline-wasm.js +++ b/packages/libdatadog/scripts/inline-wasm.js @@ -33,7 +33,7 @@ fs.writeFileSync( gluePath, glue.replace( loader, - `const wasmBytes = require('node:zlib').brotliDecompressSync(Buffer.from('${encodedWasm}', 'base64'));`, + () => `const wasmBytes = require('node:zlib').brotliDecompressSync(Buffer.from('${encodedWasm}', 'base64'));`, ), ) fs.rmSync(wasmPath) diff --git a/packages/libdatadog/test/exporter.test.js b/packages/libdatadog/test/exporter.test.js index bcb22c74..f26018ec 100644 --- a/packages/libdatadog/test/exporter.test.js +++ b/packages/libdatadog/test/exporter.test.js @@ -387,7 +387,7 @@ function createExporter (pipeline, server, options = {}) { function exporterOptions () { return { - endpoint: 'http://example.test/api/v2/spans', + endpoint: 'https://example.test/api/v2/spans', apiKey: 'test-api-key', tracerVersion: '0.1.0', languageVersion: process.version, diff --git a/packages/libdatadog/test/package.test.js b/packages/libdatadog/test/package.test.js index a4579af2..ebb9f24d 100644 --- a/packages/libdatadog/test/package.test.js +++ b/packages/libdatadog/test/package.test.js @@ -11,7 +11,7 @@ const packageRoot = path.join(__dirname, '..') const repositoryRoot = path.join(packageRoot, '..', '..') test('publishes the universal libdatadog package', () => { - const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'))) + const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) assert.strictEqual(packageJson.name, '@datadog/libdatadog') assert.strictEqual(packageJson.exports['./wasm'].require, './wasm.js') @@ -20,6 +20,7 @@ test('publishes the universal libdatadog package', () => { const wasmPackageJson = JSON.parse(fs.readFileSync( path.join(packageRoot, 'wasm', 'package.json'), + 'utf8', )) assert.strictEqual( wasmPackageJson.exports['./remote-config'].require, @@ -28,11 +29,12 @@ test('publishes the universal libdatadog package', () => { }) test('uses the libdatadog release version', () => { - const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'))) + const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) const wasmPackageJson = JSON.parse(fs.readFileSync( path.join(packageRoot, 'wasm', 'package.json'), + 'utf8', )) - const repositoryPackageJson = JSON.parse(fs.readFileSync(path.join(repositoryRoot, 'package.json'))) + const repositoryPackageJson = JSON.parse(fs.readFileSync(path.join(repositoryRoot, 'package.json'), 'utf8')) assert.strictEqual(packageJson.version, repositoryPackageJson.version) assert.strictEqual(wasmPackageJson.version, repositoryPackageJson.version) @@ -55,7 +57,7 @@ test('carries the repository metadata npm provenance verifies against', () => { path.join(packageRoot, 'package.json'), path.join(packageRoot, 'wasm', 'package.json'), ]) { - const manifest = JSON.parse(fs.readFileSync(manifestPath)) + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) assert.strictEqual(manifest.repository?.url, expectedUrl, `${manifest.name} repository.url`) } diff --git a/test/http_transport.js b/test/http_transport.js index 821c171a..e37593f4 100644 --- a/test/http_transport.js +++ b/test/http_transport.js @@ -321,6 +321,7 @@ describe('http_transport request body lifetime', () => { 'utf8', ) const memory = new WebAssembly.Memory({ initial: 1 }) + // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- WebAssembly.Memory.buffer is the full buffer. const bytes = new Uint8Array(memory.buffer) bytes.set(head, 0) bytes.set(body, head.length) diff --git a/test/pipeline.js b/test/pipeline.js index 6f452916..88fea812 100644 --- a/test/pipeline.js +++ b/test/pipeline.js @@ -178,6 +178,7 @@ class NativeSpansInterface { _refreshViews () { this._cqbView = new DataView(this._wasmMemory.buffer, this._cqbPtr) + // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- WebAssembly.Memory.buffer is the full buffer. this._cqbBytes = new Uint8Array(this._wasmMemory.buffer, this._cqbPtr) } @@ -458,20 +459,24 @@ describe('pipeline', { skip }, () => { describe('span attributes', () => { it('should set and get string tags', () => { const span = nativeSpans.createSpan() + // eslint-disable-next-line unicorn/prefer-https -- Plain HTTP is the tag value under test. + const httpUrl = 'http://example.com/api' span.setTag('http.method', 'GET') - span.setTag('http.url', 'http://example.com/api') + span.setTag('http.url', httpUrl) assert.strictEqual(span.getTag('http.method'), 'GET') - assert.strictEqual(span.getTag('http.url'), 'http://example.com/api') + assert.strictEqual(span.getTag('http.url'), httpUrl) }) it('should set and get numeric tags', () => { const span = nativeSpans.createSpan() + // eslint-disable-next-line unicorn/prefer-math-constants -- The exact fractional value is the test input. + const metricValue = 3.14159 span.setTag('http.status_code', 200) - span.setTag('custom.metric', 3.141_59) + span.setTag('custom.metric', metricValue) assert.strictEqual(span.getTag('http.status_code'), 200) - assert.strictEqual(span.getTag('custom.metric'), 3.141_59) + assert.strictEqual(span.getTag('custom.metric'), metricValue) }) it('should set and get error state', () => { @@ -730,6 +735,7 @@ describe('pipeline', { skip }, () => { // second entry). const ptr = nativeSpans.state.string_table_input_ptr() const view = new DataView(wasmMemory.buffer, ptr) + // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- WebAssembly.Memory.buffer is the full buffer. const bytes = new Uint8Array(wasmMemory.buffer, ptr) const entries = [[60_001, 'bulk-key'], [60_002, 'bulk-val']] let off = 0 diff --git a/test/wasm/sketches/index.js b/test/wasm/sketches/index.js index dfae84f6..3932318d 100644 --- a/test/wasm/sketches/index.js +++ b/test/wasm/sketches/index.js @@ -13,7 +13,7 @@ sketch.addWithCount(2, 3) assert.strictEqual(sketch.count(), 4) assert.throws(() => sketch.add(-1), /point is invalid/) -assert.throws(() => sketch.addWithCount(1, Number.NaN), /count is invalid/) +assert.throws(() => sketch.addWithCount(1, NaN), /count is invalid/) const encoded = sketch.encode() assert(encoded instanceof Uint8Array) diff --git a/yarn.lock b/yarn.lock index 8efd81a9..fa5b8f38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@babel/helper-validator-identifier@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" - integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== - "@emnapi/core@^1.4.3": version "1.8.1" resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349" @@ -29,13 +24,20 @@ dependencies: tslib "^2.4.0" -"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.5.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.0", "@eslint-community/eslint-utils@^4.9.1": +"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.5.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" +"@eslint-community/eslint-utils@^4.10.1": + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6" + integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.11.0", "@eslint-community/regexpp@^4.12.2": version "4.12.2" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" @@ -64,6 +66,14 @@ dependencies: "@types/json-schema" "^7.0.15" +"@eslint/css-tree@^4.0.5": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@eslint/css-tree/-/css-tree-4.1.0.tgz#3ad72d9e51779e1fd8550d67868ac1e94a27b39b" + integrity sha512-cg0ohyrAG3swyGqt8t1K/OK97DqBw/ftDvlvyY1fmEst5B40UOmsimwLENq74z2dyw5CDM+3zJIW+CV2nFNDdA== + dependencies: + mdn-data "2.34.0" + source-map-js "^1.2.1" + "@eslint/js@^10.0.1": version "10.0.1" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583" @@ -275,10 +285,10 @@ balanced-match@^4.0.2: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== -baseline-browser-mapping@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9" - integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== +baseline-browser-mapping@^2.11.12: + version "2.11.20" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz#26078c7a4b08299656ea7ddceaebec955dc44303" + integrity sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw== brace-expansion@^5.0.8: version "5.0.9" @@ -287,55 +297,53 @@ brace-expansion@^5.0.8: dependencies: balanced-match "^4.0.2" -browserslist@^4.28.1: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== +browserslist@^4.28.7, browserslist@^4.28.8: + version "4.28.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.8.tgz#a3c79ceb70028527e5da7dafc887f3200b5168c0" + integrity sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA== dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" + baseline-browser-mapping "^2.11.12" + caniuse-lite "^1.0.30001809" + electron-to-chromium "^1.5.402" + node-releases "^2.0.53" + update-browserslist-db "^1.3.0" builtin-modules@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-5.0.0.tgz#9be95686dedad2e9eed05592b07733db87dcff1a" integrity sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg== -caniuse-lite@^1.0.30001759: - version "1.0.30001777" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz#028f21e4b2718d138b55e692583e6810ccf60691" - integrity sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ== +caniuse-lite@^1.0.30001809: + version "1.0.30001810" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2" + integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg== change-case@^5.4.4: version "5.4.4" resolved "https://registry.yarnpkg.com/change-case/-/change-case-5.4.4.tgz#0d52b507d8fb8f204343432381d1a6d7bff97a02" integrity sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w== -ci-info@^4.3.1: +ci-info@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== -clean-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" - integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== - dependencies: - escape-string-regexp "^1.0.5" - comment-parser@^1.4.1: version "1.4.5" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.5.tgz#6c595cd090737a1010fe5ff40d86e1d21b7bd6ce" integrity sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw== -core-js-compat@^3.46.0: - version "3.48.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6" - integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q== +convert-hrtime@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/convert-hrtime/-/convert-hrtime-5.0.0.tgz#f2131236d4598b95de856926a67100a0a97e9fa3" + integrity sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg== + +core-js-compat@^3.50.0: + version "3.50.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.50.0.tgz#d5922c2a692ab1cba6078c920e7c5567421ae08e" + integrity sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q== dependencies: - browserslist "^4.28.1" + browserslist "^4.28.7" cross-spawn@^7.0.6: version "7.0.6" @@ -358,10 +366,15 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -electron-to-chromium@^1.5.263: - version "1.5.307" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz#09f8973100c39fb0d003b890393cd1d58932b1c8" - integrity sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg== +detect-indent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.2.tgz#16c516bf75d4b2f759f68214554996d467c8d648" + integrity sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A== + +electron-to-chromium@^1.5.402: + version "1.5.420" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz#fc66d26a722d6f227e2092acdf38dd55b198cb44" + integrity sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA== enhanced-resolve@^5.17.1: version "5.20.0" @@ -371,16 +384,16 @@ enhanced-resolve@^5.17.1: graceful-fs "^4.2.4" tapable "^2.3.0" +entities@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-8.0.0.tgz#c1df5fe3602429747fa233d0dd26f142f0ce4743" + integrity sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA== + escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -439,27 +452,31 @@ eslint-plugin-n@^18.2.2: ignore "^5.3.2" semver "^7.6.3" -eslint-plugin-unicorn@^63.0.0: - version "63.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-63.0.0.tgz#db210b87bb66f0f15ab675ba13d9f1fb61016b22" - integrity sha512-Iqecl9118uQEXYh7adylgEmGfkn5es3/mlQTLLkd4pXkIk9CTGrAbeUux+YljSa2ohXCBmQQ0+Ej1kZaFgcfkA== +eslint-plugin-unicorn@^74.0.0: + version "74.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-74.0.0.tgz#9b423103108d404971f8730816c9fc14182a97ae" + integrity sha512-AGnsGi2SxHg1HEAXxn9nSnZfyjvTWkxm8E8hpd/9tD6dLjBUdcD7+D6ZN64HmmCXTSXlrwVyUqe20Uyb2CaurA== dependencies: - "@babel/helper-validator-identifier" "^7.28.5" - "@eslint-community/eslint-utils" "^4.9.0" + "@eslint-community/eslint-utils" "^4.10.1" + "@eslint/css-tree" "^4.0.5" + browserslist "^4.28.8" change-case "^5.4.4" - ci-info "^4.3.1" - clean-regexp "^1.0.0" - core-js-compat "^3.46.0" + ci-info "^4.4.0" + core-js-compat "^3.50.0" + detect-indent "^7.0.2" + entities "^8.0.0" find-up-simple "^1.0.1" - globals "^16.4.0" + globals "^17.11.0" indent-string "^5.0.0" is-builtin-module "^5.0.0" - jsesc "^3.1.0" + is-identifier "^1.1.0" pluralize "^8.0.0" - regexp-tree "^0.1.27" - regjsparser "^0.13.0" - semver "^7.7.3" + quote-js-string "^0.1.0" + regjsparser "^0.13.2" + reserved-identifiers "^1.2.0" + semver "^7.8.5" strip-indent "^4.1.1" + yaml "^2.9.0" eslint-scope@^9.1.2: version "9.1.2" @@ -612,6 +629,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== +function-timeout@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/function-timeout/-/function-timeout-1.0.2.tgz#e5a7b6ffa523756ff20e1231bbe37b5f373aadd5" + integrity sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA== + get-tsconfig@^4.10.1, get-tsconfig@^4.8.1: version "4.13.6" resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.6.tgz#2fbfda558a98a691a798f123afd95915badce876" @@ -631,11 +653,6 @@ globals@^15.11.0: resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== -globals@^16.4.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-16.5.0.tgz#ccf1594a437b97653b2be13ed4d8f5c9f850cac1" - integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ== - globals@^17.11.0: version "17.11.0" resolved "https://registry.yarnpkg.com/globals/-/globals-17.11.0.tgz#d643485bb30220d7751e511cf4f68c73d3870d87" @@ -651,6 +668,13 @@ graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +identifier-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/identifier-regex/-/identifier-regex-1.1.0.tgz#042abfaf28db15223436661f3b9ec882649f6ef0" + integrity sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA== + dependencies: + reserved-identifiers "^1.0.0" + ignore@^5.2.0, ignore@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -685,12 +709,20 @@ is-glob@^4.0.0, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-identifier@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-identifier/-/is-identifier-1.1.0.tgz#6b406e15a429f7196f6496e09f8333bcb4b2db1b" + integrity sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw== + dependencies: + identifier-regex "^1.1.0" + super-regex "^1.1.0" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jsesc@^3.1.0, jsesc@~3.1.0: +jsesc@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== @@ -732,6 +764,20 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +make-asynchronous@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/make-asynchronous/-/make-asynchronous-1.1.0.tgz#6225f7f1ccaab9acaac5e2fcd0b075afefff19aa" + integrity sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg== + dependencies: + p-event "^6.0.0" + type-fest "^4.6.0" + web-worker "^1.5.0" + +mdn-data@2.34.0: + version "2.34.0" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.34.0.tgz#414f99ec5c9001ef74981b1545c2ca34f0bdaeb9" + integrity sha512-OgIlLv0NxJKVW4GTSAoEgpRGd4F2XCqGinK0MsMlBCCS/Zcm2/LsbercNWNA7PeMMcjl75NnI97eqyo7zkdxWA== + minimatch@^10.2.4, minimatch@^10.2.5, "minimatch@^9.0.3 || ^10.1.2": version "10.2.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef" @@ -754,10 +800,10 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-releases@^2.0.27: - version "2.0.36" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" - integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA== +node-releases@^2.0.53: + version "2.0.54" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.54.tgz#09af17d5647aa9f221ec5cf2becb95b68a981afe" + integrity sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ== optionator@^0.9.3: version "0.9.4" @@ -771,6 +817,13 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +p-event@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-6.0.1.tgz#8f62a1e3616d4bc01fce3abda127e0383ef4715b" + integrity sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w== + dependencies: + p-timeout "^6.1.2" + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -785,6 +838,11 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-timeout@^6.1.2: + version "6.1.4" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.4.tgz#418e1f4dd833fa96a2e3f532547dd2abdb08dbc2" + integrity sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -820,28 +878,38 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -regexp-tree@^0.1.27: - version "0.1.27" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" - integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== +quote-js-string@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/quote-js-string/-/quote-js-string-0.1.0.tgz#812aef957a2dfb31d32f0d9e9662fe558bc9842b" + integrity sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA== -regjsparser@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" - integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== +regjsparser@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.2.tgz#f654734b5c588b22ba3e21693b30523417180808" + integrity sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ== dependencies: jsesc "~3.1.0" +reserved-identifiers@^1.0.0, reserved-identifiers@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz#d2982cd698e317dd3dced1ee1c52412dbd64fc64" + integrity sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw== + resolve-pkg-maps@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -semver@^7.5.4, semver@^7.6.3, semver@^7.7.2, semver@^7.7.3: +semver@^7.5.4, semver@^7.6.3, semver@^7.7.2: version "7.7.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== +semver@^7.8.5: + version "7.8.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -854,6 +922,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + stable-hash-x@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/stable-hash-x/-/stable-hash-x-0.2.0.tgz#dfd76bfa5d839a7470125c6a6b3c8b22061793e9" @@ -864,11 +937,27 @@ strip-indent@^4.1.1: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.1.1.tgz#aba13de189d4ad9a17f6050e76554ac27585c7af" integrity sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA== +super-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/super-regex/-/super-regex-1.1.0.tgz#14b69b6374f7b3338db52ecd511dae97c27acf75" + integrity sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ== + dependencies: + function-timeout "^1.0.1" + make-asynchronous "^1.0.1" + time-span "^5.1.0" + tapable@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== +time-span@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/time-span/-/time-span-5.1.0.tgz#80c76cf5a0ca28e0842d3f10a4e99034ce94b90d" + integrity sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA== + dependencies: + convert-hrtime "^5.0.0" + tslib@^2.4.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" @@ -881,6 +970,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-fest@^4.6.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + unrs-resolver@^1.9.2: version "1.11.1" resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" @@ -908,10 +1002,10 @@ unrs-resolver@^1.9.2: "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" -update-browserslist-db@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== +update-browserslist-db@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz#9d99fbff56c50bb11ba5fd35cece5916da595836" + integrity sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -923,6 +1017,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +web-worker@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.5.0.tgz#71b2b0fbcc4293e8f0aa4f6b8a3ffebff733dcc5" + integrity sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw== + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -935,6 +1034,11 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +yaml@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From f3c69dcdb927202f88e87ed330f6867924db1e19 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 Sep 2026 18:21:09 +0200 Subject: [PATCH 08/19] refactor(libdatadog): use callbacks for agentless transport (#239) * refactor(libdatadog): use callbacks for agentless transport The public callback API still crossed Promise-based host and WASM boundaries, which left two completion models for each export. Rust keeps retry and cancellation ownership; the final callback preserves the caller's async context. Active-buffer admission completes as a local drop. A network error would retain the discarded payload through the Rust retry backoff. * refactor(libdatadog): leave export context to callers dd-trace enters every export inside its no-tracing context, so libdatadog does not need to restore caller context. (cherry picked from commit 6dd6ce9718d107ecadc4931336772f70ab30e9d2) --- .../libdatadog-wasm/src/data_pipeline/mod.rs | 89 ++++++++----- .../libdatadog/lib/agentless-transport.js | 123 ++++++++++-------- packages/libdatadog/lib/agentless.js | 26 ++-- packages/libdatadog/test/exporter.test.js | 102 +++++++++++++-- packages/libdatadog/test/transport.test.js | 75 +++++++---- 5 files changed, 279 insertions(+), 136 deletions(-) diff --git a/crates/libdatadog-wasm/src/data_pipeline/mod.rs b/crates/libdatadog-wasm/src/data_pipeline/mod.rs index fd7c58b4..51e40831 100644 --- a/crates/libdatadog-wasm/src/data_pipeline/mod.rs +++ b/crates/libdatadog-wasm/src/data_pipeline/mod.rs @@ -5,8 +5,9 @@ use std::rc::Rc; use std::time::Duration; use bytes::Bytes; +use futures::channel::oneshot; use futures::future::{AbortHandle, Abortable}; -use js_sys::{Array, Function, Object, Promise, Reflect, Uint8Array}; +use js_sys::{Array, Function, Object, Reflect, Uint8Array}; use libdatadog_data_pipeline::{ send_agentless_v04, AgentlessTraceConfig, ObfuscationConfig, SendAgentlessV04Error, TracerMetadata, DEFAULT_AGENTLESS_TIMEOUT, @@ -20,7 +21,7 @@ use libdd_trace_obfuscation::obfuscation_config::{ use libdd_trace_obfuscation::replacer::ReplaceRule; use libdd_trace_obfuscation::sql::{SqlObfuscateConfig, SqlObfuscationMode}; use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::JsFuture; +use wasm_bindgen_futures::spawn_local; struct AgentlessExporterOptions { endpoint: String, @@ -76,13 +77,23 @@ impl HttpClientCapability for HostCapabilities { ) -> Result, HttpError> { let id = self.next_call_id(); let plan = request_value(id, request)?; + let (sender, receiver) = oneshot::channel(); + let complete = Closure::once(move |error: JsValue, response: JsValue| { + let result = if error.is_undefined() { + Ok(response) + } else { + Err(error) + }; + let _ = sender.send(result); + }); let mut guard = CancelGuard::new(id, self.cancel_request.clone()); - let promise = self - .request - .call1(&JsValue::UNDEFINED, &plan) - .map(|value| Promise::resolve(&value)) + self.request + .call2(&JsValue::UNDEFINED, &plan, complete.as_ref()) + .map_err(network_error)?; + let response = receiver + .await + .map_err(|_| callback_dropped("request"))? .map_err(network_error)?; - let response = JsFuture::from(promise).await.map_err(network_error)?; guard.disarm(); let status = required_number(&response, "status")?; @@ -104,13 +115,22 @@ impl SleepCapability for HostCapabilities { async fn sleep(&self, duration: Duration) { let id = self.next_call_id(); let milliseconds = duration_millis(duration); + let (sender, receiver) = oneshot::channel(); + let complete = Closure::once(move || { + let _ = sender.send(()); + }); let mut guard = CancelGuard::new(id, self.cancel_sleep.clone()); - if let Ok(value) = self.sleep.call2( - &JsValue::UNDEFINED, - &JsValue::from_f64(f64::from(id)), - &JsValue::from_f64(f64::from(milliseconds)), - ) { - let _ = JsFuture::from(Promise::resolve(&value)).await; + if self + .sleep + .call3( + &JsValue::UNDEFINED, + &JsValue::from_f64(f64::from(id)), + &JsValue::from_f64(f64::from(milliseconds)), + complete.as_ref(), + ) + .is_ok() + { + let _ = receiver.await; } guard.disarm(); } @@ -148,8 +168,8 @@ impl Drop for CancelGuard { #[wasm_bindgen] pub struct AgentlessExporter { - metadata: TracerMetadata, - config: AgentlessTraceConfig, + metadata: Rc, + config: Rc, capabilities: HostCapabilities, in_flight: Rc>>, next_operation_id: Cell, @@ -212,8 +232,8 @@ impl AgentlessExporter { }; Ok(Self { - metadata, - config, + metadata: Rc::new(metadata), + config: Rc::new(config), capabilities, in_flight: Rc::new(RefCell::new(HashMap::new())), next_operation_id: Cell::new(1), @@ -221,23 +241,28 @@ impl AgentlessExporter { } #[wasm_bindgen(js_name = sendV04)] - pub async fn send_v04(&self, payload: &[u8]) -> Result<(), JsValue> { + pub fn send_v04(&self, payload: Vec, done: Function) { let operation_id = self.next_operation_id.get(); self.next_operation_id.set(operation_id.wrapping_add(1)); let (abort, registration) = AbortHandle::new_pair(); self.in_flight.borrow_mut().insert(operation_id, abort); - let _guard = OperationGuard { - id: operation_id, - in_flight: self.in_flight.clone(), - }; - let send = - send_agentless_v04(&self.capabilities, payload, &self.metadata, &self.config, false); - - match Abortable::new(send, registration).await { - Ok(Ok(_)) => Ok(()), - Ok(Err(error)) => Err(send_error(error)), - Err(_) => Err(JsValue::from_str("data-pipeline export was cancelled")), - } + let capabilities = self.capabilities.clone(); + let config = self.config.clone(); + let in_flight = self.in_flight.clone(); + let metadata = self.metadata.clone(); + spawn_local(async move { + let _guard = OperationGuard { + id: operation_id, + in_flight, + }; + let send = send_agentless_v04(&capabilities, &payload, &metadata, &config, false); + let error = match Abortable::new(send, registration).await { + Ok(Ok(_)) => JsValue::UNDEFINED, + Ok(Err(error)) => send_error(error), + Err(_) => JsValue::from_str("data-pipeline export was cancelled"), + }; + let _ = done.call1(&JsValue::UNDEFINED, &error); + }); } #[wasm_bindgen(js_name = cancelAll)] @@ -311,6 +336,10 @@ fn network_error(error: JsValue) -> HttpError { HttpError::Network(anyhow::anyhow!(js_error_message(error))) } +fn callback_dropped(name: &str) -> HttpError { + HttpError::Network(anyhow::anyhow!("JavaScript {name} callback was dropped")) +} + fn send_error(error: SendAgentlessV04Error) -> JsValue { JsValue::from_str(&error.to_string()) } diff --git a/packages/libdatadog/lib/agentless-transport.js b/packages/libdatadog/lib/agentless-transport.js index 1174b55f..3d09a6ef 100644 --- a/packages/libdatadog/lib/agentless-transport.js +++ b/packages/libdatadog/lib/agentless-transport.js @@ -1,6 +1,12 @@ 'use strict' +/** @typedef {{ name: string, value: string }} Header */ +/** @typedef {{ id: number, url: string, method: string, headers: Header[], body: Uint8Array }} RequestPlan */ +/** @typedef {{ status: number | undefined, body: Buffer }} Response */ +/** @typedef {(error?: Error, response?: Response) => void} RequestCallback */ + const maxActiveBufferSize = 16 * 1024 * 1024 +const discardedResponse = { status: 200, body: Buffer.alloc(0) } let activeBufferSize = 0 @@ -8,77 +14,88 @@ function createHostTransport () { const requests = new Map() const timers = new Map() - function request ({ id, url, method, headers: headerList, body }) { + /** + * @param {RequestPlan} plan + * @param {RequestCallback} done + */ + function request ({ id, url, method, headers: headerList, body }, done) { + const bodySize = body.byteLength + if (activeBufferSize + bodySize > maxActiveBufferSize) { + done(undefined, discardedResponse) + return + } + const target = new URL(url) const client = target.protocol === 'https:' ? require('node:https') : require('node:http') const headers = Object.fromEntries(headerList.map(({ name, value }) => [name, value])) - const bodySize = body.byteLength - if (activeBufferSize + bodySize > maxActiveBufferSize) { - return Promise.reject(new Error('Maximum active agentless request buffer size reached: payload is discarded.')) + let settled = false + /** + * @param {Error | undefined} error + * @param {Response} [response] + * @param {boolean} [notify=true] + */ + const finish = (error, response, notify = true) => { + if (settled) return false + settled = true + requests.delete(id) + activeBufferSize -= bodySize + if (notify) done(error, response) + return true } + const outgoing = client.request(target, { + agent: false, + headers: { ...headers, connection: 'close' }, + method, + }, (response) => { + const chunks = [] + response.on('data', chunk => chunks.push(chunk)) + response.once('aborted', () => finish(new Error('response aborted'))) + response.once('error', finish) + response.once('end', () => finish(undefined, { + status: response.statusCode, + body: Buffer.concat(chunks), + })) + }) + activeBufferSize += bodySize - return new Promise((resolve, reject) => { - let settled = false - const finish = (callback, value) => { - if (settled) return - settled = true - requests.delete(id) - activeBufferSize -= bodySize - callback(value) - } - const outgoing = client.request(target, { - agent: false, - headers: { ...headers, connection: 'close' }, - method, - }, (response) => { - const chunks = [] - response.on('data', chunk => chunks.push(chunk)) - response.once('aborted', () => finish(reject, new Error('response aborted'))) - response.once('error', error => finish(reject, error)) - response.on('end', () => finish(resolve, { - status: response.statusCode, - body: Buffer.concat(chunks), - })) - }) - activeBufferSize += bodySize - - requests.set(id, { - cancel: () => { - const error = new Error('agentless request was cancelled') - finish(reject, error) - outgoing.destroy(error) - }, - }) - outgoing.once('error', error => finish(reject, error)) - outgoing.end(body) + requests.set(id, { + cancel: () => { + if (finish(undefined, undefined, false)) outgoing.destroy() + }, }) + outgoing.once('error', finish) + outgoing.end(body) } + /** @param {number} id */ function cancelRequest (id) { requests.get(id)?.cancel() } // TODO(libdd-capabilities): Make host-backed capability futures cancel their - // underlying operation when dropped. Then sleep can return a cancellable - // operation directly, removing timer IDs, the timers map, and cancelSleep. - function sleep (id, milliseconds) { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { + // underlying operation when dropped. Then the transport can remove timer IDs, + // the timers map, and cancelSleep. + /** + * @param {number} id + * @param {number} milliseconds + * @param {() => void} done + */ + function sleep (id, milliseconds, done) { + const timeout = setTimeout(() => { + timers.delete(id) + done() + }, milliseconds) + timeout.unref?.() + timers.set(id, { + cancel: () => { + clearTimeout(timeout) timers.delete(id) - resolve() - }, milliseconds) - timeout.unref?.() - timers.set(id, { - cancel: () => { - clearTimeout(timeout) - timers.delete(id) - reject(new Error('agentless timer was cancelled')) - }, - }) + }, }) } + /** @param {number} id */ function cancelSleep (id) { timers.get(id)?.cancel() } diff --git a/packages/libdatadog/lib/agentless.js b/packages/libdatadog/lib/agentless.js index 5ea5a7f5..f9fa5292 100644 --- a/packages/libdatadog/lib/agentless.js +++ b/packages/libdatadog/lib/agentless.js @@ -45,25 +45,23 @@ class AgentlessExporter { return } - let operation + /** @param {unknown} error */ + const complete = (error) => { + if (error !== undefined) { + const message = errorMessage(error) + if (!this.#closed || message !== canceledError) { + log.error('Failed to send data-pipeline export: %s', message) + } + } + done() + } + try { - operation = this.#binding.sendV04(payload) + this.#binding.sendV04(payload, complete) } catch (error) { log.error('Failed to send data-pipeline export: %s', errorMessage(error)) done() - return } - - operation.then( - done, - (error) => { - const message = errorMessage(error) - if (!this.#closed || message !== canceledError) { - log.error('Failed to send data-pipeline export: %s', message) - } - done() - }, - ) } close () { diff --git a/packages/libdatadog/test/exporter.test.js b/packages/libdatadog/test/exporter.test.js index f26018ec..530221de 100644 --- a/packages/libdatadog/test/exporter.test.js +++ b/packages/libdatadog/test/exporter.test.js @@ -9,11 +9,15 @@ const { zstdDecompressSync } = require('node:zlib') const { encode } = require('@msgpack/msgpack') +const { createHostTransport } = require('../lib/agentless-transport') + const zstdMagic = Buffer.from([0x28, 0xB5, 0x2F, 0xFD]) const packageRoot = path.join(__dirname, '..') const wasmArtifact = path.join(packageRoot, 'wasm', 'dist', 'libdatadog_wasm.js') +/** @typedef {(error?: unknown) => void} BindingDone */ + test('package entry points defer unused agentless modules', { skip: !fs.existsSync(wasmArtifact), }, () => { @@ -28,8 +32,12 @@ test('package entry points defer unused agentless modules', { test('agentless exporter reports completion through its callback', async () => { class BindingExporter { - sendV04 () { - return Promise.resolve() + /** + * @param {Uint8Array} payload + * @param {BindingDone} done + */ + sendV04 (payload, done) { + done() } cancelAll () {} @@ -53,8 +61,12 @@ test('agentless exporter reports completion through its callback', async () => { test('agentless exporter logs asynchronous failures before reporting completion', async () => { class BindingExporter { - sendV04 () { - return Promise.reject('intake unavailable') + /** + * @param {Uint8Array} payload + * @param {BindingDone} done + */ + sendV04 (payload, done) { + queueMicrotask(() => done('intake unavailable')) } cancelAll () {} @@ -105,13 +117,15 @@ test('agentless exporter logs synchronous failures before reporting completion', }) test('agentless exporter logs failures settled before close', async () => { - let rejectSend + let completeSend class BindingExporter { - sendV04 () { - return new Promise((resolve, reject) => { - rejectSend = reject - }) + /** + * @param {Uint8Array} payload + * @param {BindingDone} done + */ + sendV04 (payload, done) { + completeSend = done } cancelAll () {} @@ -127,7 +141,7 @@ test('agentless exporter logs failures settled before close', async () => { }, log) }) - rejectSend('intake unavailable') + completeSend('intake unavailable') exporter.close() await send @@ -145,7 +159,6 @@ test('agentless exporter reports sends after close without calling the binding', class BindingExporter { sendV04 () { sends++ - return Promise.resolve() } cancelAll () { @@ -230,6 +243,45 @@ test('agentless exporter retries in Rust until the third attempt succeeds', { } }) +test('agentless exporter drops over-budget requests without retrying', { + skip: !fs.existsSync(wasmArtifact), +}, async () => { + const pipeline = require('../wasm') + const transport = createHostTransport() + let requests = 0 + let resolveFirstRequest + const firstRequestReceived = new Promise((resolve) => { + resolveFirstRequest = resolve + }) + const server = http.createServer((incoming) => { + requests++ + incoming.resume() + resolveFirstRequest() + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + transport.request({ + id: 1, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: Buffer.alloc(16 * 1024 * 1024), + }, assert.fail) + const exporter = createExporter(pipeline, server) + try { + await firstRequestReceived + const log = testLog() + await sendExport(exporter, log) + assert.deepStrictEqual(log.errors, []) + assert.strictEqual(requests, 1) + } finally { + exporter.close() + transport.cancelRequest(1) + await new Promise(resolve => server.close(resolve)) + } +}) + test('agentless exporter applies Rust timeouts and retry policy', { skip: !fs.existsSync(wasmArtifact), }, async () => { @@ -256,6 +308,32 @@ test('agentless exporter applies Rust timeouts and retry policy', { } }) +test('agentless exporter close cancels a send started in the same turn', { + skip: !fs.existsSync(wasmArtifact), +}, async () => { + const pipeline = require('../wasm') + let requests = 0 + const server = http.createServer((incoming, response) => { + requests++ + incoming.resume() + incoming.once('end', () => response.end()) + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const exporter = createExporter(pipeline, server) + try { + const log = testLog() + const send = sendExport(exporter, log) + exporter.close() + await send + assert.strictEqual(requests, 0) + assert.deepStrictEqual(log.errors, []) + } finally { + exporter.close() + await new Promise(resolve => server.close(resolve)) + } +}) + test('agentless exporter close cancels an active HTTP request', { skip: !fs.existsSync(wasmArtifact), }, async () => { @@ -397,7 +475,7 @@ function exporterOptions () { /** * @typedef {object} TestBindingExporter - * @property {(payload: Uint8Array) => Promise} sendV04 + * @property {(payload: Uint8Array, done: BindingDone) => void} sendV04 * @property {() => void} cancelAll */ diff --git a/packages/libdatadog/test/transport.test.js b/packages/libdatadog/test/transport.test.js index 0018396f..803d6216 100644 --- a/packages/libdatadog/test/transport.test.js +++ b/packages/libdatadog/test/transport.test.js @@ -6,6 +6,26 @@ const { test } = require('node:test') const { createHostTransport } = require('../lib/agentless-transport') +/** @typedef {ReturnType} HostTransport */ +/** @typedef {Parameters[0]} RequestPlan */ + +/** + * @param {HostTransport} transport + * @param {RequestPlan} plan + */ +function sendRequest (transport, plan) { + return new Promise((resolve, reject) => { + const result = transport.request(plan, (error, response) => { + if (error) { + reject(error) + } else { + resolve(response) + } + }) + assert.strictEqual(result, undefined) + }) +} + test('host transport rejects when a response is aborted', async () => { const transport = createHostTransport() let resolveResponseClosed @@ -22,22 +42,15 @@ test('host transport rejects when a response is aborted', async () => { await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) try { const { port } = server.address() - const result = transport.request({ + const result = assert.rejects(sendRequest(transport, { id: 1, url: `http://127.0.0.1:${port}`, method: 'POST', headers: [], body: Buffer.alloc(0), - }).then( - () => ({ status: 'resolved' }), - error => ({ error, status: 'rejected' }), - ) - - await responseClosed - const outcome = await result + }), /response aborted|aborted/) - assert.strictEqual(outcome.status, 'rejected') - assert.match(outcome.error.message, /response aborted|aborted/) + await Promise.all([responseClosed, result]) } finally { await new Promise(resolve => server.close(resolve)) } @@ -45,6 +58,7 @@ test('host transport rejects when a response is aborted', async () => { test('host transport cancels an active request', async () => { const transport = createHostTransport() + let completed = 0 let resolveRequest const received = new Promise((resolve) => { resolveRequest = resolve @@ -57,16 +71,17 @@ test('host transport cancels an active request', async () => { await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) try { const { port } = server.address() - const request = transport.request({ + const result = transport.request({ id: 2, url: `http://127.0.0.1:${port}`, method: 'POST', headers: [], body: Buffer.alloc(0), - }) + }, () => completed++) + assert.strictEqual(result, undefined) await received transport.cancelRequest(2) - await assert.rejects(request, /request was cancelled/) + assert.strictEqual(completed, 0) } finally { await new Promise(resolve => server.close(resolve)) } @@ -82,7 +97,7 @@ test('host transport bounds active request buffers', async () => { /** * @param {import('node:http').IncomingMessage} request * @param {import('node:http').ServerResponse} response - */ + */ const server = http.createServer((request, response) => { requestCount++ if (requestCount === 1) { @@ -97,38 +112,42 @@ test('host transport bounds active request buffers', async () => { await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) const { port } = server.address() const atLimitBody = Buffer.alloc(16 * 1024 * 1024) + const overLimitBody = Buffer.alloc(atLimitBody.length + 1) const firstRequest = transport.request({ id: 4, url: `http://127.0.0.1:${port}`, method: 'POST', headers: [], body: atLimitBody, - }) - const firstRequestCanceled = assert.rejects(firstRequest, /request was cancelled/) + }, assert.fail) + assert.strictEqual(firstRequest, undefined) try { await firstRequestReceived - await assert.rejects(transport.request({ + const discarded = await sendRequest(transport, { id: 5, url: `http://127.0.0.1:${port}`, method: 'POST', headers: [], body: Buffer.alloc(1), - }), /Maximum active agentless request buffer size reached/) + }) + assert.strictEqual(discarded.status, 200) + assert.strictEqual(discarded.body.length, 0) transport.cancelRequest(4) - await firstRequestCanceled - const response = await transport.request({ + const oversized = await sendRequest(transport, { id: 6, url: `http://127.0.0.1:${port}`, method: 'POST', headers: [], - body: atLimitBody, + body: overLimitBody, }) - assert.strictEqual(response.status, 200) + assert.strictEqual(oversized.status, 200) + assert.strictEqual(oversized.body.length, 0) + assert.strictEqual(requestCount, 1) - const nextResponse = await transport.request({ + const nextResponse = await sendRequest(transport, { id: 7, url: `http://127.0.0.1:${port}`, method: 'POST', @@ -136,18 +155,20 @@ test('host transport bounds active request buffers', async () => { body: Buffer.alloc(1), }) assert.strictEqual(nextResponse.status, 200) + assert.strictEqual(requestCount, 2) } finally { transport.cancelRequest(4) - await firstRequestCanceled await new Promise(resolve => server.close(resolve)) } }) -test('host transport cancels a pending timer', async () => { +test('host transport cancels a pending timer', () => { const transport = createHostTransport() - const sleep = transport.sleep(3, 60_000) + let completed = 0 + const pendingSleep = transport.sleep(3, 60_000, () => completed++) + assert.strictEqual(pendingSleep, undefined) transport.cancelSleep(3) - await assert.rejects(sleep, /timer was cancelled/) + assert.strictEqual(completed, 0) }) From 9b97b661051cd5da43f6003b3eaa9b997bc4f476 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 Sep 2026 18:23:42 +0200 Subject: [PATCH 09/19] ci: reuse release proposal artifacts (#237) * ci: reuse release proposal artifacts Stable releases rebuild native and WASM artifacts after the proposal checks finish, so npm can receive binaries that CI did not test. Bind one candidate bundle to the merged Git tree and stop the release when that bundle is unavailable. Refs: https://github.com/DataDog/libdatadog-nodejs/pull/236 * ci: simplify release artifact lookup The release only needs proof that the proposal run tested the merged tree, because it downloads the immutable artifacts from that run. * ci: pin release candidate artifact The release workflow can start before the proposal artifacts exist. Reruns also retain the run ID, so separate name-based downloads can mix build attempts. A stable release must use one complete bundle that matches the tree and merged same-repository head. (cherry picked from commit 20ebef2da83031d051f7a6f73968f854a0f71cd8) --- .github/workflows/build.yml | 34 ++++++++++++++ .github/workflows/release.yml | 84 +++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f240bc04..a92f5be3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,3 +127,37 @@ jobs: uses: qard/heaviest-objects-in-the-universe@v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} + + release-candidate: + if: github.event_name == 'pull_request' && github.base_ref == 'v0.x' + needs: + - lint + - package-size + - test-libdatadog + - test-libdatadog-windows + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: prebuilds + path: prebuilds + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: libdatadog-wasm + path: packages/libdatadog/wasm/dist + - id: candidate + run: | + TREE=$(git rev-parse 'HEAD^{tree}') + echo "$TREE" > release-candidate + echo "artifact-name=release-candidate-$TREE" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ steps.candidate.outputs.artifact-name }} + path: | + prebuilds + packages/libdatadog/wasm/dist + release-candidate + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 979959f9..6b8d8f20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,7 @@ jobs: runs-on: ubuntu-latest outputs: npm-tag: ${{ steps.versions.outputs.npm-tag }} + tree: ${{ steps.versions.outputs.tree }} version: ${{ steps.versions.outputs.version }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -34,10 +35,68 @@ jobs: { echo "npm-tag=$NPM_TAG" + echo "tree=$(git rev-parse 'HEAD^{tree}')" echo "version=$VERSION" } >> "$GITHUB_OUTPUT" + resolve-release-candidate: + if: github.event_name == 'push' + needs: versions + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + pull-requests: read + outputs: + run-id: ${{ steps.candidate.outputs.run-id }} + steps: + - id: candidate + env: + ARTIFACT_NAME: release-candidate-${{ needs.versions.outputs.tree }} + GH_TOKEN: ${{ github.token }} + run: | + PULL=$( + gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" --jq ' + [ .[] | select( + .state == "closed" and + .merged_at != null and + .base.ref == "v0.x" + ) + ] + | if length == 1 then .[0] else error("expected one merged v0.x pull request") end + | [.merge_commit_sha, .head.sha, .head.repo.full_name] + | @tsv + ' + ) + IFS=$'\t' read -r MERGE_SHA HEAD_SHA HEAD_REPOSITORY <<< "$PULL" + + test "$MERGE_SHA" = "$GITHUB_SHA" + test "$HEAD_REPOSITORY" = "$GITHUB_REPOSITORY" + + for attempt in {1..90}; do + RUN_ID=$( + gh api "repos/$GITHUB_REPOSITORY/actions/artifacts?name=$ARTIFACT_NAME" --jq " + [ + .artifacts[] + | select( + .expired == false and + .workflow_run.head_sha == \"$HEAD_SHA\" + ) + ] + | max_by(.created_at).workflow_run.id // empty + " + ) + test -z "$RUN_ID" || break + if test "$attempt" -lt 90; then + sleep 10 + fi + done + test -n "$RUN_ID" + + echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT" + build-test-wasm: + if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest strategy: matrix: @@ -53,6 +112,7 @@ jobs: crate: '${{ matrix.crate }}' build-libdatadog-extras: + if: github.event_name == 'workflow_dispatch' uses: Datadog/action-prebuildify/.github/workflows/build.yml@main needs: build-test-wasm with: @@ -68,6 +128,7 @@ jobs: apt-get install -y autoconf automake libtool) || true build-libdatadog-wasm: + if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest needs: versions steps: @@ -87,6 +148,7 @@ jobs: path: packages/libdatadog/wasm/dist test-libdatadog-wasm: + if: github.event_name == 'workflow_dispatch' needs: build-libdatadog-wasm runs-on: ubuntu-latest strategy: @@ -107,6 +169,7 @@ jobs: test-libdatadog-windows: name: Test libdatadog WASM (Windows, Node ${{ matrix.node }}) + if: github.event_name == 'workflow_dispatch' needs: build-libdatadog-wasm runs-on: windows-latest strategy: @@ -127,10 +190,21 @@ jobs: - run: npm test --prefix packages/libdatadog publish: + if: >- + !cancelled() && + needs.versions.result == 'success' && + ((github.event_name == 'push' && + needs.resolve-release-candidate.result == 'success') || + (github.event_name == 'workflow_dispatch' && + needs.build-libdatadog-extras.result == 'success' && + needs.build-libdatadog-wasm.result == 'success' && + needs.test-libdatadog-wasm.result == 'success' && + needs.test-libdatadog-windows.result == 'success')) runs-on: ubuntu-latest needs: - build-libdatadog-extras - build-libdatadog-wasm + - resolve-release-candidate - test-libdatadog-wasm - test-libdatadog-windows - versions @@ -138,6 +212,7 @@ jobs: name: npm url: https://npmjs.com/package/@datadog/libdatadog permissions: + actions: read id-token: write # Required for OIDC contents: read steps: @@ -156,10 +231,19 @@ jobs: node-version: '24' registry-url: https://registry.npmjs.org - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: github.event_name == 'push' + with: + name: release-candidate-${{ needs.versions.outputs.tree }} + path: . + github-token: ${{ github.token }} + run-id: ${{ needs.resolve-release-candidate.outputs.run-id }} + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: github.event_name == 'workflow_dispatch' with: name: prebuilds path: prebuilds - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: github.event_name == 'workflow_dispatch' with: name: libdatadog-wasm path: packages/libdatadog/wasm/dist From bb43d2fc2283f5feb94ac7a19a4851e91ff34293 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 Sep 2026 19:14:02 +0200 Subject: [PATCH 10/19] chore(deps): upgrade napi-rs to v3 (#244) napi and napi-derive must use the same major. Updating either crate alone leaves generated bindings incompatible with the runtime API and breaks every native build. (cherry picked from commit 5998158f80f16ed294bebe9702621accd59e31b6) --- Cargo.lock | 53 +++++++++++-------- crates/crashtracker/Cargo.toml | 4 +- crates/crashtracker/src/lib.rs | 12 ++--- .../crashtracker/src/unhandled_exception.rs | 19 +++---- crates/process_discovery/Cargo.toml | 4 +- crates/process_discovery/src/lib.rs | 45 ++++++++++++++-- test/process-discovery.js | 9 ++++ 7 files changed, 102 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9741ae22..593c9d90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,9 +281,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.6.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" dependencies = [ "unicode-segmentation", ] @@ -370,13 +370,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.9" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.119", -] +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" [[package]] name = "darling" @@ -1686,9 +1682,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" dependencies = [ "cfg-if", "windows-link 0.2.1", @@ -1814,26 +1810,34 @@ dependencies = [ [[package]] name = "napi" -version = "2.16.17" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +checksum = "58c5f4d5375213fdb7be2655e152386e82f026f9a5ba36a75556e11359aafe09" dependencies = [ "bitflags 2.13.1", "ctor", - "napi-derive", + "futures", + "libc", + "napi-build", "napi-sys", - "once_cell", + "nohash-hasher", + "rustc-hash", "serde", "serde_json", ] +[[package]] +name = "napi-build" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60fdf9b392c50e7c4170fa633bd909490ed7835cea4c046776d1a4dd8d2ae0ab" + [[package]] name = "napi-derive" -version = "2.16.13" +version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +checksum = "0fa55ea69990c90b888e9e77044410e304ce7f35de599dc6d0b5c1923d2e59af" dependencies = [ - "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", @@ -1843,12 +1847,11 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.75" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +checksum = "df4056ac7c18e4438ccf0edaed4340ca0d269278c8ec19284f7b23cb039fd0ae" dependencies = [ "convert_case", - "once_cell", "proc-macro2", "quote", "syn 2.0.119", @@ -1856,9 +1859,9 @@ dependencies = [ [[package]] name = "napi-sys" -version = "2.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a" dependencies = [ "libloading", ] @@ -1888,6 +1891,12 @@ dependencies = [ "libc", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nu-ansi-term" version = "0.50.3" diff --git a/crates/crashtracker/Cargo.toml b/crates/crashtracker/Cargo.toml index 25c6a709..1044035c 100644 --- a/crates/crashtracker/Cargo.toml +++ b/crates/crashtracker/Cargo.toml @@ -15,7 +15,7 @@ path = "src/bin/receiver.rs" [dependencies] anyhow = "1" libdd-crashtracker = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } -napi = { version = "2", features = ["serde-json"] } -napi-derive = { version = "2", default-features = false } +napi = { version = "3", features = ["serde-json"] } +napi-derive = { version = "3", default-features = false } rustls = { version = "*", default-features = false, features = ["aws-lc-rs"] } serde_json = "1" diff --git a/crates/crashtracker/src/lib.rs b/crates/crashtracker/src/lib.rs index eb906969..b39dcbae 100644 --- a/crates/crashtracker/src/lib.rs +++ b/crates/crashtracker/src/lib.rs @@ -1,4 +1,4 @@ -use napi::{Env, JsUnknown}; +use napi::{Env, Unknown}; use napi_derive::napi; mod unhandled_exception; @@ -22,9 +22,9 @@ fn apply_default_signals( #[napi] pub fn init( env: Env, - config: JsUnknown, - receiver_config: JsUnknown, - metadata: JsUnknown, + config: Unknown, + receiver_config: Unknown, + metadata: Unknown, ) -> napi::Result<()> { let config: libdd_crashtracker::CrashtrackerConfiguration = env.from_js_value(config)?; let receiver_config = env.from_js_value(receiver_config)?; @@ -38,7 +38,7 @@ pub fn init( } #[napi] -pub fn update_config(env: Env, config: JsUnknown) -> napi::Result<()> { +pub fn update_config(env: Env, config: Unknown) -> napi::Result<()> { let config: libdd_crashtracker::CrashtrackerConfiguration = env.from_js_value(config)?; let config = apply_default_signals(config); @@ -49,7 +49,7 @@ pub fn update_config(env: Env, config: JsUnknown) -> napi::Result<()> { } #[napi] -pub fn update_metadata(env: Env, metadata: JsUnknown) -> napi::Result<()> { +pub fn update_metadata(env: Env, metadata: Unknown) -> napi::Result<()> { let metadata = env.from_js_value(metadata)?; libdd_crashtracker::update_metadata(metadata).unwrap(); diff --git a/crates/crashtracker/src/unhandled_exception.rs b/crates/crashtracker/src/unhandled_exception.rs index 64dd8fcb..461ed84b 100644 --- a/crates/crashtracker/src/unhandled_exception.rs +++ b/crates/crashtracker/src/unhandled_exception.rs @@ -1,8 +1,9 @@ -use napi::{Env, JsFunction, JsObject, JsUnknown}; +use napi::bindgen_prelude::{Function, JsObjectValue, Object}; +use napi::{Env, JsValue, Unknown}; use napi_derive::napi; -fn get_optional_string_property(obj: &JsObject, key: &str) -> napi::Result> { - match obj.get_named_property::(key) { +fn get_optional_string_property(obj: &Object, key: &str) -> napi::Result> { + match obj.get_named_property::(key) { Ok(val) => { use napi::ValueType; if val.get_type()? == ValueType::String { @@ -80,21 +81,21 @@ fn parse_location(location: &str, frame: &mut libdd_crashtracker::StackFrame) { } } -fn is_error_instance(env: &Env, value: &JsUnknown) -> napi::Result { +fn is_error_instance(env: &Env, value: &Unknown) -> napi::Result { let global = env.get_global()?; - let error_ctor: JsFunction = global.get_named_property("Error")?; + let error_ctor: Function<'_, (), Unknown<'_>> = global.get_named_property("Error")?; value.instanceof(error_ctor) } -fn stringify_js_value(value: JsUnknown) -> napi::Result { +fn stringify_js_value(value: Unknown) -> napi::Result { let s = value.coerce_to_string()?.into_utf8()?; Ok(s.as_str()?.to_owned()) } -fn report_unhandled(env: &Env, error: JsUnknown, fallback_type: &str) -> napi::Result<()> { +fn report_unhandled(env: &Env, error: Unknown, fallback_type: &str) -> napi::Result<()> { let is_error = is_error_instance(env, &error)?; let (exception_type, exception_message, stacktrace) = if is_error { - let error_obj: JsObject = error.coerce_to_object()?; + let error_obj: Object = error.coerce_to_object()?; let name = get_optional_string_property(&error_obj, "name")?; let message = get_optional_string_property(&error_obj, "message")?; let stack_string = get_optional_string_property(&error_obj, "stack")?; @@ -128,7 +129,7 @@ fn report_unhandled(env: &Env, error: JsUnknown, fallback_type: &str) -> napi::R #[napi] pub fn report_uncaught_exception_monitor( env: Env, - error: JsUnknown, + error: Unknown, origin: String, ) -> napi::Result<()> { report_unhandled(&env, error, &origin) diff --git a/crates/process_discovery/Cargo.toml b/crates/process_discovery/Cargo.toml index d46b257b..5501ef0b 100644 --- a/crates/process_discovery/Cargo.toml +++ b/crates/process_discovery/Cargo.toml @@ -11,5 +11,5 @@ anyhow = "1" libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0", features = ["otel-thread-ctx"] } libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } -napi = { version = "2" } -napi-derive = { version = "2", default-features = false } +napi = { version = "3" } +napi-derive = { version = "3", default-features = false } diff --git a/crates/process_discovery/src/lib.rs b/crates/process_discovery/src/lib.rs index 6e042e8f..707f683a 100644 --- a/crates/process_discovery/src/lib.rs +++ b/crates/process_discovery/src/lib.rs @@ -50,7 +50,7 @@ pub struct ThreadLocalMetadata { pub extra_attributes: Vec, } -#[napi(constructor)] +#[napi] pub struct TracerMetadata { pub runtime_id: Option, pub tracer_version: String, @@ -63,9 +63,48 @@ pub struct TracerMetadata { /// Optional thread-level context metadata; see [`ThreadLocalMetadata`]. /// `null`/omitted (the default) disables the `threadlocal.*` block in the /// emitted OTel process context entirely. + #[napi(skip)] pub threadlocal_metadata: Option, } +#[napi] +impl TracerMetadata { + #[napi(constructor)] + pub fn new( + runtime_id: Option, + tracer_version: String, + hostname: String, + service_name: Option, + service_env: Option, + service_version: Option, + process_tags: Option, + container_id: Option, + threadlocal_metadata: Option, + ) -> Self { + Self { + runtime_id, + tracer_version, + hostname, + service_name, + service_env, + service_version, + process_tags, + container_id, + threadlocal_metadata, + } + } + + #[napi(getter)] + pub fn threadlocal_metadata(&self) -> Option { + self.threadlocal_metadata.clone() + } + + #[napi(setter)] + pub fn set_threadlocal_metadata(&mut self, value: Option) { + self.threadlocal_metadata = value; + } +} + fn convert_extra_attribute(ea: &ExtraAttribute) -> napi::Result<(String, any_value::Value)> { let value = match (&ea.string_value, ea.int_value) { (Some(s), None) => any_value::Value::StringValue(s.clone()), @@ -108,7 +147,7 @@ fn convert_threadlocal_metadata( #[napi] pub fn store_metadata(data: &TracerMetadata) -> napi::Result { - let res = tracer_metadata::store_tracer_metadata(&tracer_metadata::TracerMetadata{ + let res = tracer_metadata::store_tracer_metadata(&tracer_metadata::TracerMetadata { schema_version: 1, runtime_id: data.runtime_id.clone(), tracer_language: String::from("nodejs"), @@ -127,7 +166,7 @@ pub fn store_metadata(data: &TracerMetadata) -> napi::Result Ok(NapiAnonymousFileHandle{ _internal: handle }), + Ok(handle) => Ok(NapiAnonymousFileHandle { _internal: handle }), Err(e) => { let err_msg = format!("Failed to store the tracer configuration: {:?}", e); Err(Error::new(Status::GenericFailure, err_msg)) diff --git a/test/process-discovery.js b/test/process-discovery.js index 9f202350..b7b52fce 100644 --- a/test/process-discovery.js +++ b/test/process-discovery.js @@ -57,6 +57,15 @@ assert.strictEqual( metadata_with_threadlocal.threadlocalMetadata.extraAttributes.length, 3, ) +metadata_with_threadlocal.threadlocalMetadata = { + attributeKeys: ['updated'], + schemaVersion: undefined, + extraAttributes: [], +} +assert.deepStrictEqual( + metadata_with_threadlocal.threadlocalMetadata.attributeKeys, + ['updated'], +) const cfg_handle_threadlocal = process_discovery.storeMetadata(metadata_with_threadlocal) assert(cfg_handle_threadlocal !== undefined) From 702a0e1cfb70df8b878ca14846b011d6e5cbd617 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 Sep 2026 21:09:41 +0200 Subject: [PATCH 11/19] fix(ci): resolve release artifacts by candidate tree (#246) Pull-request workflows can build from synthetic merge commits, so workflow_run.head_sha can differ from the PR source head. Filtering an already tree-qualified artifact by that SHA prevents stable releases from finding it. --- .github/workflows/release.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b8d8f20..74646044 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,11 +64,11 @@ jobs: ) ] | if length == 1 then .[0] else error("expected one merged v0.x pull request") end - | [.merge_commit_sha, .head.sha, .head.repo.full_name] + | [.merge_commit_sha, .head.repo.full_name] | @tsv ' ) - IFS=$'\t' read -r MERGE_SHA HEAD_SHA HEAD_REPOSITORY <<< "$PULL" + IFS=$'\t' read -r MERGE_SHA HEAD_REPOSITORY <<< "$PULL" test "$MERGE_SHA" = "$GITHUB_SHA" test "$HEAD_REPOSITORY" = "$GITHUB_REPOSITORY" @@ -78,10 +78,7 @@ jobs: gh api "repos/$GITHUB_REPOSITORY/actions/artifacts?name=$ARTIFACT_NAME" --jq " [ .artifacts[] - | select( - .expired == false and - .workflow_run.head_sha == \"$HEAD_SHA\" - ) + | select(.expired == false) ] | max_by(.created_at).workflow_run.id // empty " From 1b3f8be9adbd61d5a74c2a18e4e26e54c6ed26c0 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 3 Sep 2026 22:29:56 +0200 Subject: [PATCH 12/19] fix(crashtracker): preserve cross-context exception metadata (#257) Errors created in another V8 context fail the main context's instanceof Error check, so uncaught exception reports lose their structured type, message, and stack frames. The Node-API check recognizes these errors, while the existing instanceof fallback preserves transparent proxy behavior. --- .../crashtracker/src/unhandled_exception.rs | 4 +++ .../app-uncaught-exception-cross-context.js | 28 +++++++++++++++ .../app-uncaught-exception-proxy-error.js | 35 +++++++++++++++++++ test/crashtracker/index.js | 10 ++++++ 4 files changed, 77 insertions(+) create mode 100644 test/crashtracker/app-uncaught-exception-cross-context.js create mode 100644 test/crashtracker/app-uncaught-exception-proxy-error.js diff --git a/crates/crashtracker/src/unhandled_exception.rs b/crates/crashtracker/src/unhandled_exception.rs index 461ed84b..1a28e786 100644 --- a/crates/crashtracker/src/unhandled_exception.rs +++ b/crates/crashtracker/src/unhandled_exception.rs @@ -82,6 +82,10 @@ fn parse_location(location: &str, frame: &mut libdd_crashtracker::StackFrame) { } fn is_error_instance(env: &Env, value: &Unknown) -> napi::Result { + if value.is_error()? { + return Ok(true); + } + let global = env.get_global()?; let error_ctor: Function<'_, (), Unknown<'_>> = global.get_named_property("Error")?; value.instanceof(error_ctor) diff --git a/test/crashtracker/app-uncaught-exception-cross-context.js b/test/crashtracker/app-uncaught-exception-cross-context.js new file mode 100644 index 00000000..408040a7 --- /dev/null +++ b/test/crashtracker/app-uncaught-exception-cross-context.js @@ -0,0 +1,28 @@ +'use strict' + +const vm = require('node:vm') + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +/** + * @param {unknown} error + * @param {string} origin + */ +function reportUncaughtException (error, origin) { + crashtracker.reportUncaughtExceptionMonitor(error, origin) +} + +function customerVmHandler () { + vm.runInNewContext('throw new TypeError("cross-realm failure")') +} + +process.on('uncaughtExceptionMonitor', reportUncaughtException) + +customerVmHandler() diff --git a/test/crashtracker/app-uncaught-exception-proxy-error.js b/test/crashtracker/app-uncaught-exception-proxy-error.js new file mode 100644 index 00000000..00c4095f --- /dev/null +++ b/test/crashtracker/app-uncaught-exception-proxy-error.js @@ -0,0 +1,35 @@ +'use strict' + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +/** + * @param {unknown} error + * @param {string} origin + */ +function reportUncaughtException (error, origin) { + crashtracker.reportUncaughtExceptionMonitor(error, origin) +} + +/** + * @param {TypeError} target + * @param {string | symbol} property + */ +function getErrorProperty (target, property) { + return Reflect.get(target, property, target) +} + +function customerProxyHandler () { + const error = new TypeError('proxied failure') + throw new Proxy(error, { get: getErrorProperty }) +} + +process.on('uncaughtExceptionMonitor', reportUncaughtException) + +customerProxyHandler() diff --git a/test/crashtracker/index.js b/test/crashtracker/index.js index 9c6c2c00..a235a3e4 100644 --- a/test/crashtracker/index.js +++ b/test/crashtracker/index.js @@ -148,6 +148,16 @@ const server = app.listen(async () => { expectedMessage: 'something went wrong', expectedFrame: 'myFaultyFunction', }) + await testUnhandledError('uncaught-exception-cross-context', 'app-uncaught-exception-cross-context', { + expectedType: 'TypeError', + expectedMessage: 'cross-realm failure', + expectedFrame: 'customerVmHandler', + }) + await testUnhandledError('uncaught-exception-proxy-error', 'app-uncaught-exception-proxy-error', { + expectedType: 'TypeError', + expectedMessage: 'proxied failure', + expectedFrame: 'customerProxyHandler', + }) await testUnhandledNonError('uncaught-exception-non-error', 'app-uncaught-exception-non-error', { expectedFallbackType: 'uncaughtException', expectedValue: 'a plain string error', From 228ddb83e2a17931fa7b189aa7d98f1a3dc79c11 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 3 Sep 2026 22:30:56 +0200 Subject: [PATCH 13/19] chore: remove obsolete repository residue (#247) Root sketch and zstd checks became unreachable when their bindings moved to the universal package, which already tests both capabilities. The WASM matrix now uses the checksum-verified setup shared with the universal package. --- .github/actions/build-test-wasm/action.yaml | 6 +- Cargo.lock | 84 --------------------- LICENSE-3rdparty.csv | 7 -- crates/capabilities/Cargo.toml | 4 - crates/library_config/Cargo.toml | 4 - crates/pipeline/Cargo.toml | 3 - crates/process_discovery/Cargo.toml | 1 - crates/remote_config/Cargo.toml | 3 - eslint.config.js | 1 - global.js | 1 - test/wasm/datadog-js-zstd/index.js | 51 ------------- test/wasm/sketches/index.js | 29 ------- 12 files changed, 1 insertion(+), 193 deletions(-) delete mode 100644 global.js delete mode 100644 test/wasm/datadog-js-zstd/index.js delete mode 100644 test/wasm/sketches/index.js diff --git a/.github/actions/build-test-wasm/action.yaml b/.github/actions/build-test-wasm/action.yaml index fe006065..6c8f589a 100644 --- a/.github/actions/build-test-wasm/action.yaml +++ b/.github/actions/build-test-wasm/action.yaml @@ -7,15 +7,11 @@ inputs: runs: using: 'composite' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: ./.github/actions/setup-wasm - run: yarn install shell: bash - - name: Install wasm-pack - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - shell: bash - name: Build WASM run: | mkdir -p ./prebuilds/${{ inputs.crate }} diff --git a/Cargo.lock b/Cargo.lock index 593c9d90..b5bd01cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,12 +179,6 @@ dependencies = [ "crossbeam-channel", ] -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" version = "1.4.4" @@ -1211,13 +1205,11 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", - "futures-core", "http 1.5.0", "js-sys", "libdd-capabilities", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", ] [[package]] @@ -1690,21 +1682,13 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - [[package]] name = "library-config" version = "0.2.0" dependencies = [ - "anyhow", "getrandom 0.2.17", "libdd-library-config", "wasm-bindgen", - "wasm-bindgen-test", ] [[package]] @@ -1767,16 +1751,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "minicov" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aa3aa12b448ac225b3102217d1ac5cc717908f02722926524b0599c933c7a0" -dependencies = [ - "cc", - "walkdir", -] - [[package]] name = "miniz_oxide" version = "0.9.1" @@ -1897,15 +1871,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "num-conv" version = "0.2.2" @@ -1930,7 +1895,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2098,12 +2062,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - [[package]] name = "openssl-probe" version = "0.1.6" @@ -2192,7 +2150,6 @@ dependencies = [ "uuid", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", ] [[package]] @@ -2256,7 +2213,6 @@ dependencies = [ name = "process-discovery" version = "0.1.0" dependencies = [ - "anyhow", "libdd-library-config", "libdd-trace-protobuf", "napi", @@ -2400,7 +2356,6 @@ dependencies = [ "serde_json", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", ] [[package]] @@ -3285,45 +3240,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-bindgen-test" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "895a2607575412a4eda1df892084a375ea10dfeadc4d7d2ab87b854e4ddc7ba1" -dependencies = [ - "async-trait", - "cast", - "js-sys", - "libm", - "minicov", - "nu-ansi-term", - "num-traits", - "oorandom", - "serde", - "serde_json", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", - "wasm-bindgen-test-shared", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288cb0ebe215033bf949ae1fd046726daa4c32a157f24b9dc6ac387a52aa759" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "wasm-bindgen-test-shared" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ff1c1b360982e93b6d8ea9c04836f71dba0817a16f91e229cf3a51bdd9d987" - [[package]] name = "web-time" version = "1.1.0" diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 3e05281c..722cea6a 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -19,7 +19,6 @@ bs58,https://github.com/Nullus157/bs58-rs,MIT OR Apache-2.0,The bs58 Authors bumpalo,https://github.com/fitzgen/bumpalo,MIT OR Apache-2.0,Nick Fitzgerald bytes,https://github.com/tokio-rs/bytes,MIT,"Carl Lerche , Sean McArthur " cadence,https://github.com/56quarters/cadence,Apache-2.0 OR MIT,Nick Pillitteri -cast,https://github.com/japaric/cast.rs,MIT OR Apache-2.0,Jorge Aparicio cc,https://github.com/rust-lang/cc-rs,MIT OR Apache-2.0,Alex Crichton cesu8,https://github.com/emk/cesu8-rs,Apache-2.0 OR MIT,Eric Kidd cfg-if,https://github.com/rust-lang/cfg-if,MIT OR Apache-2.0,Alex Crichton @@ -135,7 +134,6 @@ libdd-trace-stats,https://github.com/DataDog/libdatadog/tree/main/libdd-trace-st libdd-trace-utils,https://github.com/DataDog/libdatadog/tree/main/libdd-trace-utils,Apache-2.0,The libdd-trace-utils Authors libdd-tuf,https://github.com/theupdateframework/rust-tuf,MIT OR Apache-2.0,"heartsucker , Erick Tryzelaar " libloading,https://github.com/nagisa/rust_libloading,ISC,Simonas Kazlauskas -libm,https://github.com/rust-lang/compiler-builtins,MIT,"Alex Crichton , Amanieu d'Antras , Jorge Aparicio , Trevor Gross " linux-raw-sys,https://github.com/sunfishcode/linux-raw-sys,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,Dan Gohman litemap,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers log,https://github.com/rust-lang/log,MIT OR Apache-2.0,The Rust Project Developers @@ -144,7 +142,6 @@ memchr,https://github.com/BurntSushi/memchr,Unlicense OR MIT,"Andrew Gallant , Simonas Kazlauskas " memmap2,https://github.com/RazrFalcon/memmap2-rs,MIT OR Apache-2.0,"Dan Burkert , Yevhenii Reizner , The Contributors" memoffset,https://github.com/Gilnaa/memoffset,MIT,Gilad Naaman -minicov,https://github.com/Amanieu/minicov,Apache-2.0 OR MIT,Amanieu d'Antras miniz_oxide,https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide,MIT OR Zlib OR Apache-2.0,"Frommi , oyvindln , Rich Geldreich richgel99@gmail.com" mio,https://github.com/tokio-rs/mio,MIT,"Carl Lerche , Thomas de Zeeuw , Tokio Contributors " msvc-demangler,https://github.com/mstange/msvc-demangler-rust,MIT OR NCSA,"Markus Stange , Jeff Muizelaar " @@ -153,7 +150,6 @@ napi-derive,https://github.com/napi-rs/napi-rs,MIT,"LongYinan nix,https://github.com/nix-rust/nix,MIT,The nix-rust Project Developers -nu-ansi-term,https://github.com/nushell/nu-ansi-term,MIT,"ogham@bsago.me, Ryan Scheel (Havvy) , Josh Triplett , The Nushell Project Developers" num-conv,https://github.com/jhpratt/num-conv,MIT OR Apache-2.0,Jacob Pratt num-derive,https://github.com/rust-num/num-derive,MIT OR Apache-2.0,The Rust Project Developers num-traits,https://github.com/rust-num/num-traits,MIT OR Apache-2.0,The Rust Project Developers @@ -172,7 +168,6 @@ objc2-quartz-core,https://github.com/madsmtm/objc2,Zlib OR Apache-2.0 OR MIT,The objc2-ui-kit,https://github.com/madsmtm/objc2,Zlib OR Apache-2.0 OR MIT,The objc2-ui-kit Authors objc2-user-notifications,https://github.com/madsmtm/objc2,Zlib OR Apache-2.0 OR MIT,The objc2-user-notifications Authors once_cell,https://github.com/matklad/once_cell,MIT OR Apache-2.0,Aleksey Kladov -oorandom,https://hg.sr.ht/~icefox/oorandom,MIT,Simon Heath openssl-probe,https://github.com/alexcrichton/openssl-probe,MIT OR Apache-2.0,Alex Crichton os_info,https://github.com/stanislav-tkach/os_info,MIT,"Jan Schulte , Stanislav Tkach " page_size,https://github.com/Elzair/page_size_rs,MIT OR Apache-2.0,Philip Woods @@ -286,8 +281,6 @@ wasm-bindgen-futures,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/cr wasm-bindgen-macro,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro,MIT OR Apache-2.0,The wasm-bindgen Developers wasm-bindgen-macro-support,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro-support,MIT OR Apache-2.0,The wasm-bindgen Developers wasm-bindgen-shared,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/shared,MIT OR Apache-2.0,The wasm-bindgen Developers -wasm-bindgen-test-macro,https://github.com/wasm-bindgen/wasm-bindgen,MIT OR Apache-2.0,The wasm-bindgen Developers -wasm-bindgen-test-shared,https://github.com/rustwasm/wasm-bindgen/tree/master/crates/test-shared,MIT OR Apache-2.0,The wasm-bindgen Developers wasm-encoder,https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-encoder,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,Nick Fitzgerald wasm-metadata,https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-metadata,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,The wasm-metadata Authors wasmparser,https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasmparser,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,Yury Delendik diff --git a/crates/capabilities/Cargo.toml b/crates/capabilities/Cargo.toml index 28999d39..57df8ee3 100644 --- a/crates/capabilities/Cargo.toml +++ b/crates/capabilities/Cargo.toml @@ -13,9 +13,5 @@ wasm-bindgen-futures = "0.4" js-sys = "0.3" http = "1" bytes = "1.4" -futures-core = "0.3" anyhow = "1" libdd-capabilities = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } - -[dev-dependencies] -wasm-bindgen-test = "0.3" diff --git a/crates/library_config/Cargo.toml b/crates/library_config/Cargo.toml index 81740f74..efd0a6a8 100644 --- a/crates/library_config/Cargo.toml +++ b/crates/library_config/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] -anyhow = "1" libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } wasm-bindgen = "0.2.100" @@ -15,8 +14,5 @@ wasm-bindgen = "0.2.100" [package.metadata.wasm-pack.profile.release] wasm-opt = ["-O", "--enable-bulk-memory"] -[dev-dependencies] -wasm-bindgen-test = "0.3.50" - [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/crates/pipeline/Cargo.toml b/crates/pipeline/Cargo.toml index d7deb862..4f388182 100644 --- a/crates/pipeline/Cargo.toml +++ b/crates/pipeline/Cargo.toml @@ -25,9 +25,6 @@ console_error_panic_hook = "0.1" getrandom = { version = "0.2", features = ["js"] } uuid = { version = "1", features = ["js"] } -[dev-dependencies] -wasm-bindgen-test = "0.3" - # The pipeline wasm uses post-MVP features (bulk-memory for change-buffer # copies, sign-extension, etc.) that the wasm-opt bundled with wasm-pack does # not enable by default. Pass --all-features so wasm-opt validation passes diff --git a/crates/process_discovery/Cargo.toml b/crates/process_discovery/Cargo.toml index 5501ef0b..f75fa61d 100644 --- a/crates/process_discovery/Cargo.toml +++ b/crates/process_discovery/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] -anyhow = "1" libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0", features = ["otel-thread-ctx"] } libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } diff --git a/crates/remote_config/Cargo.toml b/crates/remote_config/Cargo.toml index 649144fb..431bf52b 100644 --- a/crates/remote_config/Cargo.toml +++ b/crates/remote_config/Cargo.toml @@ -22,6 +22,3 @@ console_error_panic_hook = "0.1" [package.metadata.wasm-pack.profile.release] wasm-opt = ["-O", "--all-features"] - -[dev-dependencies] -wasm-bindgen-test = "0.3" diff --git a/eslint.config.js b/eslint.config.js index 05b8ed8d..3fa122db 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -49,7 +49,6 @@ module.exports = [ }], 'unicorn/name-replacements': 'off', // Naming churn 'unicorn/no-break-in-nested-loop': 'off', // Conflicts with performance-oriented loops - 'unicorn/no-global-object-property-assignment': 'off', // Needed for intentional global initialization 'unicorn/no-negated-array-predicate': 'off', // Predicate inversion is harder to read 'unicorn/no-return-array-push': 'off', // Questionable benefit 'unicorn/no-this-outside-of-class': 'off', // Object methods and callback APIs can bind `this` diff --git a/global.js b/global.js deleted file mode 100644 index 98b66d6f..00000000 --- a/global.js +++ /dev/null @@ -1 +0,0 @@ -globalThis.__dd_collector = require('.') diff --git a/test/wasm/datadog-js-zstd/index.js b/test/wasm/datadog-js-zstd/index.js deleted file mode 100644 index 734dfeb8..00000000 --- a/test/wasm/datadog-js-zstd/index.js +++ /dev/null @@ -1,51 +0,0 @@ -const assert = require('node:assert') - -const loader = require('../../../load') - -const zstd = loader.load('datadog-js-zstd') -assert(zstd !== undefined) - -// Create some compressible data -const SAMPLE_SIZE = 512 -const SAMPLE_COUNT = 1024 -const DATA_SIZE = SAMPLE_COUNT * 4 * SAMPLE_SIZE - -const samples = [] -for (let i = 0; i < SAMPLE_COUNT; i++) { - const sample = Array.from({ length: SAMPLE_SIZE }) - for (let j = 0; j < SAMPLE_SIZE; j++) { - sample[j] = Math.trunc(Math.random() * 256) - } - samples.push(sample) -} -const data = Array.from({ length: DATA_SIZE }) -for (let i = 0; i < DATA_SIZE; i += SAMPLE_SIZE) { - data.push(...samples[Math.trunc(Math.random() * SAMPLE_COUNT)]) -} -// Introduce some irregularities -for (let i = 0; i < SAMPLE_COUNT; i++) { - data[Math.trunc(Math.random() * DATA_SIZE)] = 0 -} -const dataArr = new Uint8Array(data) -const compressed3 = zstd.zstd_compress(dataArr, 3) -ensureCompressed(compressed3) - -// Test that 0 means default compression level -const compressed0 = zstd.zstd_compress(dataArr, 0) -ensureCompressed(compressed3) -assert(compressed0.length == compressed3.length) - -// Test that compression levels are correctly passed on. -// Level 18 should produce a smaller output than level 3. -// We can go all the way up to 22, but it is significantly slower. -const compressed18 = zstd.zstd_compress(dataArr, 18) -ensureCompressed(compressed18) -assert(compressed18.length < compressed3.length) - -function ensureCompressed (compressed) { - assert(compressed.length > 4) - assert.equal(compressed[0], 0x28) - assert.equal(compressed[1], 0xB5) - assert.equal(compressed[2], 0x2F) - assert.equal(compressed[3], 0xFD) -} diff --git a/test/wasm/sketches/index.js b/test/wasm/sketches/index.js deleted file mode 100644 index 3932318d..00000000 --- a/test/wasm/sketches/index.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -const assert = require('node:assert') - -const loader = require('../../../load') -const { DDSketch } = loader.load('sketches') - -const sketch = new DDSketch() -assert.strictEqual(sketch.count(), 0) - -sketch.add(1) -sketch.addWithCount(2, 3) -assert.strictEqual(sketch.count(), 4) - -assert.throws(() => sketch.add(-1), /point is invalid/) -assert.throws(() => sketch.addWithCount(1, NaN), /count is invalid/) - -const encoded = sketch.encode() -assert(encoded instanceof Uint8Array) -assert(encoded.length > 0) - -assert.strictEqual(sketch.count(), 4) -sketch.add(3) -assert.strictEqual(sketch.count(), 5) - -const reencoded = sketch.encode() -assert(reencoded instanceof Uint8Array) -assert(reencoded.length > 0) -assert.strictEqual(sketch.count(), 5) From 78023a8f3414512678d823705d5fb18b1d6322b6 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 3 Sep 2026 22:34:55 +0200 Subject: [PATCH 14/19] refactor(process-discovery): accept metadata objects (#248) `dd-trace` still calls the positional `TracerMetadata` constructor, so a small factory preserves that contract while `storeMetadata` moves to plain objects. --- crates/process_discovery/src/lib.rs | 126 +++++++++++++--------------- test/process-discovery.js | 88 ++++++++----------- 2 files changed, 95 insertions(+), 119 deletions(-) diff --git a/crates/process_discovery/src/lib.rs b/crates/process_discovery/src/lib.rs index 707f683a..a9509ef3 100644 --- a/crates/process_discovery/src/lib.rs +++ b/crates/process_discovery/src/lib.rs @@ -17,7 +17,6 @@ impl NapiAnonymousFileHandle {} /// exactly one of `string_value` / `int_value` — the other variants of OTel's /// `AnyValue` (bool, double, bytes, array, kvlist) are not yet exposed. /// Passing both set or neither set is rejected as invalid input. -#[derive(Clone)] #[napi(object)] pub struct ExtraAttribute { pub key: String, @@ -29,7 +28,6 @@ pub struct ExtraAttribute { /// OTel process context. When present on a [`TracerMetadata`], drives the /// `threadlocal.*` block in the emitted process context; when absent, no such /// block is emitted. -#[derive(Clone)] #[napi(object)] pub struct ThreadLocalMetadata { /// Ordered list of attribute key names for thread-level OTEP-4947 context @@ -50,7 +48,7 @@ pub struct ThreadLocalMetadata { pub extra_attributes: Vec, } -#[napi] +#[napi(object)] pub struct TracerMetadata { pub runtime_id: Option, pub tracer_version: String, @@ -61,60 +59,53 @@ pub struct TracerMetadata { pub process_tags: Option, pub container_id: Option, /// Optional thread-level context metadata; see [`ThreadLocalMetadata`]. - /// `null`/omitted (the default) disables the `threadlocal.*` block in the - /// emitted OTel process context entirely. - #[napi(skip)] + /// Omitted or `undefined` disables the `threadlocal.*` block in the emitted + /// OTel process context entirely. pub threadlocal_metadata: Option, } -#[napi] -impl TracerMetadata { - #[napi(constructor)] - pub fn new( - runtime_id: Option, - tracer_version: String, - hostname: String, - service_name: Option, - service_env: Option, - service_version: Option, - process_tags: Option, - container_id: Option, - threadlocal_metadata: Option, - ) -> Self { - Self { - runtime_id, - tracer_version, - hostname, - service_name, - service_env, - service_version, - process_tags, - container_id, - threadlocal_metadata, - } - } - - #[napi(getter)] - pub fn threadlocal_metadata(&self) -> Option { - self.threadlocal_metadata.clone() - } - - #[napi(setter)] - pub fn set_threadlocal_metadata(&mut self, value: Option) { - self.threadlocal_metadata = value; +/// Builds a metadata object for callers that use the positional constructor. +#[allow(clippy::too_many_arguments)] +#[napi(js_name = "TracerMetadata")] +pub fn legacy_tracer_metadata( + runtime_id: Option, + tracer_version: String, + hostname: String, + service_name: Option, + service_env: Option, + service_version: Option, + process_tags: Option, + container_id: Option, + threadlocal_metadata: Option, +) -> TracerMetadata { + TracerMetadata { + runtime_id, + tracer_version, + hostname, + service_name, + service_env, + service_version, + process_tags, + container_id, + threadlocal_metadata, } } -fn convert_extra_attribute(ea: &ExtraAttribute) -> napi::Result<(String, any_value::Value)> { - let value = match (&ea.string_value, ea.int_value) { - (Some(s), None) => any_value::Value::StringValue(s.clone()), - (None, Some(i)) => any_value::Value::IntValue(i), +fn convert_extra_attribute(attribute: ExtraAttribute) -> napi::Result<(String, any_value::Value)> { + let ExtraAttribute { + key, + string_value, + int_value, + } = attribute; + let value = match (string_value, int_value) { + (Some(value), None) => any_value::Value::StringValue(value), + (None, Some(value)) => any_value::Value::IntValue(value), (Some(_), Some(_)) => { return Err(Error::new( Status::InvalidArg, format!( "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, both are", - ea.key, + key, ), )); } @@ -123,53 +114,52 @@ fn convert_extra_attribute(ea: &ExtraAttribute) -> napi::Result<(String, any_val Status::InvalidArg, format!( "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, neither is", - ea.key, + key, ), )); } }; - Ok((ea.key.clone(), value)) + Ok((key, value)) } fn convert_threadlocal_metadata( - tlm: &ThreadLocalMetadata, + metadata: ThreadLocalMetadata, ) -> napi::Result { Ok(tracer_metadata::ThreadLocalMetadata { - attribute_keys: tlm.attribute_keys.clone(), - schema_version: tlm.schema_version.clone(), - extra_attributes: tlm + attribute_keys: metadata.attribute_keys, + schema_version: metadata.schema_version, + extra_attributes: metadata .extra_attributes - .iter() + .into_iter() .map(convert_extra_attribute) .collect::>()?, }) } #[napi] -pub fn store_metadata(data: &TracerMetadata) -> napi::Result { - let res = tracer_metadata::store_tracer_metadata(&tracer_metadata::TracerMetadata { +pub fn store_metadata(data: TracerMetadata) -> napi::Result { + let result = tracer_metadata::store_tracer_metadata(&tracer_metadata::TracerMetadata { schema_version: 1, - runtime_id: data.runtime_id.clone(), + runtime_id: data.runtime_id, tracer_language: String::from("nodejs"), - tracer_version: data.tracer_version.clone(), - hostname: data.hostname.clone(), - service_name: data.service_name.clone(), - service_env: data.service_env.clone(), - service_version: data.service_version.clone(), - process_tags: data.process_tags.clone(), - container_id: data.container_id.clone(), + tracer_version: data.tracer_version, + hostname: data.hostname, + service_name: data.service_name, + service_env: data.service_env, + service_version: data.service_version, + process_tags: data.process_tags, + container_id: data.container_id, threadlocal_metadata: data .threadlocal_metadata - .as_ref() .map(convert_threadlocal_metadata) .transpose()?, }); - match res { + match result { Ok(handle) => Ok(NapiAnonymousFileHandle { _internal: handle }), - Err(e) => { - let err_msg = format!("Failed to store the tracer configuration: {:?}", e); - Err(Error::new(Status::GenericFailure, err_msg)) + Err(error) => { + let error_message = format!("Failed to store the tracer configuration: {:?}", error); + Err(Error::new(Status::GenericFailure, error_message)) } } } diff --git a/test/process-discovery.js b/test/process-discovery.js index b7b52fce..69b0e4c9 100644 --- a/test/process-discovery.js +++ b/test/process-discovery.js @@ -8,7 +8,21 @@ const libdatadog = require('..') const process_discovery = libdatadog.load('process-discovery') assert(process_discovery !== undefined) -const metadata = new process_discovery.TracerMetadata( +const metadata = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f897', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + serviceName: 'my_svc', + serviceEnv: 'my_env', + serviceVersion: 'my_version', + processTags: 'entrypoint.name:server,svc.auto:my_svc', + containerId: 'abc123def456abc123def456abc123def456abc123def456abc123def456abc123', +} + +const cfg_handle = process_discovery.storeMetadata(metadata) +assert(cfg_handle !== undefined) + +const positional_metadata = new process_discovery.TracerMetadata( '7938685c-19dd-490f-b9b3-8aae4c22f897', '1.0.0', 'my_hostname', @@ -18,24 +32,21 @@ const metadata = new process_discovery.TracerMetadata( 'entrypoint.name:server,svc.auto:my_svc', 'abc123def456abc123def456abc123def456abc123def456abc123def456abc123', ) - -const cfg_handle = process_discovery.storeMetadata(metadata) -assert(cfg_handle !== undefined) +const positional_cfg_handle = process_discovery.storeMetadata(positional_metadata) +assert(positional_cfg_handle !== undefined) // Same shape, plus a thread-local metadata block (OTEP-4947). libdatadog // implicitly prepends `datadog.local_root_span_id` at wire index 0 in the // attribute key map; entries here start at wire index 1. `schemaVersion` and // `extraAttributes` describe the on-the-wire record schema for readers. -const metadata_with_threadlocal = new process_discovery.TracerMetadata( - '7938685c-19dd-490f-b9b3-8aae4c22f898', - '1.0.0', - 'my_hostname', - 'my_svc', - 'my_env', - 'my_version', - undefined, - undefined, - { +const metadata_with_threadlocal = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f898', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + serviceName: 'my_svc', + serviceEnv: 'my_env', + serviceVersion: 'my_version', + threadlocalMetadata: { attributeKeys: ['endpoint', 'http.status'], schemaVersion: 'nodejs_v1_dev', extraAttributes: [ @@ -44,44 +55,21 @@ const metadata_with_threadlocal = new process_discovery.TracerMetadata( { key: 'threadlocal.runtime.name', stringValue: 'nodejs' }, ], }, -) -assert.deepStrictEqual( - metadata_with_threadlocal.threadlocalMetadata.attributeKeys, - ['endpoint', 'http.status'], -) -assert.strictEqual( - metadata_with_threadlocal.threadlocalMetadata.schemaVersion, - 'nodejs_v1_dev', -) -assert.strictEqual( - metadata_with_threadlocal.threadlocalMetadata.extraAttributes.length, - 3, -) -metadata_with_threadlocal.threadlocalMetadata = { - attributeKeys: ['updated'], - schemaVersion: undefined, - extraAttributes: [], } -assert.deepStrictEqual( - metadata_with_threadlocal.threadlocalMetadata.attributeKeys, - ['updated'], -) const cfg_handle_threadlocal = process_discovery.storeMetadata(metadata_with_threadlocal) assert(cfg_handle_threadlocal !== undefined) // An ExtraAttribute with neither stringValue nor intValue set is a caller // error — one of them has to be picked. -const bad_metadata_neither = new process_discovery.TracerMetadata( - '7938685c-19dd-490f-b9b3-8aae4c22f899', - '1.0.0', - 'my_hostname', - undefined, undefined, undefined, undefined, undefined, - { +const bad_metadata_neither = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f899', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + threadlocalMetadata: { attributeKeys: [], - schemaVersion: undefined, extraAttributes: [{ key: 'threadlocal.bogus' }], }, -) +} assert.throws( () => process_discovery.storeMetadata(bad_metadata_neither), /neither is/, @@ -89,17 +77,15 @@ assert.throws( // Setting both stringValue and intValue is also a caller error — the intent // is ambiguous, so reject. -const bad_metadata_both = new process_discovery.TracerMetadata( - '7938685c-19dd-490f-b9b3-8aae4c22f89a', - '1.0.0', - 'my_hostname', - undefined, undefined, undefined, undefined, undefined, - { +const bad_metadata_both = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f89a', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + threadlocalMetadata: { attributeKeys: [], - schemaVersion: undefined, extraAttributes: [{ key: 'threadlocal.bogus', stringValue: 's', intValue: 1 }], }, -) +} assert.throws( () => process_discovery.storeMetadata(bad_metadata_both), /both are/, From cb0ffc85c41d70262b7dd69343a0c39f3a45611f Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 3 Sep 2026 22:36:33 +0200 Subject: [PATCH 15/19] bench(agentless): add end-to-end pipeline workloads (#250) Build the measured WASM artifact first and stop timing at the generated request callback so transport latency cannot hide transformation and compression costs. --- eslint.config.js | 1 + .../benchmark/agentless-pipeline.js | 355 ++++++++++++++++++ packages/libdatadog/package.json | 1 + 3 files changed, 357 insertions(+) create mode 100644 packages/libdatadog/benchmark/agentless-pipeline.js diff --git a/eslint.config.js b/eslint.config.js index 3fa122db..890ee8ba 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -113,6 +113,7 @@ module.exports = [ // These imports are either installed by the package test workflow or // generated during its build; root lint cannot resolve them. files: [ + 'packages/libdatadog/benchmark/agentless-pipeline.js', 'packages/libdatadog/lib/wasm.js', 'packages/libdatadog/remote-config.js', 'packages/libdatadog/test/bundlers.test.js', diff --git a/packages/libdatadog/benchmark/agentless-pipeline.js b/packages/libdatadog/benchmark/agentless-pipeline.js new file mode 100644 index 00000000..ddfc39fa --- /dev/null +++ b/packages/libdatadog/benchmark/agentless-pipeline.js @@ -0,0 +1,355 @@ +'use strict' + +const assert = require('node:assert/strict') + +const { encode } = require('@msgpack/msgpack') +const binding = require('@datadog/libdatadog-wasm') + +const emptyBody = Buffer.alloc(0) +const zstdMagic = Buffer.from([0x28, 0xB5, 0x2F, 0xFD]) +const richMetaStruct = encode({ attempt: 1, feature: 'checkout' }) +const selectedWorkload = process.argv[2] + +/** @typedef {'http' | 'mixed-repeated-sql' | 'mixed-unique-sql' | 'rich'} WorkloadShape */ +/** + * @typedef {object} WorkloadOptions + * @property {string} name + * @property {number} traceCount + * @property {number} spansPerTrace + * @property {WorkloadShape} shape + * @property {number} iterations + */ +/** + * @typedef {object} Workload + * @property {string} name + * @property {number} traceCount + * @property {number} spansPerTrace + * @property {number} iterations + * @property {Uint8Array} payload + */ +/** @typedef {{ name: string, value: string }} Header */ +/** @typedef {{ method: string, url: string, headers: Header[], body: Uint8Array }} RequestPlan */ +/** @typedef {{ status: number, body: Uint8Array }} BindingResponse */ +/** @typedef {{ elapsedNanoseconds: number, outputBytes: number, requests: number, plan?: RequestPlan }} Sample */ + +const exporterOptions = { + endpoint: 'https://example.test/v1/input', + apiKey: 'test-api-key', + hostname: 'benchmark-host', + env: 'benchmark', + service: 'web', + version: '1.0.0', + runtimeId: 'benchmark-runtime', + containerId: 'benchmark-container', + tracerVersion: '0.1.0', + languageVersion: process.version, + languageInterpreter: 'v8', +} + +const workloadOptions = [ + { name: 'tiny', traceCount: 1, spansPerTrace: 1, shape: 'http', iterations: 5000 }, + { name: 'common-http', traceCount: 100, spansPerTrace: 3, shape: 'http', iterations: 300 }, + { + name: 'mixed-repeated-sql', + traceCount: 100, + spansPerTrace: 3, + shape: 'mixed-repeated-sql', + iterations: 200, + }, + { + name: 'mixed-unique-sql', + traceCount: 100, + spansPerTrace: 3, + shape: 'mixed-unique-sql', + iterations: 200, + }, + { name: 'rich', traceCount: 25, spansPerTrace: 8, shape: 'rich', iterations: 200 }, + { name: 'large', traceCount: 1000, spansPerTrace: 5, shape: 'mixed-repeated-sql', iterations: 15 }, +] + +/** @param {WorkloadOptions} options */ +function createWorkload (options) { + const traces = Array.from({ length: options.traceCount }) + for (let traceIndex = 0; traceIndex < traces.length; traceIndex++) { + const spans = Array.from({ length: options.spansPerTrace }) + for (let spanIndex = 0; spanIndex < spans.length; spanIndex++) { + spans[spanIndex] = createSpan(traceIndex, spanIndex, options.spansPerTrace, options.shape) + } + traces[traceIndex] = spans + } + + return { + name: options.name, + traceCount: options.traceCount, + spansPerTrace: options.spansPerTrace, + iterations: options.iterations, + payload: encode(traces, { useBigInt64: true }), + } +} + +/** + * @param {number} traceIndex + * @param {number} spanIndex + * @param {number} spansPerTrace + * @param {WorkloadShape} shape + */ +function createSpan (traceIndex, spanIndex, spansPerTrace, shape) { + const isDatabaseSpan = shape !== 'http' && spanIndex !== 0 + const queryId = shape === 'mixed-unique-sql' + ? traceIndex * spansPerTrace + spanIndex + : 7 + const name = isDatabaseSpan ? 'postgres.query' : 'http.request' + const resource = isDatabaseSpan + ? `SELECT id, email FROM users WHERE tenant_id = 42 AND id = ${queryId}` + : `GET /users/${traceIndex % 100}` + const start = 1_800_000_000_000_000_000n + BigInt(traceIndex * 1_000_000 + spanIndex * 1000) + const meta = { + 'component': isDatabaseSpan ? 'pg' : 'http', + 'env': 'benchmark', + 'span.kind': spanIndex === 0 ? 'server' : 'client', + 'version': '1.0.0', + } + if (isDatabaseSpan) { + meta['db.system'] = 'postgresql' + meta['db.user'] = 'benchmark-user' + } else { + meta['http.method'] = 'GET' + meta['http.status_code'] = '200' + meta['http.url'] = `https://example.test/users/${traceIndex}?token=secret` + } + + const span = { + service: isDatabaseSpan ? 'postgres' : 'web', + name, + resource, + trace_id: BigInt(traceIndex + 1), + span_id: BigInt(traceIndex * 16 + spanIndex + 1), + parent_id: spanIndex === 0 ? 0n : BigInt(traceIndex * 16 + spanIndex), + start, + duration: 100_000 + spanIndex * 1000, + error: shape === 'rich' && spanIndex % 7 === 0 ? 1 : 0, + meta, + metrics: { + '_dd.measured': 1, + '_sampling_priority_v1': 1, + }, + type: isDatabaseSpan ? 'sql' : 'web', + } + + if (shape === 'rich') { + for (let tagIndex = 0; tagIndex < 8; tagIndex++) { + meta[`benchmark.tag.${tagIndex}`] = `value-${traceIndex}-${spanIndex}-${tagIndex}` + } + span.meta_struct = { 'benchmark.context': richMetaStruct } + if (spanIndex % 4 === 0) { + span.span_events = [{ + name: 'exception', + time_unix_nano: start + 500n, + attributes: { + 'exception.count': { type: 2, int_value: 1n }, + 'exception.escaped': { type: 1, bool_value: false }, + 'exception.message': { type: 0, string_value: 'request timed out' }, + }, + }] + span.span_links = [{ + trace_id: BigInt(traceIndex + 10_001), + trace_id_high: 0x12_34n, + span_id: BigInt(spanIndex + 20_001), + attributes: { 'link.name': 'scheduled_by' }, + flags: 1, + tracestate: 'dd=s:1', + }] + } + } + + return span +} + +/** + * @param {Uint8Array} payload + * @param {number} iterations + * @param {boolean} capturePlan + * @returns {Promise} + */ +function runIterations (payload, iterations, capturePlan) { + let rejectRun + let resolveRun + let requests = 0 + let outputBytes = 0 + let plan + /** + * @param {(sample: Sample) => void} resolve + * @param {(error: Error) => void} reject + */ + const completed = new Promise((resolve, reject) => { + resolveRun = resolve + rejectRun = reject + }) + + /** + * @param {RequestPlan} requestPlan + * @param {(error: undefined, response: BindingResponse) => void} done + */ + const request = (requestPlan, done) => { + requests++ + outputBytes = requestPlan.body.byteLength + if (capturePlan && plan === undefined) { + plan = { + ...requestPlan, + body: Buffer.from(requestPlan.body), + } + } + done(undefined, { status: 202, body: emptyBody }) + } + + let operations = 0 + + /** @param {unknown} error */ + const complete = (error) => { + if (error !== undefined) { + exporter.free() + rejectRun(new Error(String(error))) + return + } + + operations++ + if (operations < iterations) { + exporter.sendV04(payload, complete) + return + } + + const elapsedNanoseconds = Number(process.hrtime.bigint() - start) + exporter.free() + resolveRun({ elapsedNanoseconds, outputBytes, requests, plan }) + } + + const exporter = new binding.AgentlessExporter( + exporterOptions, + request, + cancelRequest, + completeSleep, + cancelSleep, + ) + const start = process.hrtime.bigint() + exporter.sendV04(payload, complete) + return completed +} + +function cancelRequest () {} + +/** + * @param {number} id + * @param {number} milliseconds + * @param {() => void} done + */ +function completeSleep (id, milliseconds, done) { + assert(Number.isInteger(id)) + assert(Number.isInteger(milliseconds)) + done() +} + +function cancelSleep () {} + +/** @param {number} left @param {number} right */ +function compareNumbers (left, right) { + return left - right +} + +/** @param {number[]} samples */ +function trimmedMean (samples) { + const sorted = [...samples] + sorted.sort(compareNumbers) + let sum = 0 + for (let index = 1; index < sorted.length - 1; index++) { + sum += sorted[index] + } + return sum / (sorted.length - 2) +} + +/** @param {Workload} workload */ +async function validateWorkload (workload) { + const sample = await runIterations(workload.payload, 1, true) + const { plan } = sample + assert(plan) + assert.strictEqual(sample.requests, 1) + assert.strictEqual(plan.method, 'POST') + assert.strictEqual(plan.url, exporterOptions.endpoint) + assert.strictEqual(findHeader(plan.headers, 'content-encoding'), 'zstd') + assert.strictEqual(findHeader(plan.headers, 'content-type'), 'application/json') + assert.strictEqual(findHeader(plan.headers, 'dd-api-key'), exporterOptions.apiKey) + assert.deepStrictEqual(plan.body.subarray(0, zstdMagic.length), zstdMagic) +} + +/** @param {Header[]} headers @param {string} name */ +function findHeader (headers, name) { + for (const header of headers) { + if (header.name === name) return header.value + } +} + +/** @param {Workload} workload */ +async function benchmarkWorkload (workload) { + await validateWorkload(workload) + const warmupBatchIterations = Math.max(workload.iterations, 50) + let warmupElapsedNanoseconds = 0 + let warmupIterations = 0 + do { + const sample = await runIterations(workload.payload, warmupBatchIterations, false) + warmupElapsedNanoseconds += sample.elapsedNanoseconds + warmupIterations += warmupBatchIterations + } while (warmupElapsedNanoseconds < 1e9) + + const samples = Array.from({ length: 7 }) + let outputBytes = 0 + for (let trial = 0; trial < samples.length; trial++) { + const sample = await runIterations(workload.payload, workload.iterations, false) + assert.strictEqual(sample.requests, workload.iterations) + samples[trial] = sample.elapsedNanoseconds / workload.iterations + outputBytes = sample.outputBytes + } + + const mean = trimmedMean(samples) + return { + name: workload.name, + traceCount: workload.traceCount, + spansPerTrace: workload.spansPerTrace, + inputBytes: workload.payload.byteLength, + outputBytes, + warmupIterations, + iterationsPerTrial: workload.iterations, + samplesNanosecondsPerOperation: samples, + trimmedMeanNanosecondsPerOperation: mean, + operationsPerSecond: 1e9 / mean, + } +} + +function selectedWorkloads () { + const workloads = [] + for (const options of workloadOptions) { + if (selectedWorkload === undefined || selectedWorkload === options.name) { + workloads.push(createWorkload(options)) + } + } + if (workloads.length === 0) { + throw new Error(`unknown workload: ${selectedWorkload}`) + } + return workloads +} + +async function main () { + const results = [] + for (const workload of selectedWorkloads()) { + results.push(await benchmarkWorkload(workload)) + } + console.log(JSON.stringify({ + benchmark: 'agentless-pipeline', + node: process.version, + v8: process.versions.v8, + trials: 7, + results, + })) +} + +// CommonJS does not support top-level await. +// eslint-disable-next-line unicorn/prefer-top-level-await +main() diff --git a/packages/libdatadog/package.json b/packages/libdatadog/package.json index 83bbdde3..270e5711 100644 --- a/packages/libdatadog/package.json +++ b/packages/libdatadog/package.json @@ -53,6 +53,7 @@ "node": ">=18" }, "scripts": { + "benchmark:agentless": "npm run build:wasm:binary && npm run inline:wasm && node benchmark/agentless-pipeline.js", "build:wasm": "npm run build:wasm:binary && npm run build:remote-config:binary && npm run inline:wasm && npm run inline:remote-config", "build:wasm:binary": "node ../../scripts/build-wasm.js ../../crates/libdatadog-wasm wasm/dist", "build:remote-config:binary": "node ../../scripts/build-wasm.js ../../crates/remote_config wasm/dist/remote-config", From 4964901ed782da687ae628263e1b1dd7a88551ab Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 3 Sep 2026 22:38:02 +0200 Subject: [PATCH 16/19] perf(wasm): rebuild std with immediate-abort panics (#255) Setting panic=abort does not remove formatting and unwinding paths already compiled into the standard library. Pin the WASM-only nightly toolchain and rebuild std with immediate-abort panics while leaving native builds on stable Rust. With the same optimizer and source revision, the main artifact shrinks from 443,295 to 372,236 bytes (16.0%). The measurement reproduced across two clean builds. --- .github/actions/setup-wasm/action.yaml | 13 +++++++++---- scripts/build-wasm.js | 24 +++++++++++++++++++++--- wasm-rust-toolchain | 1 + 3 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 wasm-rust-toolchain diff --git a/.github/actions/setup-wasm/action.yaml b/.github/actions/setup-wasm/action.yaml index f70dc23a..59e42031 100644 --- a/.github/actions/setup-wasm/action.yaml +++ b/.github/actions/setup-wasm/action.yaml @@ -3,8 +3,12 @@ description: Restore the Cargo cache and install the pinned WASM toolchain runs: using: composite steps: - - name: Add WASM target - run: rustup target add wasm32-unknown-unknown + - name: Install WASM Rust toolchain + run: | + rustup toolchain install "$(cat wasm-rust-toolchain)" \ + --profile minimal \ + --component rust-src \ + --target wasm32-unknown-unknown shell: bash - name: Restore Cargo build cache uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -16,11 +20,12 @@ runs: ~/.cargo/registry/index target key: >- - wasm-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml') }}-${{ + wasm-${{ runner.os }}-${{ + hashFiles('Cargo.lock', '*rust-toolchain*', '.cargo/config.toml', 'scripts/build-wasm.js') }}-${{ hashFiles('Cargo.toml', 'crates/**/Cargo.toml', 'crates/**/*.rs') }} restore-keys: >- wasm-${{ runner.os }}-${{ - hashFiles('Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml') }}- + hashFiles('Cargo.lock', '*rust-toolchain*', '.cargo/config.toml', 'scripts/build-wasm.js') }}- - name: Install wasm-pack env: WASM_PACK_SHA256: 278a8d668085821f4d1a637bd864f1713f872b0ae3a118c77562a308c0abfe8d diff --git a/scripts/build-wasm.js b/scripts/build-wasm.js index a6717f93..62ef1a02 100644 --- a/scripts/build-wasm.js +++ b/scripts/build-wasm.js @@ -16,6 +16,10 @@ const path = require('node:path') const childProcess = require('node:child_process') const isMacOS = os.platform() === 'darwin' +const wasmRustToolchain = fs.readFileSync( + path.join(__dirname, '..', 'wasm-rust-toolchain'), + 'utf8', +).trim() const libraries = [ 'library_config', 'pipeline', @@ -25,6 +29,20 @@ const libraries = [ const env = { ...process.env, } +const pathKey = Object.keys(env).find(key => key.toUpperCase() === 'PATH') ?? 'PATH' +const rustFlagsKey = Object.keys(env).find(key => key.toUpperCase() === 'RUSTFLAGS') ?? 'RUSTFLAGS' +const rustupToolchainKey = Object.keys(env).find(key => key.toUpperCase() === 'RUSTUP_TOOLCHAIN') ?? 'RUSTUP_TOOLCHAIN' +const cargoPath = childProcess.execFileSync( + 'rustup', + ['which', 'cargo', '--toolchain', wasmRustToolchain], + { encoding: 'utf8' }, +).trim() + +env[pathKey] = `${path.dirname(cargoPath)}${path.delimiter}${env[pathKey]}` +env[rustFlagsKey] = [env[rustFlagsKey], '-Zunstable-options', '-Cpanic=immediate-abort'] + .filter(Boolean) + .join(' ') +env[rustupToolchainKey] = wasmRustToolchain if (isMacOS) { const homebrewDir = env.HOMEBREW_DIR ?? '/opt/homebrew' @@ -42,9 +60,9 @@ if (isMacOS) { process.exit(1) // eslint-disable-line unicorn/no-process-exit } - if (!env.PATH.includes(llvmBinDir)) { + if (!env[pathKey].includes(llvmBinDir)) { // Add LLVM to PATH if not already included - env.PATH = `${llvmBinDir}:${env.PATH}` + env[pathKey] = `${llvmBinDir}${path.delimiter}${env[pathKey]}` } // Force C/C++ code (e.g. zstd-sys) to use Homebrew's clang for wasm32. Otherwise a global @@ -68,7 +86,7 @@ function buildWasm (cratePath, outputDirectory, options = {}) { const args = ['build'] if (profiling) args.push('--profiling') if (skipOptimization) args.push('--no-opt') - args.push('--target', 'nodejs', cratePath, '--out-dir', resolvedOutputDirectory) + args.push('--target', 'nodejs', cratePath, '--out-dir', resolvedOutputDirectory, '--', '-Z', 'build-std=std') childProcess.execFileSync('wasm-pack', args, { env: { ...env, diff --git a/wasm-rust-toolchain b/wasm-rust-toolchain new file mode 100644 index 00000000..ee9644bc --- /dev/null +++ b/wasm-rust-toolchain @@ -0,0 +1 @@ +nightly-2026-07-26 From 71403645647ac134e5e7ca3c5f4cd93cf9b8766c Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 3 Sep 2026 23:26:37 +0200 Subject: [PATCH 17/19] fix(capabilities): reject incomplete HTTP responses (#249) An incomplete HTTP response emits 'aborted' without 'end', so the transport promise and its Remote Config poll remain pending. --- crates/capabilities/src/http_transport.js | 75 +++++++---- .../libdatadog/test/remote-config.test.js | 36 +++++ test/http_transport.js | 123 +++++++++++++++++- 3 files changed, 204 insertions(+), 30 deletions(-) diff --git a/crates/capabilities/src/http_transport.js b/crates/capabilities/src/http_transport.js index 0adb7822..97f42859 100644 --- a/crates/capabilities/src/http_transport.js +++ b/crates/capabilities/src/http_transport.js @@ -193,6 +193,44 @@ module.exports.httpRequest = function (host, port, isHttps, socketPath, connecti function attempt () { return new Promise((resolve, reject) => { + let chunks + let response + let settled = false + + /** @param {Error} [error] */ + function settle (error) { + if (settled) return + settled = true + + if (error) { + reject(error) + return + } + + const body = Buffer.concat(chunks) + if (responseHeaderObserver) { + try { + responseHeaderObserver(response.rawHeaders) + } catch (error) { + // Only read `error.message` (a string) rather than stringifying an + // arbitrary thrown value, so a hostile/throwing toString on the + // error can't turn the log line into its own failure path. + process.stderr.write('responseHeaderObserver error: ' + (error && error.message) + '\n') + } + } + resolve([ + response.statusCode, + response.rawHeaders, + // Buffer is a Uint8Array with exact byteOffset and byteLength. + // Rust copies it into Bytes before this response is released. + body, + ]) + } + + function abortResponse () { + settle(new Error('response aborted')) + } + storage(() => { // wasm_memory.buffer is replaced each time WebAssembly.Memory grows, so // the views must be recreated on every attempt against the current buffer. @@ -216,31 +254,18 @@ module.exports.httpRequest = function (host, port, isHttps, socketPath, connecti ? { socketPath, method, path, headers } : { host, port, method, path, headers } if (!connectionPooling) requestOptions.agent = false - const req = transport.request(requestOptions, (res) => { - const chunks = [] + /** @param {import('node:http').IncomingMessage} res */ + function handleResponse (res) { + response = res + chunks = [] res.on('data', chunk => chunks.push(chunk)) - res.on('end', () => { - const body = Buffer.concat(chunks) - if (responseHeaderObserver) { - try { - responseHeaderObserver(res.rawHeaders) - } catch (error) { - // Only read `err.message` (a string) rather than stringifying an - // arbitrary thrown value, so a hostile/throwing toString on the - // error can't turn the log line into its own failure path. - process.stderr.write('responseHeaderObserver error: ' + (error && error.message) + '\n') - } - } - resolve([ - res.statusCode, - res.rawHeaders, - // Buffer is a Uint8Array with exact byteOffset and byteLength. - // Rust copies it into Bytes before this response is released. - body, - ]) - }) - }) - req.on('error', reject) + res.once('end', settle) + res.once('aborted', abortResponse) + res.once('error', settle) + } + + const req = transport.request(requestOptions, handleResponse) + req.once('error', settle) // The request head (method/path/headers) was supplied via requestOptions // above; just write the stable Node-owned body. (No `req._header` @@ -249,7 +274,7 @@ module.exports.httpRequest = function (host, port, isHttps, socketPath, connecti req.write(body) req.end() } catch (error) { - reject(error) + settle(error) } }) }) diff --git a/packages/libdatadog/test/remote-config.test.js b/packages/libdatadog/test/remote-config.test.js index 3ff4ac0b..3b89f886 100644 --- a/packages/libdatadog/test/remote-config.test.js +++ b/packages/libdatadog/test/remote-config.test.js @@ -115,6 +115,42 @@ test('exports agentless remote config from the dedicated entry point', async () } }) +test('rejects an aborted agentless remote config response', async () => { + const originalRequest = https.request + let requestCount = 0 + + /** + * @param {import('node:https').RequestOptions} requestOptions + * @param {(response: import('node:http').IncomingMessage) => void} onResponse + */ + function request (requestOptions, onResponse) { + assert.strictEqual(typeof requestOptions.method, 'string') + requestCount++ + const outgoing = new EventEmitter() + outgoing.write = () => {} + outgoing.end = () => { + queueMicrotask(() => { + const response = new EventEmitter() + response.statusCode = 200 + response.rawHeaders = [] + onResponse(response) + response.emit('aborted') + }) + } + return outgoing + } + + https.request = request + try { + const fetcher = new RemoteConfigFetcher(fetcherOptions()) + + await assert.rejects(fetcher.fetchChanges(), /response aborted/) + assert(requestCount > 0) + } finally { + https.request = originalRequest + } +}) + /** @param {() => void} callback */ function runWithoutStorage (callback) { callback() diff --git a/test/http_transport.js b/test/http_transport.js index e37593f4..3a78d592 100644 --- a/test/http_transport.js +++ b/test/http_transport.js @@ -6,12 +6,13 @@ // from a Uint8Array view over `wasm_memory.buffer`, so we hand it a fake memory // object containing a well-formed HTTP/1.1 request head. -const { describe, it, before, after, beforeEach } = require('node:test') -const assert = require('node:assert') +const assert = require('node:assert/strict') +const fs = require('node:fs') const http = require('node:http') const os = require('node:os') const path = require('node:path') -const fs = require('node:fs') +const { PassThrough } = require('node:stream') +const { describe, it, before, after, beforeEach } = require('node:test') const transport = require('../crates/capabilities/src/http_transport') @@ -25,6 +26,112 @@ function fakeWasmMemory (headBytes) { return { buffer: buf } } +/** + * @param {'end' | 'error' | 'request-error' | 'write-error'} event + * @param {Error} [error] + */ +async function requestWithResponseEvent (event, error) { + const originalRequest = http.request + const head = Buffer.from('POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n') + + /** + * @param {import('node:http').RequestOptions} requestOptions + * @param {(response: import('node:http').IncomingMessage) => void} onResponse + */ + function request (requestOptions, onResponse) { + assert.strictEqual(requestOptions.method, 'POST') + const outgoing = new PassThrough() + outgoing.write = () => { + if (event === 'write-error') throw error + return true + } + outgoing.end = () => { + queueMicrotask(() => { + if (event === 'request-error') { + outgoing.emit('error', error) + return + } + + const response = new PassThrough() + response.statusCode = 200 + response.rawHeaders = [] + onResponse(response) + response.emit('data', Buffer.from('partial')) + response.emit(event, error) + }) + } + return outgoing + } + + http.request = request + try { + return await transport.httpRequest('localhost', 80, false, '', true, 0, head.length, 0, 0, fakeWasmMemory(head)) + } finally { + http.request = originalRequest + } +} + +describe('http_transport response lifecycle', () => { + it('resolves complete responses', async () => { + const [status, , body] = await requestWithResponseEvent('end') + + assert.strictEqual(status, 200) + assert.strictEqual(Buffer.from(body).toString(), 'partial') + }) + + it('rejects aborted responses', async () => { + let observations = 0 + let resolveResponseClosed + const responseClosed = new Promise((resolve) => { + resolveResponseClosed = resolve + }) + const server = http.createServer((request, response) => { + response.writeHead(200, { 'content-length': 100 }) + response.write('partial') + response.once('close', resolveResponseClosed) + setImmediate(() => response.destroy()) + }) + transport.setResponseHeaderObserver(() => { + observations++ + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + try { + const { port } = server.address() + const head = Buffer.from( + `POST / HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\nContent-Length: 0\r\n\r\n`, + ) + const request = transport.httpRequest( + '127.0.0.1', port, false, '', true, 0, head.length, 0, 0, fakeWasmMemory(head), + ) + + await Promise.all([responseClosed, assert.rejects(request, /response aborted/)]) + assert.strictEqual(observations, 0) + } finally { + transport.setResponseHeaderObserver(undefined) + await new Promise(resolve => server.close(resolve)) + } + }) + + it('rejects response errors', async () => { + const error = new Error('response failed') + + await assert.rejects(requestWithResponseEvent('error', error), error) + }) + + it('preserves request errors', async () => { + const error = new Error('request failed') + + await assert.rejects(requestWithResponseEvent('request-error', error), error) + }) + + it('preserves synchronous write errors', async () => { + const error = new Error('write failed') + + await assert.rejects(requestWithResponseEvent('write-error', error), error) + }) +}) + describe('http_transport response header observer', () => { let server let port @@ -60,12 +167,18 @@ describe('http_transport response header observer', () => { it('invokes the observer with the raw response headers', async () => { let observed - transport.setResponseHeaderObserver((rawHeaders) => { + let observations = 0 + + /** @param {string[]} rawHeaders */ + function observeHeaders (rawHeaders) { observed = rawHeaders - }) + observations++ + } + transport.setResponseHeaderObserver(observeHeaders) await doRequest() + assert.strictEqual(observations, 1) assert.ok(Array.isArray(observed), 'observer received the raw headers array') const idx = observed.findIndex(h => h.toLowerCase() === 'datadog-container-tags-hash') assert.notStrictEqual(idx, -1, 'container-tags hash header present') From f3cc3a307aaffa8ab3fa7ac910ae05ea71215783 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 4 Sep 2026 19:25:23 +0200 Subject: [PATCH 18/19] feat(libdatadog): accept a borrowed transport agent (#259) * feat(libdatadog): accept a borrowed transport agent The exporter cancels its own requests on close but leaves the caller-owned agent alive. * fix(libdatadog): honor borrowed transport agents An unconditional Connection: close header prevents keep-alive agents from reusing sockets. The transport option also accepted objects without Node's required addRequest method, so invalid values failed on the first export. --- packages/libdatadog/README.md | 3 + packages/libdatadog/index.d.ts | 14 ++++- .../libdatadog/lib/agentless-transport.js | 10 +++- packages/libdatadog/lib/agentless.js | 11 ++-- packages/libdatadog/lib/wasm.js | 9 ++- packages/libdatadog/test/exporter.test.js | 58 ++++++++++++++++++- packages/libdatadog/test/types.test.ts | 9 +++ 7 files changed, 100 insertions(+), 14 deletions(-) diff --git a/packages/libdatadog/README.md b/packages/libdatadog/README.md index 3ba86705..417eb6eb 100644 --- a/packages/libdatadog/README.md +++ b/packages/libdatadog/README.md @@ -17,6 +17,9 @@ The package accepts Datadog v0.4 MessagePack payloads and exports them to an agentless intake. `sendV04()` reports completion through a callback and sends delivery failures to the supplied logger. It does not return a promise. +`createAgentlessExporter(options, { agent })` accepts an optional borrowed +Node.js HTTP agent. The caller owns the agent and its lifetime. + The package uses a wasm-bindgen backend with the WebAssembly bytes embedded in JavaScript. The canonical inlined output is published as the regular `@datadog/libdatadog-wasm` dependency from the `wasm` workspace. The WASM diff --git a/packages/libdatadog/index.d.ts b/packages/libdatadog/index.d.ts index 37b58dee..39f52f7a 100644 --- a/packages/libdatadog/index.d.ts +++ b/packages/libdatadog/index.d.ts @@ -80,6 +80,15 @@ export interface AgentlessExporterOptions { obfuscation?: ObfuscationConfig } +interface AgentlessTransportAgent { + addRequest(request: object, options: object): void +} + +export interface AgentlessTransportOptions { + /** Borrowed Node.js HTTP agent. The exporter does not destroy it. */ + agent?: AgentlessTransportAgent +} + export interface AgentlessExporter { sendV04(payload: Uint8Array, done: () => void, log: AgentlessLogger): void close(): void @@ -89,7 +98,10 @@ export interface AgentlessLogger { error(message: string, ...args: unknown[]): void } -export function createAgentlessExporter(options: AgentlessExporterOptions): AgentlessExporter +export function createAgentlessExporter( + options: AgentlessExporterOptions, + transportOptions?: AgentlessTransportOptions +): AgentlessExporter export function backend(): 'wasm' export function zstd_compress(data: Uint8Array, level: number): Uint8Array diff --git a/packages/libdatadog/lib/agentless-transport.js b/packages/libdatadog/lib/agentless-transport.js index 3d09a6ef..58140dfb 100644 --- a/packages/libdatadog/lib/agentless-transport.js +++ b/packages/libdatadog/lib/agentless-transport.js @@ -4,13 +4,16 @@ /** @typedef {{ id: number, url: string, method: string, headers: Header[], body: Uint8Array }} RequestPlan */ /** @typedef {{ status: number | undefined, body: Buffer }} Response */ /** @typedef {(error?: Error, response?: Response) => void} RequestCallback */ +/** @typedef {import('../index').AgentlessTransportOptions} AgentlessTransportOptions */ const maxActiveBufferSize = 16 * 1024 * 1024 const discardedResponse = { status: 200, body: Buffer.alloc(0) } let activeBufferSize = 0 -function createHostTransport () { +/** @param {AgentlessTransportOptions} [options] */ +function createHostTransport ({ agent } = {}) { + const requestAgent = agent ?? false const requests = new Map() const timers = new Map() @@ -28,6 +31,7 @@ function createHostTransport () { const target = new URL(url) const client = target.protocol === 'https:' ? require('node:https') : require('node:http') const headers = Object.fromEntries(headerList.map(({ name, value }) => [name, value])) + if (requestAgent === false) headers.connection = 'close' let settled = false /** @@ -44,8 +48,8 @@ function createHostTransport () { return true } const outgoing = client.request(target, { - agent: false, - headers: { ...headers, connection: 'close' }, + agent: requestAgent, + headers, method, }, (response) => { const chunks = [] diff --git a/packages/libdatadog/lib/agentless.js b/packages/libdatadog/lib/agentless.js index f9fa5292..7e0acebb 100644 --- a/packages/libdatadog/lib/agentless.js +++ b/packages/libdatadog/lib/agentless.js @@ -5,6 +5,7 @@ const { randomUUID } = require('node:crypto') const { createHostTransport } = require('./agentless-transport') /** @typedef {import('../index').AgentlessExporterOptions} AgentlessExporterOptions */ +/** @typedef {import('../index').AgentlessTransportOptions} AgentlessTransportOptions */ /** @typedef {import('../index').AgentlessLogger} AgentlessLogger */ /** @typedef {typeof import('@datadog/libdatadog-wasm')} AgentlessBinding */ @@ -17,13 +18,14 @@ class AgentlessExporter { /** * @param {AgentlessBinding} binding * @param {AgentlessExporterOptions} options + * @param {AgentlessTransportOptions} [transportOptions] */ - constructor (binding, options) { + constructor (binding, options, transportOptions) { const { runtimeId } = options const bindingOptions = runtimeId === undefined || runtimeId === null ? { ...options, runtimeId: randomUUID() } : options - const transport = createHostTransport() + const transport = createHostTransport(transportOptions) this.#binding = new binding.AgentlessExporter( bindingOptions, transport.request, @@ -81,9 +83,10 @@ function errorMessage (error) { /** * @param {AgentlessBinding} binding * @param {AgentlessExporterOptions} options + * @param {AgentlessTransportOptions} [transportOptions] */ -function createAgentlessExporter (binding, options) { - return new AgentlessExporter(binding, options) +function createAgentlessExporter (binding, options, transportOptions) { + return new AgentlessExporter(binding, options, transportOptions) } module.exports = { createAgentlessExporter } diff --git a/packages/libdatadog/lib/wasm.js b/packages/libdatadog/lib/wasm.js index 3bb9c3d5..e2eefa0c 100644 --- a/packages/libdatadog/lib/wasm.js +++ b/packages/libdatadog/lib/wasm.js @@ -9,7 +9,10 @@ module.exports = { zstd_compress: binding.zstd_compress, } -/** @param {import('../index').AgentlessExporterOptions} options */ -function createAgentlessExporter (options) { - return require('./agentless').createAgentlessExporter(binding, options) +/** + * @param {import('../index').AgentlessExporterOptions} options + * @param {import('../index').AgentlessTransportOptions} [transportOptions] + */ +function createAgentlessExporter (options, transportOptions) { + return require('./agentless').createAgentlessExporter(binding, options, transportOptions) } diff --git a/packages/libdatadog/test/exporter.test.js b/packages/libdatadog/test/exporter.test.js index 530221de..9bd9d513 100644 --- a/packages/libdatadog/test/exporter.test.js +++ b/packages/libdatadog/test/exporter.test.js @@ -18,6 +18,39 @@ const wasmArtifact = path.join(packageRoot, 'wasm', 'dist', 'libdatadog_wasm.js' /** @typedef {(error?: unknown) => void} BindingDone */ +class TrackingAgent extends http.Agent { + connections = 0 + requests = 0 + destroyed = false + + constructor () { + super({ keepAlive: true }) + } + + /** + * @param {import('node:http').ClientRequestArgs} options + * @param {(error: Error | null, stream: import('node:stream').Duplex) => void} [callback] + */ + createConnection (options, callback) { + this.connections++ + return super.createConnection(options, callback) + } + + /** + * @param {import('node:http').ClientRequest} request + * @param {import('node:http').RequestOptions} options + */ + addRequest (request, options) { + this.requests++ + super.addRequest(request, options) + } + + destroy () { + this.destroyed = true + super.destroy() + } +} + test('package entry points defer unused agentless modules', { skip: !fs.existsSync(wasmArtifact), }, () => { @@ -188,6 +221,21 @@ test('package entry point compresses agentless v0.4 exports with Zstandard', { await assertExport(require('..')) }) +test('package entry point uses a borrowed transport agent', { + skip: !fs.existsSync(wasmArtifact), +}, async () => { + const agent = new TrackingAgent() + + try { + await assertExport(require('..'), { agent }, 2) + assert.strictEqual(agent.requests, 2) + assert.strictEqual(agent.connections, 1) + assert.strictEqual(agent.destroyed, false) + } finally { + agent.destroy() + } +}) + test('inline-WASM backend validates optional values', { skip: !fs.existsSync(wasmArtifact), }, async () => { @@ -400,8 +448,10 @@ test('agentless exporter close cancels retry backoff', { /** * @param {typeof import('..')} pipeline + * @param {import('../index').AgentlessTransportOptions} [transportOptions] + * @param {number} [count] */ -async function assertExport (pipeline) { +async function assertExport (pipeline, transportOptions, count = 1) { const received = await withIntake(async (endpoint) => { const exporter = pipeline.createAgentlessExporter({ endpoint, @@ -412,10 +462,12 @@ async function assertExport (pipeline) { runtimeId: 'runtime-id', service: 'service', containerId: 'container-id', - }) + }, transportOptions) try { - await sendExport(exporter) + for (let i = 0; i < count; i++) { + await sendExport(exporter) + } } finally { exporter.close() } diff --git a/packages/libdatadog/test/types.test.ts b/packages/libdatadog/test/types.test.ts index 3775554a..9a08e4eb 100644 --- a/packages/libdatadog/test/types.test.ts +++ b/packages/libdatadog/test/types.test.ts @@ -1,5 +1,6 @@ import { type AgentlessLogger, + type AgentlessTransportOptions, backend, createAgentlessExporter, DDSketch, @@ -20,7 +21,15 @@ const agentlessExporter = createAgentlessExporter({ tracerVersion: '1.2.3', languageVersion: '22.0.0', languageInterpreter: 'v8', +}, { + agent: { + addRequest () {}, + }, }) +const invalidTransportOptions: AgentlessTransportOptions = { + // @ts-expect-error Transport agents must implement addRequest. + agent: {}, +} const logger: AgentlessLogger = { error () {}, } From 971f3c809baeae42c5fc90ce13f9efba104aacec Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 Sep 2026 20:16:11 +0200 Subject: [PATCH 19/19] v0.21.0 --- package.json | 2 +- packages/libdatadog/package-lock.json | 8 ++++---- packages/libdatadog/package.json | 4 ++-- packages/libdatadog/wasm/package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index cd4eef95..9d55bb81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/libdatadog-extras", - "version": "0.20.0", + "version": "0.21.0", "description": "Optional native and WASM libdatadog functionality for Node.js", "main": "index.js", "scripts": { diff --git a/packages/libdatadog/package-lock.json b/packages/libdatadog/package-lock.json index 690af2b0..8d0d5f42 100644 --- a/packages/libdatadog/package-lock.json +++ b/packages/libdatadog/package-lock.json @@ -1,18 +1,18 @@ { "name": "@datadog/libdatadog", - "version": "0.20.0", + "version": "0.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@datadog/libdatadog", - "version": "0.20.0", + "version": "0.21.0", "license": "Apache-2.0", "workspaces": [ "wasm" ], "dependencies": { - "@datadog/libdatadog-wasm": "0.20.0" + "@datadog/libdatadog-wasm": "0.21.0" }, "devDependencies": { "@msgpack/msgpack": "^3.1.3", @@ -1901,7 +1901,7 @@ }, "wasm": { "name": "@datadog/libdatadog-wasm", - "version": "0.20.0", + "version": "0.21.0", "license": "Apache-2.0", "engines": { "node": ">=18" diff --git a/packages/libdatadog/package.json b/packages/libdatadog/package.json index 270e5711..b0a4c163 100644 --- a/packages/libdatadog/package.json +++ b/packages/libdatadog/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/libdatadog", - "version": "0.20.0", + "version": "0.21.0", "description": "WASM Node.js bindings for libdatadog", "license": "Apache-2.0", "repository": { @@ -67,7 +67,7 @@ "test:types": "tsc --noEmit --strict --target ES2022 --module Node16 --moduleResolution Node16 test/types.test.ts" }, "dependencies": { - "@datadog/libdatadog-wasm": "0.20.0" + "@datadog/libdatadog-wasm": "0.21.0" }, "devDependencies": { "@msgpack/msgpack": "^3.1.3", diff --git a/packages/libdatadog/wasm/package.json b/packages/libdatadog/wasm/package.json index 70f49d52..59a319a5 100644 --- a/packages/libdatadog/wasm/package.json +++ b/packages/libdatadog/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/libdatadog-wasm", - "version": "0.20.0", + "version": "0.21.0", "description": "WASM fallback for @datadog/libdatadog", "license": "Apache-2.0", "repository": {