Skip to content

Commit 23046e6

Browse files
authored
Merge pull request #55 from stainless-api/release-please--branches--main--changes--next--components--stainless
release: 0.1.0-alpha.16
2 parents 544c4b7 + 188294b commit 23046e6

18 files changed

+601
-376
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.1.0-alpha.15"
2+
".": "0.1.0-alpha.16"
33
}

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Changelog
22

3+
## 0.1.0-alpha.16 (2025-01-22)
4+
5+
Full Changelog: [v0.1.0-alpha.15...v0.1.0-alpha.16](https://github.com/stainless-api/builds-node-api/compare/v0.1.0-alpha.15...v0.1.0-alpha.16)
6+
7+
### ⚠ BREAKING CHANGES
8+
9+
* **client:** document proxy use + clean up old code ([#57](https://github.com/stainless-api/builds-node-api/issues/57))
10+
11+
### Bug Fixes
12+
13+
* correctly send default header values ([#58](https://github.com/stainless-api/builds-node-api/issues/58)) ([6818d5a](https://github.com/stainless-api/builds-node-api/commit/6818d5a92cfa3edeeb88797b7f5e567cee31dcc4))
14+
15+
16+
### Chores
17+
18+
* **client:** document proxy use + clean up old code ([#57](https://github.com/stainless-api/builds-node-api/issues/57)) ([18f95e7](https://github.com/stainless-api/builds-node-api/commit/18f95e7c13d5920a88aee790477824c66989cc05))
19+
* **client:** improve node-fetch file upload errors ([#56](https://github.com/stainless-api/builds-node-api/issues/56)) ([1f48b98](https://github.com/stainless-api/builds-node-api/commit/1f48b98aaf358183b517d07da5fd44909119f1c1))
20+
* **internal:** codegen related update ([#54](https://github.com/stainless-api/builds-node-api/issues/54)) ([a0504f2](https://github.com/stainless-api/builds-node-api/commit/a0504f2230fb783e10b1443645f5d26fdde13987))
21+
* **internal:** minor restructuring ([#59](https://github.com/stainless-api/builds-node-api/issues/59)) ([a03e6ca](https://github.com/stainless-api/builds-node-api/commit/a03e6ca28992147f6aac63493ec28f3f99ac6e33))
22+
323
## 0.1.0-alpha.15 (2025-01-17)
424

525
Full Changelog: [v0.1.0-alpha.14...v0.1.0-alpha.15](https://github.com/stainless-api/builds-node-api/compare/v0.1.0-alpha.14...v0.1.0-alpha.15)

README.md

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -250,30 +250,62 @@ const client = new Stainless({
250250
Note that if given a `STAINLESS_LOG=debug` environment variable, this library will log all requests and responses automatically.
251251
This is intended for debugging purposes only and may change in the future without notice.
252252

253-
### Configuring an HTTP(S) Agent (e.g., for proxies)
253+
### Fetch options
254254

255-
By default, this library uses a stable agent for all http/https requests to reuse TCP connections, eliminating many TCP & TLS handshakes and shaving around 100ms off most requests.
255+
If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.)
256256

257-
If you would like to disable or customize this behavior, for example to use the API behind a proxy, you can pass an `httpAgent` which is used for all requests (be they http or https), for example:
257+
```ts
258+
import Stainless from 'stainless';
259+
260+
const client = new Stainless({
261+
fetchOptions: {
262+
// `RequestInit` options
263+
},
264+
});
265+
```
266+
267+
#### Configuring proxies
268+
269+
To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy
270+
options to requests:
271+
272+
<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/node.svg" align="top" width="18" height="21"> **Node** <sup>[[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)]</sup>
258273

259-
<!-- prettier-ignore -->
260274
```ts
261-
import http from 'http';
262-
import { HttpsProxyAgent } from 'https-proxy-agent';
275+
import Stainless from 'stainless';
276+
import * as undici from 'undici';
277+
278+
const proxyAgent = new undici.ProxyAgent('http://localhost:8888');
279+
const client = new Stainless({
280+
fetchOptions: {
281+
dispatcher: proxyAgent,
282+
},
283+
});
284+
```
285+
286+
<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/bun.svg" align="top" width="18" height="21"> **Bun** <sup>[[docs](https://bun.sh/guides/http/proxy)]</sup>
287+
288+
```ts
289+
import Stainless from 'stainless';
263290

264-
// Configure the default for all requests:
265291
const client = new Stainless({
266-
httpAgent: new HttpsProxyAgent(process.env.PROXY_URL),
292+
fetchOptions: {
293+
proxy: 'http://localhost:8888',
294+
},
267295
});
296+
```
268297

269-
// Override per-request:
270-
await client.builds.outputs.retrieve(
271-
'bui_123',
272-
{ target: 'node' },
273-
{
274-
httpAgent: new http.Agent({ keepAlive: false }),
298+
<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/deno.svg" align="top" width="18" height="21"> **Deno** <sup>[[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)]</sup>
299+
300+
```ts
301+
import Stainless from 'npm:stainless';
302+
303+
const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } });
304+
const client = new Stainless({
305+
fetchOptions: {
306+
client: httpClient,
275307
},
276-
);
308+
});
277309
```
278310

279311
## Frequently Asked Questions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "stainless",
3-
"version": "0.1.0-alpha.15",
3+
"version": "0.1.0-alpha.16",
44
"description": "The official TypeScript library for the Stainless API",
55
"author": "Stainless <[email protected]>",
66
"types": "dist/index.d.ts",

scripts/utils/attw-report.cjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')
88
(
99
(problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) ||
1010
// This is intentional for backwards compat reasons.
11-
(problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js'))
11+
(problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) ||
12+
// this is intentional, we deliberately attempt to import types that may not exist from parent node_modules
13+
// folders to better support various runtimes without triggering automatic type acquisition.
14+
(problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules'))
1215
)
1316
),
1417
);

src/client.ts

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types';
4-
import type { HTTPMethod, PromiseOrValue } from './internal/types';
4+
import type { HTTPMethod, PromiseOrValue, MergedRequestInit } from './internal/types';
55
import { uuid4 } from './internal/utils/uuid';
66
import { validatePositiveInteger, isAbsoluteURL } from './internal/utils/values';
77
import { sleep } from './internal/utils/sleep';
@@ -11,14 +11,12 @@ import { getPlatformHeaders } from './internal/detect-platform';
1111
import * as Shims from './internal/shims';
1212
import * as Opts from './internal/request-options';
1313
import { VERSION } from './version';
14-
import { isBlobLike } from './uploads';
15-
import { buildHeaders } from './internal/headers';
1614
import * as Errors from './error';
1715
import * as Uploads from './uploads';
1816
import * as API from './resources/index';
1917
import { APIPromise } from './api-promise';
2018
import { type Fetch } from './internal/builtin-types';
21-
import { HeadersLike, NullableHeaders } from './internal/headers';
19+
import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers';
2220
import { FinalRequestOptions, RequestOptions } from './internal/request-options';
2321
import { readEnv } from './internal/utils/env';
2422
import { logger } from './internal/utils/log';
@@ -78,15 +76,12 @@ export interface ClientOptions {
7876
* Note that request timeouts are retried by default, so in a worst-case scenario you may wait
7977
* much longer than this timeout before the promise succeeds or fails.
8078
*/
81-
timeout?: number;
82-
79+
timeout?: number | undefined;
8380
/**
84-
* An HTTP agent used to manage HTTP(S) connections.
85-
*
86-
* If not provided, an agent will be constructed by default in the Node.js environment,
87-
* otherwise no agent is used.
81+
* Additional `RequestInit` options to be passed to `fetch` calls.
82+
* Properties will be overridden by per-request `fetchOptions`.
8883
*/
89-
httpAgent?: Shims.Agent;
84+
fetchOptions?: MergedRequestInit | undefined;
9085

9186
/**
9287
* Specify a custom `fetch` function implementation.
@@ -101,23 +96,23 @@ export interface ClientOptions {
10196
*
10297
* @default 2
10398
*/
104-
maxRetries?: number;
99+
maxRetries?: number | undefined;
105100

106101
/**
107102
* Default headers to include with every request to the API.
108103
*
109104
* These can be removed in individual requests by explicitly setting the
110105
* header to `null` in request options.
111106
*/
112-
defaultHeaders?: HeadersLike;
107+
defaultHeaders?: HeadersLike | undefined;
113108

114109
/**
115110
* Default query parameters to include with every request to the API.
116111
*
117112
* These can be removed in individual requests by explicitly setting the
118113
* param to `undefined` in request options.
119114
*/
120-
defaultQuery?: Record<string, string | undefined>;
115+
defaultQuery?: Record<string, string | undefined> | undefined;
121116

122117
/**
123118
* Set the log level.
@@ -147,7 +142,7 @@ export class Stainless {
147142
timeout: number;
148143
logger: Logger | undefined;
149144
logLevel: LogLevel | undefined;
150-
httpAgent: Shims.Agent | undefined;
145+
fetchOptions: MergedRequestInit | undefined;
151146

152147
private fetch: Fetch;
153148
#encoder: Opts.RequestEncoder;
@@ -160,7 +155,7 @@ export class Stainless {
160155
* @param {string | undefined} [opts.apiKey=process.env['API_KEY'] ?? undefined]
161156
* @param {string} [opts.baseURL=process.env['STAINLESS_BASE_URL'] ?? https://api.stainlessapi.com] - Override the default base URL for the API.
162157
* @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
163-
* @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections.
158+
* @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls.
164159
* @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
165160
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
166161
* @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API.
@@ -194,7 +189,7 @@ export class Stainless {
194189
this.logLevel = envLevel;
195190
}
196191
}
197-
this.httpAgent = options.httpAgent;
192+
this.fetchOptions = options.fetchOptions;
198193
this.maxRetries = options.maxRetries ?? 2;
199194
this.fetch = options.fetch ?? Shims.getDefaultFetch();
200195
this.#encoder = Opts.FallbackEncoder;
@@ -331,14 +326,8 @@ export class Stainless {
331326
opts?: PromiseOrValue<RequestOptions>,
332327
): APIPromise<Rsp> {
333328
return this.request(
334-
Promise.resolve(opts).then(async (opts) => {
335-
const body =
336-
opts && isBlobLike(opts?.body) ? new DataView(await opts.body.arrayBuffer())
337-
: opts?.body instanceof DataView ? opts.body
338-
: opts?.body instanceof ArrayBuffer ? new DataView(opts.body)
339-
: opts && ArrayBuffer.isView(opts?.body) ? new DataView(opts.body.buffer)
340-
: opts?.body;
341-
return { method, path, ...opts, body };
329+
Promise.resolve(opts).then((opts) => {
330+
return { method, path, ...opts };
342331
}),
343332
);
344333
}
@@ -533,30 +522,18 @@ export class Stainless {
533522
const url = this.buildURL(path!, query as Record<string, unknown>);
534523
if ('timeout' in options) validatePositiveInteger('timeout', options.timeout);
535524
const timeout = options.timeout ?? this.timeout;
536-
const httpAgent = options.httpAgent ?? this.httpAgent;
537-
const minAgentTimeout = timeout + 1000;
538-
if (
539-
typeof (httpAgent as any)?.options?.timeout === 'number' &&
540-
minAgentTimeout > ((httpAgent as any).options.timeout ?? 0)
541-
) {
542-
// Allow any given request to bump our agent active socket timeout.
543-
// This may seem strange, but leaking active sockets should be rare and not particularly problematic,
544-
// and without mutating agent we would need to create more of them.
545-
// This tradeoff optimizes for performance.
546-
(httpAgent as any).options.timeout = minAgentTimeout;
547-
}
548-
549525
const { bodyHeaders, body } = this.buildBody({ options });
550526
const reqHeaders = this.buildHeaders({ options, method, bodyHeaders, retryCount });
551527

552528
const req: FinalizedRequestInit = {
553529
method,
554530
headers: reqHeaders,
555-
...(httpAgent && { agent: httpAgent }),
556531
...(options.signal && { signal: options.signal }),
557532
...((globalThis as any).ReadableStream &&
558533
body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }),
559534
...(body && { body }),
535+
...((this.fetchOptions as any) ?? {}),
536+
...((options.fetchOptions as any) ?? {}),
560537
};
561538

562539
return { req, url, timeout };

src/index.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@
22

33
export { Stainless as default } from './client';
44

5-
export {
6-
multipartFormRequestOptions,
7-
maybeMultipartFormRequestOptions,
8-
type Uploadable,
9-
createForm,
10-
toFile,
11-
} from './uploads';
5+
export { type Uploadable, toFile } from './uploads';
126
export { APIPromise } from './api-promise';
137
export { Stainless, type ClientOptions } from './client';
148
export {

src/internal/builtin-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3-
export type Fetch = typeof fetch;
3+
export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
44

55
/**
66
* An alias to the builtin `RequestInit` type so we can

src/internal/request-options.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
import { NullableHeaders } from './headers';
44

5-
import type { Agent } from './shims';
65
import type { BodyInit } from './builtin-types';
76
import { isEmptyObj, hasOwn } from './utils/values';
8-
import type { HTTPMethod, KeysEnum } from './types';
7+
import type { HTTPMethod, KeysEnum, MergedRequestInit } from './types';
98
import { type HeadersLike } from './headers';
109

1110
export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string };
@@ -19,7 +18,7 @@ export type RequestOptions = {
1918
maxRetries?: number;
2019
stream?: boolean | undefined;
2120
timeout?: number;
22-
httpAgent?: Agent;
21+
fetchOptions?: MergedRequestInit;
2322
signal?: AbortSignal | undefined | null;
2423
idempotencyKey?: string;
2524

@@ -39,7 +38,7 @@ const requestOptionsKeys: KeysEnum<RequestOptions> = {
3938
maxRetries: true,
4039
stream: true,
4140
timeout: true,
42-
httpAgent: true,
41+
fetchOptions: true,
4342
signal: true,
4443
idempotencyKey: true,
4544

src/internal/shims.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,6 @@
1010
import { type Fetch } from './builtin-types';
1111
import { type ReadableStream } from './shim-types';
1212

13-
/**
14-
* A minimal copy of the `Agent` type from `undici-types` so we can
15-
* use it in the `ClientOptions` type.
16-
*
17-
* https://nodejs.org/api/http.html#class-httpagent
18-
*/
19-
export interface Agent {
20-
dispatch(options: any, handler: any): boolean;
21-
closed: boolean;
22-
destroyed: boolean;
23-
}
24-
2513
export function getDefaultFetch(): Fetch {
2614
if (typeof fetch !== 'undefined') {
2715
return fetch;
@@ -122,3 +110,36 @@ export function ReadableStreamFrom<T>(iterable: Iterable<T> | AsyncIterable<T>):
122110
},
123111
});
124112
}
113+
114+
/**
115+
* Most browsers don't yet have async iterable support for ReadableStream,
116+
* and Node has a very different way of reading bytes from its "ReadableStream".
117+
*
118+
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
119+
*/
120+
export function ReadableStreamToAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
121+
if (stream[Symbol.asyncIterator]) return stream;
122+
123+
const reader = stream.getReader();
124+
return {
125+
async next() {
126+
try {
127+
const result = await reader.read();
128+
if (result?.done) reader.releaseLock(); // release lock when stream becomes closed
129+
return result;
130+
} catch (e) {
131+
reader.releaseLock(); // release lock when stream becomes errored
132+
throw e;
133+
}
134+
},
135+
async return() {
136+
const cancelPromise = reader.cancel();
137+
reader.releaseLock();
138+
await cancelPromise;
139+
return { done: true, value: undefined };
140+
},
141+
[Symbol.asyncIterator]() {
142+
return this;
143+
},
144+
};
145+
}

0 commit comments

Comments
 (0)