From 8d95d6fd20b0c692206731b9130df6a49ef9e217 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 06:23:20 +0000 Subject: [PATCH 01/10] fix(client): avoid memory leak with abort signals --- src/client.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 5220555..a19374e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -512,9 +512,10 @@ export class Brapi { controller: AbortController, ): Promise { const { signal, method, ...options } = init || {}; - if (signal) signal.addEventListener('abort', () => controller.abort()); + const abort = controller.abort.bind(controller); + if (signal) signal.addEventListener('abort', abort, { once: true }); - const timeout = setTimeout(() => controller.abort(), ms); + const timeout = setTimeout(abort, ms); const isReadableBody = ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || From 8d770677b7685b6b5115eec2a678f05bab13b5fd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 06:26:51 +0000 Subject: [PATCH 02/10] chore(client): do not parse responses with empty content-length --- src/internal/parse.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/internal/parse.ts b/src/internal/parse.ts index 6f5ba2e..1e5a00d 100644 --- a/src/internal/parse.ts +++ b/src/internal/parse.ts @@ -29,6 +29,12 @@ export async function defaultParseResponse(client: Brapi, props: APIResponseP const mediaType = contentType?.split(';')[0]?.trim(); const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); if (isJSON) { + const contentLength = response.headers.get('content-length'); + if (contentLength === '0') { + // if there is no content we can't do anything + return undefined as T; + } + const json = await response.json(); return json as T; } From 940d1c22aec74bbb2394953b189c57eda013baff Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 04:41:05 +0000 Subject: [PATCH 03/10] chore(client): restructure abort controller binding --- src/client.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index a19374e..82a09f8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -512,7 +512,7 @@ export class Brapi { controller: AbortController, ): Promise { const { signal, method, ...options } = init || {}; - const abort = controller.abort.bind(controller); + const abort = this._makeAbort(controller); if (signal) signal.addEventListener('abort', abort, { once: true }); const timeout = setTimeout(abort, ms); @@ -538,6 +538,7 @@ export class Brapi { return await this.fetch.call(undefined, url, fetchOptions); } finally { clearTimeout(timeout); + if (signal) signal.removeEventListener('abort', abort); } } @@ -682,6 +683,12 @@ export class Brapi { return headers.values; } + private _makeAbort(controller: AbortController) { + // note: we can't just inline this method inside `fetchWithTimeout()` because then the closure + // would capture all request options, and cause a memory leak. + return () => controller.abort(); + } + private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { bodyHeaders: HeadersLike; body: BodyInit | undefined; From 13b99225a929f1cfc968313464df754b9f64b9fb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 06:10:58 +0000 Subject: [PATCH 04/10] fix(client): avoid removing abort listener too early --- src/client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 82a09f8..d4c38d1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -538,7 +538,6 @@ export class Brapi { return await this.fetch.call(undefined, url, fetchOptions); } finally { clearTimeout(timeout); - if (signal) signal.removeEventListener('abort', abort); } } From fccc6b5f06cb7463889b71d0792e3af84b069ff2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:58:10 +0000 Subject: [PATCH 05/10] chore(internal): avoid type checking errors with ts-reset --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index d4c38d1..107142d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -471,7 +471,7 @@ export class Brapi { loggerFor(this).info(`${responseInfo} - ${retryMessage}`); const errText = await response.text().catch((err: any) => castToError(err).message); - const errJSON = safeJSON(errText); + const errJSON = safeJSON(errText) as any; const errMessage = errJSON ? undefined : errText; loggerFor(this).debug( From 4767da44630b6bb2ca454d103f4098ab1c57561e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 05:16:42 +0000 Subject: [PATCH 06/10] chore(internal/client): fix form-urlencoded requests --- src/client.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/client.ts b/src/client.ts index 107142d..fde829c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -720,6 +720,14 @@ export class Brapi { (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) ) { return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; + } else if ( + typeof body === 'object' && + headers.values.get('content-type') === 'application/x-www-form-urlencoded' + ) { + return { + bodyHeaders: { 'content-type': 'application/x-www-form-urlencoded' }, + body: this.stringifyQuery(body as Record), + }; } else { return this.#encoder({ body, headers }); } From 3ed6269f28554ca012cd8470afd7f1d99a30b33c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 06:26:54 +0000 Subject: [PATCH 07/10] chore(internal): remove mock server code --- scripts/mock | 41 ----------------------------------------- scripts/test | 46 ---------------------------------------------- 2 files changed, 87 deletions(-) delete mode 100755 scripts/mock diff --git a/scripts/mock b/scripts/mock deleted file mode 100755 index 0b28f6e..0000000 --- a/scripts/mock +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run prism mock on the given spec -if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - - # Wait for server to come online - echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do - echo -n "." - sleep 0.1 - done - - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - - echo -else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" -fi diff --git a/scripts/test b/scripts/test index 7bce051..548da9b 100755 --- a/scripts/test +++ b/scripts/test @@ -4,53 +4,7 @@ set -e cd "$(dirname "$0")/.." -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if ! is_overriding_api_base_url && ! prism_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo -fi echo "==> Running tests" ./node_modules/.bin/jest "$@" From fde31931e0c13f8bdca6aca52062c87e0c0d6aab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 06:28:01 +0000 Subject: [PATCH 08/10] chore: update mock server docs --- CONTRIBUTING.md | 6 ------ tests/api-resources/available.test.ts | 4 ++-- tests/api-resources/quote.test.ts | 8 ++++---- tests/api-resources/v2/crypto.test.ts | 8 ++++---- tests/api-resources/v2/currency.test.ts | 8 ++++---- tests/api-resources/v2/inflation.test.ts | 8 ++++---- tests/api-resources/v2/prime-rate.test.ts | 8 ++++---- 7 files changed, 22 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 131f6da..2d0e8c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,12 +65,6 @@ $ pnpm link -—global brapi ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. - -```sh -$ npx prism mock path/to/your/openapi.yml -``` - ```sh $ yarn run test ``` diff --git a/tests/api-resources/available.test.ts b/tests/api-resources/available.test.ts index 6cd723e..b363b0c 100644 --- a/tests/api-resources/available.test.ts +++ b/tests/api-resources/available.test.ts @@ -8,7 +8,7 @@ const client = new Brapi({ }); describe('resource available', () => { - // Prism tests are disabled + // Mock server tests are disabled test.skip('list', async () => { const responsePromise = client.available.list(); const rawResponse = await responsePromise.asResponse(); @@ -20,7 +20,7 @@ describe('resource available', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/quote.test.ts b/tests/api-resources/quote.test.ts index b6cbe06..705a652 100644 --- a/tests/api-resources/quote.test.ts +++ b/tests/api-resources/quote.test.ts @@ -8,7 +8,7 @@ const client = new Brapi({ }); describe('resource quote', () => { - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve', async () => { const responsePromise = client.quote.retrieve('PETR4,MGLU3'); const rawResponse = await responsePromise.asResponse(); @@ -20,7 +20,7 @@ describe('resource quote', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -39,7 +39,7 @@ describe('resource quote', () => { ).rejects.toThrow(Brapi.NotFoundError); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('list', async () => { const responsePromise = client.quote.list(); const rawResponse = await responsePromise.asResponse(); @@ -51,7 +51,7 @@ describe('resource quote', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/v2/crypto.test.ts b/tests/api-resources/v2/crypto.test.ts index 33242f8..bf56234 100644 --- a/tests/api-resources/v2/crypto.test.ts +++ b/tests/api-resources/v2/crypto.test.ts @@ -8,7 +8,7 @@ const client = new Brapi({ }); describe('resource crypto', () => { - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve: only required params', async () => { const responsePromise = client.v2.crypto.retrieve({ coin: 'coin' }); const rawResponse = await responsePromise.asResponse(); @@ -20,7 +20,7 @@ describe('resource crypto', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve: required and optional params', async () => { const response = await client.v2.crypto.retrieve({ coin: 'coin', @@ -31,7 +31,7 @@ describe('resource crypto', () => { }); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable', async () => { const responsePromise = client.v2.crypto.listAvailable(); const rawResponse = await responsePromise.asResponse(); @@ -43,7 +43,7 @@ describe('resource crypto', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/v2/currency.test.ts b/tests/api-resources/v2/currency.test.ts index 1b0bcda..6fb0890 100644 --- a/tests/api-resources/v2/currency.test.ts +++ b/tests/api-resources/v2/currency.test.ts @@ -8,7 +8,7 @@ const client = new Brapi({ }); describe('resource currency', () => { - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve: only required params', async () => { const responsePromise = client.v2.currency.retrieve({ currency: 'USD-BRL,EUR-USD' }); const rawResponse = await responsePromise.asResponse(); @@ -20,12 +20,12 @@ describe('resource currency', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve: required and optional params', async () => { const response = await client.v2.currency.retrieve({ currency: 'USD-BRL,EUR-USD', token: 'token' }); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable', async () => { const responsePromise = client.v2.currency.listAvailable(); const rawResponse = await responsePromise.asResponse(); @@ -37,7 +37,7 @@ describe('resource currency', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/v2/inflation.test.ts b/tests/api-resources/v2/inflation.test.ts index 1b4798e..97cda54 100644 --- a/tests/api-resources/v2/inflation.test.ts +++ b/tests/api-resources/v2/inflation.test.ts @@ -8,7 +8,7 @@ const client = new Brapi({ }); describe('resource inflation', () => { - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve', async () => { const responsePromise = client.v2.inflation.retrieve(); const rawResponse = await responsePromise.asResponse(); @@ -20,7 +20,7 @@ describe('resource inflation', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -39,7 +39,7 @@ describe('resource inflation', () => { ).rejects.toThrow(Brapi.NotFoundError); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable', async () => { const responsePromise = client.v2.inflation.listAvailable(); const rawResponse = await responsePromise.asResponse(); @@ -51,7 +51,7 @@ describe('resource inflation', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/v2/prime-rate.test.ts b/tests/api-resources/v2/prime-rate.test.ts index 2c1e658..3062561 100644 --- a/tests/api-resources/v2/prime-rate.test.ts +++ b/tests/api-resources/v2/prime-rate.test.ts @@ -8,7 +8,7 @@ const client = new Brapi({ }); describe('resource primeRate', () => { - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve', async () => { const responsePromise = client.v2.primeRate.retrieve(); const rawResponse = await responsePromise.asResponse(); @@ -20,7 +20,7 @@ describe('resource primeRate', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('retrieve: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -39,7 +39,7 @@ describe('resource primeRate', () => { ).rejects.toThrow(Brapi.NotFoundError); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable', async () => { const responsePromise = client.v2.primeRate.listAvailable(); const rawResponse = await responsePromise.asResponse(); @@ -51,7 +51,7 @@ describe('resource primeRate', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled + // Mock server tests are disabled test.skip('listAvailable: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( From 1a93d46145de5605b9b1fc088f5ba998626246d0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 06:44:19 +0000 Subject: [PATCH 09/10] fix(docs/contributing): correct pnpm link command --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d0e8c9..c7ede2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,7 @@ $ yarn link brapi # With pnpm $ pnpm link --global $ cd ../my-package -$ pnpm link -—global brapi +$ pnpm link --global brapi ``` ## Running tests From 4c130626d17ed2040847b2abf4b0aa0ae6157c43 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 06:44:35 +0000 Subject: [PATCH 10/10] release: 1.0.6 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 23058a3..bf3786c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.0.5" + ".": "1.0.6" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8026414..a9fee1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 1.0.6 (2026-02-24) + +Full Changelog: [v1.0.5...v1.0.6](https://github.com/brapi-dev/brapi-typescript/compare/v1.0.5...v1.0.6) + +### Bug Fixes + +* **client:** avoid memory leak with abort signals ([8d95d6f](https://github.com/brapi-dev/brapi-typescript/commit/8d95d6fd20b0c692206731b9130df6a49ef9e217)) +* **client:** avoid removing abort listener too early ([13b9922](https://github.com/brapi-dev/brapi-typescript/commit/13b99225a929f1cfc968313464df754b9f64b9fb)) +* **docs/contributing:** correct pnpm link command ([1a93d46](https://github.com/brapi-dev/brapi-typescript/commit/1a93d46145de5605b9b1fc088f5ba998626246d0)) + + +### Chores + +* **client:** do not parse responses with empty content-length ([8d77067](https://github.com/brapi-dev/brapi-typescript/commit/8d770677b7685b6b5115eec2a678f05bab13b5fd)) +* **client:** restructure abort controller binding ([940d1c2](https://github.com/brapi-dev/brapi-typescript/commit/940d1c22aec74bbb2394953b189c57eda013baff)) +* **internal/client:** fix form-urlencoded requests ([4767da4](https://github.com/brapi-dev/brapi-typescript/commit/4767da44630b6bb2ca454d103f4098ab1c57561e)) +* **internal:** avoid type checking errors with ts-reset ([fccc6b5](https://github.com/brapi-dev/brapi-typescript/commit/fccc6b5f06cb7463889b71d0792e3af84b069ff2)) +* **internal:** remove mock server code ([3ed6269](https://github.com/brapi-dev/brapi-typescript/commit/3ed6269f28554ca012cd8470afd7f1d99a30b33c)) +* update mock server docs ([fde3193](https://github.com/brapi-dev/brapi-typescript/commit/fde31931e0c13f8bdca6aca52062c87e0c0d6aab)) + ## 1.0.5 (2026-01-24) Full Changelog: [v1.0.4...v1.0.5](https://github.com/brapi-dev/brapi-typescript/compare/v1.0.4...v1.0.5) diff --git a/package.json b/package.json index 729bfe4..9ba9792 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "brapi", - "version": "1.0.5", + "version": "1.0.6", "description": "The official TypeScript library for the Brapi API", "author": "Brapi ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index c38c828..6ebb2bc 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '1.0.5'; // x-release-please-version +export const VERSION = '1.0.6'; // x-release-please-version