chore(deps): bump major dependencies (#10587)

* chore: bumped zod@3.25.76 to zod@4.6.5

Zod 4 treats a required z.undefined() object key as non-optional, so
absent keys in the RequestCollectionSchema union discriminators failed.
Wrap those keys in .optional() to keep v3 semantics.

Update the deep-strict auth test to expect the unrecognized_keys issue
that zod 4 reports for the strict empty-object fallback branch.

* chore: bumped jsdom@25.0.1 to jsdom@30.1.2

- Clears @asamuzakjp/css-color@3.2.0 advisory (AIKIDO-2026-199800); css-color now 7.1.4.
- No source changes needed: none of the jsdom 26-30 breaking changes hit the repo's jsdom tests.

* chore: bumped @bufbuild/protobuf@1.10.1 to @bufbuild/protobuf@2.16.0

Moves the gRPC stack to connect v2 alongside protobuf v2 (connect-node
and connect 1.7.0 -> 2.2.0). This clears the transitive undici 5.29.0
and @fastify/busboy 2.1.1 under connect-node 1.x.

Source changes:
- grpc.ts: createPromiseClient is removed in v2, so the reflection
  service is now built from a FileDescriptorProto via fromJson and
  createFileRegistry (proto3.makeMessageType is removed). The unary call
  goes through transport.unary directly because createClient cannot type
  a runtime-built descriptor.
- grpc.ts: toBinary takes the schema (FileDescriptorSetSchema from wkt).
- grpc.test.ts: mock transport uses the v2 unary signature (no service
  argument, method.output, fromJsonString).

* chore: regenerate insomnia JSON schema for dependency bumps

* test: add grpc reflection regression tests for connect v2

* test: add smoke tests for buf reflection API

* fix: don't auto-send gRPC reflection API key on request open

* fix: drop non-standard starts_with format from published schema
This commit is contained in:
kwburns-kong authored and GitHub committed 2026-10-19 10:27:30 +08:00
1 parent d370ff0a60
commit ec7b4e1d2a
10 files changed
+832 -263

No files matched your search

+333 -201
View File
@@ -123,23 +123,72 @@
}
},
"node_modules/@asamuzakjp/css-color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.1.6.tgz",
"integrity": "sha512-jZJUZX0mR6JqnjQGb79yTYbR5WXmACmcLoFSgZk+7W+0qMJ8jWGRp9ncYvE1ihwL3dwbmfeCe6/O0rRwWTko4g==",
"license": "MIT",
"dependencies": {
"@csstools/css-calc": "^2.1.3",
"@csstools/css-color-parser": "^3.0.9",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
"lru-cache": "^10.4.3"
"@csstools/css-calc": "^3.4.3",
"@csstools/css-color-parser": "^4.2.6",
"@csstools/css-parser-algorithms": "^4.0.2",
"@csstools/css-tokenizer": "^4.0.2",
"lru-cache": "^11.5.3"
},
"engines": {
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
"version": "11.5.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
"integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@asamuzakjp/dom-selector": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
"license": "MIT",
"dependencies": {
"bidi-js": "^1.1.0",
"css-tree": "^3.2.1",
"is-potential-custom-element-name": "^1.0.1",
"lru-cache": "^11.5.3"
},
"engines": {
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
}
},
"node_modules/@asamuzakjp/dom-selector/node_modules/css-tree": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
"license": "MIT",
"dependencies": {
"mdn-data": "2.27.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
"version": "11.5.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
"integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@asamuzakjp/dom-selector/node_modules/mdn-data": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"license": "CC0-1.0"
},
"node_modules/@asyncapi/specs": {
"version": "6.11.1",
@@ -1127,53 +1176,41 @@
"node": ">=6.9.0"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz",
"integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@connectrpc/connect": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.7.0.tgz",
"integrity": "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==",
"license": "Apache-2.0",
"peerDependencies": {
"@bufbuild/protobuf": "^1.10.0"
}
},
"node_modules/@connectrpc/connect-node": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-1.7.0.tgz",
"integrity": "sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==",
"license": "Apache-2.0",
"dependencies": {
"undici": "^5.28.4"
},
"engines": {
"node": ">=16.0.0"
},
"peerDependencies": {
"@bufbuild/protobuf": "^1.10.0",
"@connectrpc/connect": "1.7.0"
}
},
"node_modules/@connectrpc/connect-node/node_modules/undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"node_modules/@bramus/specificity": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.3.tgz",
"integrity": "sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
"css-tree": "^3.2.1"
},
"engines": {
"node": ">=14.0"
"bin": {
"specificity": "bin/cli.js"
}
},
"node_modules/@bramus/specificity/node_modules/css-tree": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
"license": "MIT",
"dependencies": {
"mdn-data": "2.27.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/@bramus/specificity/node_modules/mdn-data": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"license": "CC0-1.0"
},
"node_modules/@csstools/color-helpers": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.2.tgz",
"integrity": "sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==",
"funding": [
{
"type": "github",
@@ -1186,13 +1223,13 @@
],
"license": "MIT-0",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.3.tgz",
"integrity": "sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==",
"funding": [
{
"type": "github",
@@ -1205,17 +1242,17 @@
],
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-parser-algorithms": "^4.0.2",
"@csstools/css-tokenizer": "^4.0.2"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.6.tgz",
"integrity": "sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==",
"funding": [
{
"type": "github",
@@ -1228,21 +1265,21 @@
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.1.0",
"@csstools/css-calc": "^2.1.4"
"@csstools/color-helpers": "^6.1.2",
"@csstools/css-calc": "^3.4.3"
},
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-parser-algorithms": "^4.0.2",
"@csstools/css-tokenizer": "^4.0.2"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.2.tgz",
"integrity": "sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==",
"funding": [
{
"type": "github",
@@ -1255,16 +1292,16 @@
],
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-tokenizer": "^4.0.2"
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.2.tgz",
"integrity": "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==",
"funding": [
{
"type": "github",
@@ -1277,7 +1314,7 @@
],
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
}
},
"node_modules/@customerio/cdp-analytics-browser": {
@@ -2349,6 +2386,23 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@exodus/bytes": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.16.0.tgz",
"integrity": "sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==",
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@noble/hashes": "^1.8.0 || ^2.0.0"
},
"peerDependenciesMeta": {
"@noble/hashes": {
"optional": true
}
}
},
"node_modules/@exodus/schemasafe": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz",
@@ -2371,15 +2425,6 @@
"npm": ">=10"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
@@ -3288,7 +3333,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
"integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
"dev": true,
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
@@ -10250,6 +10295,15 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/bidi-js": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz",
"integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==",
"license": "MIT",
"dependencies": {
"require-from-string": "^2.0.2"
}
},
"node_modules/bignumber.js": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
@@ -11628,25 +11682,6 @@
"node": ">=4"
}
},
"node_modules/cssstyle": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
"integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^3.2.0",
"rrweb-cssom": "^0.8.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/cssstyle/node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"license": "MIT"
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -11693,16 +11728,25 @@
}
},
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-8.0.0.tgz",
"integrity": "sha512-JQUCCK+/CAGbGGx618PlFZMXH81Jtj7t73CHv9dd/zVWvxgGT2zLFRjnKb+Ng46GIwcy+f7f88qd4duqllK+WQ==",
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0"
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^17.1.2"
},
"engines": {
"node": ">=18"
"node": "^22.14.0 || >=24.0.0"
}
},
"node_modules/data-urls/node_modules/whatwg-mimetype": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/data-view-buffer": {
@@ -15431,15 +15475,15 @@
}
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-7.0.0.tgz",
"integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==",
"license": "MIT",
"dependencies": {
"whatwg-encoding": "^3.1.1"
"@exodus/bytes": "^1.15.1"
},
"engines": {
"node": ">=18"
"node": "^22.13.0 || >=24.0.0"
}
},
"node_modules/html-entities": {
@@ -16896,38 +16940,37 @@
"license": "MIT"
},
"node_modules/jsdom": {
"version": "25.0.1",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz",
"integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
"version": "30.1.3",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
"license": "MIT",
"dependencies": {
"cssstyle": "^4.1.0",
"data-urls": "^5.0.0",
"decimal.js": "^10.4.3",
"form-data": "^4.0.0",
"html-encoding-sniffer": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
"@asamuzakjp/css-color": "^7.1.3",
"@asamuzakjp/dom-selector": "^10.0.3",
"@bramus/specificity": "^2.4.2",
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
"@exodus/bytes": "^1.16.0",
"css-tree": "^3.2.1",
"data-urls": "^8.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^7.0.0",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.12",
"parse5": "^7.1.2",
"rrweb-cssom": "^0.7.1",
"lru-cache": "^11.5.3",
"parse5": "^8.0.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^5.0.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0",
"ws": "^8.18.0",
"tough-cookie": "^6.0.2",
"undici": "^8.12.1",
"w3c-xmlserializer": "^6.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^17.2.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
},
"peerDependencies": {
"canvas": "^2.11.2"
"canvas": "^3.2.3"
},
"peerDependenciesMeta": {
"canvas": {
@@ -16935,34 +16978,112 @@
}
}
},
"node_modules/jsdom/node_modules/form-data": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"node_modules/jsdom/node_modules/@csstools/css-syntax-patches-for-csstree": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.15.tgz",
"integrity": "sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"peerDependencies": {
"css-tree": "^3.2.1"
},
"peerDependenciesMeta": {
"css-tree": {
"optional": true
}
}
},
"node_modules/jsdom/node_modules/css-tree": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
"mdn-data": "2.27.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": ">= 6"
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/jsdom/node_modules/entities": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
"integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/jsdom/node_modules/lru-cache": {
"version": "11.5.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
"integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/jsdom/node_modules/mdn-data": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"license": "CC0-1.0"
},
"node_modules/jsdom/node_modules/parse5": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
"license": "MIT",
"dependencies": {
"entities": "^8.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/jsdom/node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
"integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==",
"license": "BSD-3-Clause",
"dependencies": {
"tldts": "^6.1.32"
"tldts": "^7.0.5"
},
"engines": {
"node": ">=16"
}
},
"node_modules/jsdom/node_modules/undici": {
"version": "8.12.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
}
},
"node_modules/jsdom/node_modules/whatwg-mimetype": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/jsep": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
@@ -19696,12 +19817,6 @@
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==",
"license": "MIT"
},
"node_modules/nwsapi": {
"version": "2.2.23",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
"integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
"license": "MIT"
},
"node_modules/oas-kit-common": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz",
@@ -22124,12 +22239,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/rrweb-cssom": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
"integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
"license": "MIT"
},
"node_modules/rtl-css-js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz",
@@ -23719,12 +23828,6 @@
"node": ">= 6"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
"node_modules/tailwind-merge": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
@@ -24047,21 +24150,21 @@
}
},
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
"version": "7.4.18",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.18.tgz",
"integrity": "sha512-somxA8xKAUzlAxl0TX/jMSYIbRNFIVGnI96pVGJPeMc5BsHmfVXn6mOJT6U++ELnl04eGfzpfcEuZe8WiASkXg==",
"license": "MIT",
"dependencies": {
"tldts-core": "^6.1.86"
"tldts-core": "^7.4.18"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"version": "7.4.18",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.18.tgz",
"integrity": "sha512-KI2PKvCmvQI/xD6ypPqkU0i7oxNXR/O58RLRaHR4svsgVUQ2JE/XC8BtMbG6PSehD27I8sZJRcC1ASnCy6lp3g==",
"license": "MIT"
},
"node_modules/tmp": {
@@ -24151,15 +24254,15 @@
}
},
"node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-7.0.0.tgz",
"integrity": "sha512-BcDSDW+SIGWVOns1Zm6Yg91q9AEj7eaID0QDY9oIqJD1A8dscNwMcl9Sms2DeuWCNov1X0/oXanHGYQBSLwZsg==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
"node": "^22.14.0 || >=24.0.0"
}
},
"node_modules/truncate-utf8-bytes": {
@@ -25571,15 +25674,15 @@
"license": "MIT"
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-6.0.0.tgz",
"integrity": "sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q==",
"license": "MIT",
"dependencies": {
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
}
},
"node_modules/watskeburt": {
@@ -25627,12 +25730,12 @@
"license": "0BSD"
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
"node": ">=20"
}
},
"node_modules/whatwg-encoding": {
@@ -25658,16 +25761,17 @@
}
},
"node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"version": "17.3.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.3.0.tgz",
"integrity": "sha512-KwOAJrpf+GTgBvcBQJcP38sMF0DsQKdwtG+TxM2pW44P6E5k0hDbWrDmyuJDIF0Uef0aQq0ZoJs+5oI+k8ddhg==",
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
"@exodus/bytes": "^1.16.0",
"tr46": "^7.0.0",
"webidl-conversions": "^8.0.1"
},
"engines": {
"node": ">=18"
"node": "^22.14.0 || >=24.0.0"
}
},
"node_modules/which": {
@@ -26184,9 +26288,9 @@
"dependencies": {
"@apideck/better-ajv-errors": "^0.3.6",
"@apidevtools/swagger-parser": "12.1.0",
"@bufbuild/protobuf": "^1.10.0",
"@connectrpc/connect": "^1.6.1",
"@connectrpc/connect-node": "^1.6.1",
"@bufbuild/protobuf": "^2.16.0",
"@connectrpc/connect": "^2.2.0",
"@connectrpc/connect-node": "^2.2.0",
"@customerio/cdp-analytics-browser": "^0.5.9",
"@faker-js/faker": "^10.6.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
@@ -26258,7 +26362,7 @@
"isbot": "^5",
"isomorphic-git": "1.25.7",
"js-yaml": "^4.1.0",
"jsdom": "^25.0.1",
"jsdom": "^30.1.2",
"jshint": "^2.13.6",
"json-bigint": "^1.0.0",
"json-order": "^1.1.3",
@@ -26817,6 +26921,34 @@
"@types/node-forge": "^1.3.14"
}
},
"packages/insomnia/node_modules/@bufbuild/protobuf": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.16.0.tgz",
"integrity": "sha512-FWa0sPlqYGJgpTs6OxBRcL/AW4JT0OIO+W1ajerluoTVh8CV0B7XM1qsNCRgnkSCkRCgOICKHRQlY17jrsm0+Q==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"packages/insomnia/node_modules/@connectrpc/connect": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.2.0.tgz",
"integrity": "sha512-M63OQ1ZFcPUm85CuewJ9G9CMV8LjzGR9935xOnXNQGSazAbwMb3x2W7vXBYM1zqrP7XFD8uxp/dUAhirclgwnQ==",
"license": "Apache-2.0",
"peerDependencies": {
"@bufbuild/protobuf": "^2.7.0"
}
},
"packages/insomnia/node_modules/@connectrpc/connect-node": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.2.0.tgz",
"integrity": "sha512-E6uUO6e11WdynAuBjuN+0WpHjqxEM9BNDdfDIJHcn49Esg/WxSIbn2y1KFt9XL96IJi01qdBbvubcdpBz1X3Zw==",
"license": "Apache-2.0",
"engines": {
"node": ">=22"
},
"peerDependencies": {
"@bufbuild/protobuf": "^2.7.0",
"@connectrpc/connect": "2.2.0"
}
},
"packages/insomnia/node_modules/@testing-library/dom": {
"version": "10.4.1",
"dev": true,
@@ -0,0 +1,46 @@
type: collection.insomnia.rest/5.0
schema_version: "5.1"
name: gRPC Reflection API
meta:
id: wrk_grpc_reflection_api_smoke
created: 1736278634829
modified: 1736278634829
collection:
- url: localhost:50051
name: ReflectionApiUnary
meta:
id: greq_grpc_reflection_api_smoke
created: 1638200268776
modified: 1669986544274
isPrivate: false
sortKey: -1638200268776
body:
text: |-
{
"latitude":"409146138",
"longitude":"-746188906"
}
reflectionApi:
enabled: true
url: http://localhost:4010
apiKey: smoke-reflection-key
module: smoke-module
- url: localhost:50051
name: ReflectionApiInvalidKey
meta:
id: greq_grpc_reflection_api_invalid_key_smoke
created: 1638200268776
modified: 1669986544274
isPrivate: false
sortKey: -1638200268775
body:
text: |-
{
"latitude":"409146138",
"longitude":"-746188906"
}
reflectionApi:
enabled: true
url: http://localhost:4010
apiKey: wrong-key
module: smoke-module
@@ -0,0 +1,155 @@
import express from 'express';
// Mock of the buf.build reflection API (Connect unary) used by getMethodsFromReflectionServer.
// The Insomnia Connect client sends application/proto by default, so responses are encoded to match the
// request content type (binary protobuf or JSON).
const ROUTE_GUIDE_FILE = {
name: 'route_guide.proto',
package: 'routeguide',
syntax: 'proto3',
messageType: [
{
name: 'Point',
field: [
{ name: 'latitude', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_INT32', jsonName: 'latitude' },
{ name: 'longitude', number: 2, label: 'LABEL_OPTIONAL', type: 'TYPE_INT32', jsonName: 'longitude' },
],
},
{
name: 'Feature',
field: [
{ name: 'name', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'name' },
{
name: 'location',
number: 2,
label: 'LABEL_OPTIONAL',
type: 'TYPE_MESSAGE',
typeName: '.routeguide.Point',
jsonName: 'location',
},
],
},
],
service: [
{
name: 'RouteGuide',
method: [{ name: 'GetFeature', inputType: '.routeguide.Point', outputType: '.routeguide.Feature' }],
},
],
};
// Protobuf wire encoding for the descriptor subset above (field numbers from descriptor.proto).
const LABEL_VALUES: Record<string, number> = { LABEL_OPTIONAL: 1, LABEL_REPEATED: 3 };
const TYPE_VALUES: Record<string, number> = { TYPE_INT32: 5, TYPE_STRING: 9, TYPE_MESSAGE: 11 };
const encodeVarint = (value: number): Buffer => {
const bytes: number[] = [];
let rest = value;
while (rest > 0x7f) {
bytes.push((rest & 0x7f) | 0x80);
rest >>>= 7;
}
bytes.push(rest);
return Buffer.from(bytes);
};
const encodeTag = (fieldNumber: number, wireType: number) => encodeVarint((fieldNumber << 3) | wireType);
const encodeString = (fieldNumber: number, value: string) => {
const bytes = Buffer.from(value, 'utf8');
return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(bytes.length), bytes]);
};
const encodeInt = (fieldNumber: number, value: number) =>
Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]);
const encodeMessage = (fieldNumber: number, body: Buffer) =>
Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]);
type FileDescriptor = typeof ROUTE_GUIDE_FILE;
const encodeFileDescriptor = (file: FileDescriptor) =>
Buffer.concat([
encodeString(1, file.name),
encodeString(2, file.package),
...file.messageType.map(message =>
encodeMessage(
4,
Buffer.concat([
encodeString(1, message.name),
...message.field.map(field =>
encodeMessage(
2,
Buffer.concat([
encodeString(1, field.name),
encodeInt(3, field.number),
encodeInt(4, LABEL_VALUES[field.label]),
encodeInt(5, TYPE_VALUES[field.type]),
...('typeName' in field ? [encodeString(6, field.typeName)] : []),
encodeString(10, field.jsonName),
]),
),
),
]),
),
),
...file.service.map(service =>
encodeMessage(
6,
Buffer.concat([
encodeString(1, service.name),
...service.method.map(method =>
encodeMessage(
2,
Buffer.concat([
encodeString(1, method.name),
encodeString(2, method.inputType),
encodeString(3, method.outputType),
]),
),
),
]),
),
),
encodeString(12, file.syntax),
]);
// GetFileDescriptorSetResponse { FileDescriptorSet file_descriptor_set = 1 } where FileDescriptorSet { repeated FileDescriptorProto file = 1 }
const encodeResponse = () => encodeMessage(1, encodeMessage(1, encodeFileDescriptor(ROUTE_GUIDE_FILE)));
// The Connect client only sets `module` (field 1, string) on the wire.
const decodeModuleFromProto = (body: Buffer): string | undefined => {
if (body[0] !== 0x0a) {
return undefined;
}
const length = body[1];
return body.subarray(2, 2 + length).toString('utf8');
};
const router = express.Router();
router.post(
'/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet',
express.raw({ type: '*/*' }),
(req, res) => {
if (req.headers.authorization !== 'Bearer smoke-reflection-key') {
res.status(401).json({ code: 'unauthenticated', message: 'invalid api key' });
return;
}
const body = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
const isJson = (req.headers['content-type'] ?? '').includes('json');
const moduleName = isJson
? (JSON.parse(body.toString('utf8') || '{}') as { module?: string }).module
: decodeModuleFromProto(body);
if (moduleName !== 'smoke-module') {
res.status(404).json({ code: 'not_found', message: 'module not found' });
return;
}
if (isJson) {
res.status(200).json({ fileDescriptorSet: { file: [ROUTE_GUIDE_FILE] } });
return;
}
res.status(200).type('application/proto').send(encodeResponse());
},
);
export default router;
@@ -29,6 +29,7 @@ import githubApi from './github-api';
import gitlabApi from './gitlab-api';
import { schema } from './graphql';
import { startGRPCServer } from './grpc';
import grpcReflectionApi from './grpc-reflection-api';
import insomniaApi from './insomnia-api';
import { mtlsRouter } from './mtls';
import { oauthRoutes } from './oauth';
@@ -109,6 +110,7 @@ app.get('/cookies', (_req, res) => {
app.use('/file', express.static('fixtures/files'));
app.use('/auth/basic', basicAuthRouter);
app.use('/protected', mtlsRouter);
app.use(grpcReflectionApi);
githubApi(app);
gitlabApi(app);
@@ -0,0 +1,39 @@
import { expect } from '@playwright/test';
import { loadFixture } from '../../playwright/paths';
import { test } from '../../playwright/test';
test('can load methods through the buf reflection API', async ({ app, page, insomnia }) => {
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
const text = await loadFixture('grpc-reflection-api.yaml');
await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);
await page.getByLabel('Import').click();
await page.locator('[data-test-id="import-from-clipboard"]').click();
await page.getByRole('button', { name: 'Scan' }).click();
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();
await insomnia.navigationSidebar.clickRequestOrFolder('ReflectionApiUnary');
await page.getByTestId('button-server-reflection').click();
await page.getByRole('button', { name: 'Select Method' }).click();
await expect.soft(page.getByRole('option', { name: 'RouteGuide/GetFeature' })).toBeVisible();
});
test('shows an error for an invalid reflection api key', async ({ app, page, insomnia }) => {
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
const text = await loadFixture('grpc-reflection-api.yaml');
await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);
await page.getByLabel('Import').click();
await page.locator('[data-test-id="import-from-clipboard"]').click();
await page.getByRole('button', { name: 'Scan' }).click();
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();
await insomnia.navigationSidebar.clickRequestOrFolder('ReflectionApiInvalidKey');
await page.getByTestId('button-server-reflection').click();
await expect.soft(page.getByRole('dialog')).toContainText('Invalid reflection server api key');
});
+4 -4
View File
@@ -51,9 +51,9 @@
"dependencies": {
"@apideck/better-ajv-errors": "^0.3.6",
"@apidevtools/swagger-parser": "12.1.0",
"@bufbuild/protobuf": "^1.10.0",
"@connectrpc/connect": "^1.6.1",
"@connectrpc/connect-node": "^1.6.1",
"@bufbuild/protobuf": "^2.16.0",
"@connectrpc/connect": "^2.2.0",
"@connectrpc/connect-node": "^2.2.0",
"@customerio/cdp-analytics-browser": "^0.5.9",
"@faker-js/faker": "^10.6.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
@@ -125,7 +125,7 @@
"isbot": "^5",
"isomorphic-git": "1.25.7",
"js-yaml": "^4.1.0",
"jsdom": "^25.0.1",
"jsdom": "^30.1.2",
"jshint": "^2.13.6",
"json-bigint": "^1.0.0",
"json-order": "^1.1.3",
@@ -0,0 +1,182 @@
import { type DescMessage, type DescMethodUnary, fromJson, type JsonObject, type MessageInitShape } from '@bufbuild/protobuf';
import { Code, ConnectError, type UnaryResponse } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-node';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { version } from '../../../../package.json';
import { loadMethodsFromReflection } from '../grpc';
vi.mock('@connectrpc/connect-node');
vi.mock('../../../network/grpc/write-proto-file.node');
const reflectionApi = {
enabled: true,
apiKey: 'TEST_KEY',
url: 'https://buf.build',
module: 'buf.build/acme/greeter',
};
const fileDescriptorSetJson: JsonObject = {
file: [
{
name: 'acme/greeter/v1/greeter.proto',
package: 'acme.greeter.v1',
syntax: 'proto3',
messageType: [
{
name: 'Msg',
field: [{ name: 'text', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'text' }],
},
],
service: [
{
name: 'GreeterService',
method: [
{ name: 'Unary', inputType: '.acme.greeter.v1.Msg', outputType: '.acme.greeter.v1.Msg' },
{
name: 'ClientStream',
inputType: '.acme.greeter.v1.Msg',
outputType: '.acme.greeter.v1.Msg',
clientStreaming: true,
},
{
name: 'ServerStream',
inputType: '.acme.greeter.v1.Msg',
outputType: '.acme.greeter.v1.Msg',
serverStreaming: true,
},
{
name: 'BidiStream',
inputType: '.acme.greeter.v1.Msg',
outputType: '.acme.greeter.v1.Msg',
clientStreaming: true,
serverStreaming: true,
},
],
},
],
},
],
};
// Installs a fake Connect transport whose unary() is the given mock.
const useTransport = (unary: ReturnType<typeof vi.fn>) => {
vi.mocked(createConnectTransport).mockReturnValue({ unary } as unknown as ReturnType<typeof createConnectTransport>);
};
const loadBuf = (options: { apiKey?: string; disableUserAgentHeader?: boolean } = {}) =>
loadMethodsFromReflection({
url: 'foo.com',
metadata: [],
rejectUnauthorized: true,
disableUserAgentHeader: options.disableUserAgentHeader,
reflectionApi: { ...reflectionApi, apiKey: options.apiKey ?? reflectionApi.apiKey },
});
const headersOf = (unary: ReturnType<typeof vi.fn>) => new Headers(unary.mock.calls[0][3] as HeadersInit);
describe('loadMethodsFromReflection (buf reflection api)', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('error mapping', () => {
it('maps Unauthenticated to an invalid api key error', async () => {
useTransport(vi.fn().mockRejectedValue(new ConnectError('denied', Code.Unauthenticated)));
await expect(loadBuf()).rejects.toThrow('Invalid reflection server api key');
});
it('maps NotFound to a module access error', async () => {
useTransport(vi.fn().mockRejectedValue(new ConnectError('missing', Code.NotFound)));
await expect(loadBuf()).rejects.toThrow(
"The reflection server api key doesn't have access to the module or the module does not exists",
);
});
it('rethrows other Connect errors unchanged', async () => {
const error = new ConnectError('boom', Code.Internal);
useTransport(vi.fn().mockRejectedValue(error));
await expect(loadBuf()).rejects.toBe(error);
});
it('rethrows non-Connect errors unchanged', async () => {
const error = new Error('network down');
useTransport(vi.fn().mockRejectedValue(error));
await expect(loadBuf()).rejects.toBe(error);
});
});
describe('request headers', () => {
it('sends the Insomnia User-Agent and Bearer auth when enabled', async () => {
const unary = vi.fn().mockRejectedValue(new ConnectError('stop', Code.Internal));
useTransport(unary);
await expect(loadBuf()).rejects.toBeDefined();
expect(headersOf(unary).get('User-Agent')).toBe(`insomnia/${version}`);
expect(headersOf(unary).get('Authorization')).toBe('Bearer TEST_KEY');
});
it('omits the User-Agent header when disabled', async () => {
const unary = vi.fn().mockRejectedValue(new ConnectError('stop', Code.Internal));
useTransport(unary);
await expect(loadBuf({ disableUserAgentHeader: true })).rejects.toBeDefined();
expect(headersOf(unary).has('User-Agent')).toBe(false);
expect(headersOf(unary).get('Authorization')).toBe('Bearer TEST_KEY');
});
it('omits the Authorization header when no api key is set', async () => {
const unary = vi.fn().mockRejectedValue(new ConnectError('stop', Code.Internal));
useTransport(unary);
await expect(loadBuf({ apiKey: '' })).rejects.toBeDefined();
expect(headersOf(unary).has('Authorization')).toBe(false);
});
});
describe('response handling', () => {
it('returns no methods when the response has no file descriptor set', async () => {
useTransport(
vi.fn(
async (method: DescMethodUnary): Promise<UnaryResponse> =>
({ message: fromJson(method.output, {}) }) as unknown as UnaryResponse,
),
);
await expect(loadBuf()).resolves.toStrictEqual([]);
});
it('maps unary, client, server and bidi methods from the file descriptor set', async () => {
useTransport(
vi.fn(
async (
method: DescMethodUnary,
_signal: AbortSignal | undefined,
_timeoutMs: number | undefined,
_header: HeadersInit | undefined,
_input: MessageInitShape<DescMessage>,
): Promise<UnaryResponse> =>
({
message: fromJson(method.output, { fileDescriptorSet: fileDescriptorSetJson }),
}) as unknown as UnaryResponse,
),
);
const methods = await loadBuf();
expect(methods.map(({ fullPath, type }) => ({ fullPath, type }))).toStrictEqual([
{ fullPath: '/acme.greeter.v1.GreeterService/Unary', type: 'unary' },
{ fullPath: '/acme.greeter.v1.GreeterService/ClientStream', type: 'client' },
{ fullPath: '/acme.greeter.v1.GreeterService/ServerStream', type: 'server' },
{ fullPath: '/acme.greeter.v1.GreeterService/BidiStream', type: 'bidi' },
]);
});
});
});
@@ -1,5 +1,5 @@
// @ts-nocheck
import type { AnyMessage, MethodInfo, PartialMessage, ServiceType } from '@bufbuild/protobuf';
import { type DescMessage, type DescMethodUnary, fromJsonString, type MessageInitShape } from '@bufbuild/protobuf';
import type { UnaryResponse } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-node';
import * as grpcReflection from 'grpc-reflection-js';
@@ -193,23 +193,22 @@ describe('loadMethodsFromReflection', () => {
expect(options.baseUrl).toStrictEqual('https://buf.build');
return {
async unary(
service: ServiceType,
method: MethodInfo,
method: DescMethodUnary,
_: AbortSignal | undefined,
__: number | undefined,
header: HeadersInit | undefined,
input: PartialMessage<AnyMessage>,
input: MessageInitShape<DescMessage>,
): Promise<UnaryResponse> {
expect(new Headers(header).get('Authorization')).toStrictEqual('Bearer TEST_KEY');
expect(input).toStrictEqual({ module: 'buf.build/connectrpc/eliza' });
return {
service: service,
method: method,
header: new Headers(),
trailer: new Headers(),
stream: false,
// Output of running `buf curl https://buf.build/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet --data '{"module": "buf.build/connectrpc/eliza"}' --schema buf.build/bufbuild/reflect -H 'Authorization: Bearer buf-token'`
message: method.O.fromJsonString(
message: fromJsonString(
method.output,
'{"fileDescriptorSet":{"file":[{"name":"connectrpc/eliza/v1/eliza.proto","package":"connectrpc.eliza.v1","messageType":[{"name":"SayRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"SayResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"IntroduceRequest","field":[{"name":"name","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"name"}]},{"name":"IntroduceResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]}],"service":[{"name":"ElizaService","method":[{"name":"Say","inputType":".connectrpc.eliza.v1.SayRequest","outputType":".connectrpc.eliza.v1.SayResponse","options":{"idempotencyLevel":"NO_SIDE_EFFECTS"}},{"name":"Converse","inputType":".connectrpc.eliza.v1.ConverseRequest","outputType":".connectrpc.eliza.v1.ConverseResponse","options":{},"clientStreaming":true,"serverStreaming":true},{"name":"Introduce","inputType":".connectrpc.eliza.v1.IntroduceRequest","outputType":".connectrpc.eliza.v1.IntroduceResponse","options":{},"serverStreaming":true}]}],"syntax":"proto3"}]},"version":"233fca715f49425581ec0a1b660be886"}',
),
};
+63 -51
View File
@@ -1,10 +1,11 @@
import { createFileRegistry, type DescMethodUnary, fromJson, toBinary } from '@bufbuild/protobuf';
import {
FileDescriptorSet as ProtobufEsFileDescriptorSet,
MethodIdempotency,
MethodKind,
proto3,
} from '@bufbuild/protobuf';
import { Code, ConnectError, createPromiseClient } from '@connectrpc/connect';
file_google_protobuf_descriptor,
FileDescriptorProtoSchema,
type FileDescriptorSet,
FileDescriptorSetSchema,
} from '@bufbuild/protobuf/wkt';
import { Code, ConnectError } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-node';
import {
type Call,
@@ -132,65 +133,76 @@ const getMethodsFromReflectionServer = async (
disableUserAgentHeader: boolean,
): Promise<MethodDefs[]> => {
const { url, module, apiKey } = reflectionApi;
const GetFileDescriptorSetRequest = proto3.makeMessageType('buf.reflect.v1beta1.GetFileDescriptorSetRequest', () => [
{ no: 1, name: 'module', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
{ no: 2, name: 'version', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
{
no: 3,
name: 'symbols',
kind: 'scalar',
T: 9 /* ScalarType.STRING */,
repeated: true,
},
]);
const GetFileDescriptorSetResponse = proto3.makeMessageType(
'buf.reflect.v1beta1.GetFileDescriptorSetResponse',
() => [
{
no: 1,
name: 'file_descriptor_set',
kind: 'message',
T: ProtobufEsFileDescriptorSet,
},
{ no: 2, name: 'version', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
],
const reflectionRegistry = createFileRegistry(
fromJson(FileDescriptorProtoSchema, {
name: 'buf/reflect/v1beta1/file_descriptor_set_service.proto',
package: 'buf.reflect.v1beta1',
syntax: 'proto3',
dependency: ['google/protobuf/descriptor.proto'],
messageType: [
{
name: 'GetFileDescriptorSetRequest',
field: [
{ name: 'module', number: 1, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'module' },
{ name: 'version', number: 2, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'version' },
{ name: 'symbols', number: 3, label: 'LABEL_REPEATED', type: 'TYPE_STRING', jsonName: 'symbols' },
],
},
{
name: 'GetFileDescriptorSetResponse',
field: [
{
name: 'file_descriptor_set',
number: 1,
label: 'LABEL_OPTIONAL',
type: 'TYPE_MESSAGE',
typeName: '.google.protobuf.FileDescriptorSet',
jsonName: 'fileDescriptorSet',
},
{ name: 'version', number: 2, label: 'LABEL_OPTIONAL', type: 'TYPE_STRING', jsonName: 'version' },
],
},
],
service: [
{
name: 'FileDescriptorSetService',
method: [
{
name: 'GetFileDescriptorSet',
inputType: '.buf.reflect.v1beta1.GetFileDescriptorSetRequest',
outputType: '.buf.reflect.v1beta1.GetFileDescriptorSetResponse',
options: { idempotencyLevel: 'NO_SIDE_EFFECTS' },
},
],
},
],
}),
importName => (importName === 'google/protobuf/descriptor.proto' ? file_google_protobuf_descriptor : undefined),
);
const FileDescriptorSetService = {
typeName: 'buf.reflect.v1beta1.FileDescriptorSetService',
methods: {
getFileDescriptorSet: {
name: 'GetFileDescriptorSet',
I: GetFileDescriptorSetRequest,
O: GetFileDescriptorSetResponse,
kind: MethodKind.Unary,
idempotency: MethodIdempotency.NoSideEffects,
},
},
} as const;
const FileDescriptorSetService = reflectionRegistry.getService('buf.reflect.v1beta1.FileDescriptorSetService');
invariant(FileDescriptorSetService, 'FileDescriptorSetService is missing from the reflection descriptor');
const transport = createConnectTransport({
baseUrl: url,
httpVersion: '1.1',
});
const client = createPromiseClient(FileDescriptorSetService, transport);
const getFileDescriptorSetMethod = FileDescriptorSetService.method.getFileDescriptorSet;
invariant(getFileDescriptorSetMethod.methodKind === 'unary', 'GetFileDescriptorSet must be a unary method');
const headers: HeadersInit = {
...(disableUserAgentHeader ? {} : { 'User-Agent': `insomnia/${version}` }),
...(apiKey === '' ? {} : { Authorization: `Bearer ${apiKey}` }),
};
try {
const res = await client.getFileDescriptorSet(
{
module,
},
{
headers,
},
);
// Calls the transport directly: createClient() cannot type a descriptor built at runtime.
const res = await transport.unary(getFileDescriptorSetMethod as DescMethodUnary, undefined, undefined, headers, {
module,
});
const response = res.message as { fileDescriptorSet?: FileDescriptorSet };
const methodDefs: MethodDefs[] = [];
if (res.fileDescriptorSet === undefined) {
if (response.fileDescriptorSet === undefined) {
return [];
}
const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer(
Buffer.from(res.fileDescriptorSet.toBinary()),
Buffer.from(toBinary(FileDescriptorSetSchema, response.fileDescriptorSet)),
);
for (const definition of Object.values(packageDefinition)) {
const serviceDefinition = asServiceDefinition(definition);
@@ -86,7 +86,9 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({ grpcState, setGrpcSt
console.log(`[gRPC] loading proto file methods pf=${activeRequest.protoFileId}`);
const methods = await window.main.grpc.loadMethods(activeRequest.protoFileId);
setGrpcState({ ...grpcState, methods });
} else if (activeRequest.url && activeRequest.reflectionApi) {
} else if (activeRequest.url && activeRequest.reflectionApi && !activeRequest.reflectionApi.enabled) {
// Intentionally skipped when the buf reflection API is enabled: loading would send the API key on pane open.
// In that case methods are only loaded when the user clicks the reflection refresh button.
const requestGroups = (
await db.withAncestors<GrpcRequest | RequestGroup>(activeRequest, [models.requestGroup.type])
).filter(isRequestGroup);