Skip to content

Commit a115b7c

Browse files
release: 1.2.2 (#59)
* chore(client): improve path param validation * chore: add docs to RequestOptions type * chore: make some internal functions async * release: 1.2.2 --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 717d17e commit a115b7c

11 files changed

Lines changed: 282 additions & 45 deletions

File tree

.release-please-manifest.json

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

CHANGELOG.md

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

3+
## 1.2.2 (2025-07-10)
4+
5+
Full Changelog: [v1.2.1...v1.2.2](https://github.com/GRID-is/api-sdk-ts/compare/v1.2.1...v1.2.2)
6+
7+
### Chores
8+
9+
* add docs to RequestOptions type ([a197e2f](https://github.com/GRID-is/api-sdk-ts/commit/a197e2f7d9053be0bb303005ef5da2b620cd21a2))
10+
* **client:** improve path param validation ([e6d7ff7](https://github.com/GRID-is/api-sdk-ts/commit/e6d7ff72a2de23b52636a752093ba9362543d58a))
11+
* make some internal functions async ([4f16312](https://github.com/GRID-is/api-sdk-ts/commit/4f163129b3d74e9662e609a9e0191bc2778ec659))
12+
313
## 1.2.1 (2025-06-28)
414

515
Full Changelog: [v1.2.0...v1.2.1](https://github.com/GRID-is/api-sdk-ts/compare/v1.2.0...v1.2.1)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@grid-is/api",
3-
"version": "1.2.1",
3+
"version": "1.2.2",
44
"description": "The official TypeScript library for the Grid API",
55
"author": "Grid <info@grid.is>",
66
"types": "dist/index.d.ts",

src/client.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export interface ClientOptions {
7373
*
7474
* Note that request timeouts are retried by default, so in a worst-case scenario you may wait
7575
* much longer than this timeout before the promise succeeds or fails.
76+
*
77+
* @unit milliseconds
7678
*/
7779
timeout?: number | undefined;
7880
/**
@@ -198,7 +200,7 @@ export class Grid {
198200
* Create a new client instance re-using the same options given to the current client with optional overriding.
199201
*/
200202
withOptions(options: Partial<ClientOptions>): this {
201-
return new (this.constructor as any as new (props: ClientOptions) => typeof this)({
203+
const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({
202204
...this._options,
203205
baseURL: this.baseURL,
204206
maxRetries: this.maxRetries,
@@ -210,6 +212,7 @@ export class Grid {
210212
apiKey: this.apiKey,
211213
...options,
212214
});
215+
return client;
213216
}
214217

215218
/**
@@ -227,7 +230,7 @@ export class Grid {
227230
return;
228231
}
229232

230-
protected authHeaders(opts: FinalRequestOptions): NullableHeaders | undefined {
233+
protected async authHeaders(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> {
231234
return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]);
232235
}
233236

@@ -359,7 +362,9 @@ export class Grid {
359362

360363
await this.prepareOptions(options);
361364

362-
const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining });
365+
const { req, url, timeout } = await this.buildRequest(options, {
366+
retryCount: maxRetries - retriesRemaining,
367+
});
363368

364369
await this.prepareRequest(req, { url, options });
365370

@@ -437,7 +442,7 @@ export class Grid {
437442
} with status ${response.status} in ${headersTime - startTime}ms`;
438443

439444
if (!response.ok) {
440-
const shouldRetry = this.shouldRetry(response);
445+
const shouldRetry = await this.shouldRetry(response);
441446
if (retriesRemaining && shouldRetry) {
442447
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
443448

@@ -555,7 +560,7 @@ export class Grid {
555560
}
556561
}
557562

558-
private shouldRetry(response: Response): boolean {
563+
private async shouldRetry(response: Response): Promise<boolean> {
559564
// Note this is not a standard header.
560565
const shouldRetryHeader = response.headers.get('x-should-retry');
561566

@@ -632,18 +637,18 @@ export class Grid {
632637
return sleepSeconds * jitter * 1000;
633638
}
634639

635-
buildRequest(
640+
async buildRequest(
636641
inputOptions: FinalRequestOptions,
637642
{ retryCount = 0 }: { retryCount?: number } = {},
638-
): { req: FinalizedRequestInit; url: string; timeout: number } {
643+
): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> {
639644
const options = { ...inputOptions };
640645
const { method, path, query, defaultBaseURL } = options;
641646

642647
const url = this.buildURL(path!, query as Record<string, unknown>, defaultBaseURL);
643648
if ('timeout' in options) validatePositiveInteger('timeout', options.timeout);
644649
options.timeout = options.timeout ?? this.timeout;
645650
const { bodyHeaders, body } = this.buildBody({ options });
646-
const reqHeaders = this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount });
651+
const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount });
647652

648653
const req: FinalizedRequestInit = {
649654
method,
@@ -659,7 +664,7 @@ export class Grid {
659664
return { req, url, timeout: options.timeout };
660665
}
661666

662-
private buildHeaders({
667+
private async buildHeaders({
663668
options,
664669
method,
665670
bodyHeaders,
@@ -669,7 +674,7 @@ export class Grid {
669674
method: HTTPMethod;
670675
bodyHeaders: HeadersLike;
671676
retryCount: number;
672-
}): Headers {
677+
}): Promise<Headers> {
673678
let idempotencyHeaders: HeadersLike = {};
674679
if (this.idempotencyHeader && method !== 'get') {
675680
if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey();
@@ -686,7 +691,7 @@ export class Grid {
686691
...getPlatformHeaders(),
687692
'X-Client-Name': 'api-sdk',
688693
},
689-
this.authHeaders(options),
694+
await this.authHeaders(options),
690695
this._options.defaultHeaders,
691696
bodyHeaders,
692697
options.headers,

src/internal/request-options.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,70 @@ import { type HeadersLike } from './headers';
99
export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string };
1010

1111
export type RequestOptions = {
12+
/**
13+
* The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete').
14+
*/
1215
method?: HTTPMethod;
16+
17+
/**
18+
* The URL path for the request.
19+
*
20+
* @example "/v1/foo"
21+
*/
1322
path?: string;
23+
24+
/**
25+
* Query parameters to include in the request URL.
26+
*/
1427
query?: object | undefined | null;
28+
29+
/**
30+
* The request body. Can be a string, JSON object, FormData, or other supported types.
31+
*/
1532
body?: unknown;
33+
34+
/**
35+
* HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples.
36+
*/
1637
headers?: HeadersLike;
38+
39+
/**
40+
* The maximum number of times that the client will retry a request in case of a
41+
* temporary failure, like a network error or a 5XX error from the server.
42+
*
43+
* @default 2
44+
*/
1745
maxRetries?: number;
46+
1847
stream?: boolean | undefined;
48+
49+
/**
50+
* The maximum amount of time (in milliseconds) that the client should wait for a response
51+
* from the server before timing out a single request.
52+
*
53+
* @unit milliseconds
54+
*/
1955
timeout?: number;
56+
57+
/**
58+
* Additional `RequestInit` options to be passed to the underlying `fetch` call.
59+
* These options will be merged with the client's default fetch options.
60+
*/
2061
fetchOptions?: MergedRequestInit;
62+
63+
/**
64+
* An AbortSignal that can be used to cancel the request.
65+
*/
2166
signal?: AbortSignal | undefined | null;
67+
68+
/**
69+
* A unique key for this request to enable idempotency.
70+
*/
2271
idempotencyKey?: string;
72+
73+
/**
74+
* Override the default base URL for this specific request.
75+
*/
2376
defaultBaseURL?: string | undefined;
2477

2578
__binaryResponse?: boolean | undefined;

src/internal/uploads.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export const multipartFormRequestOptions = async (
9090
return { ...opts, body: await createForm(opts.body, fetch) };
9191
};
9292

93-
const supportsFormDataMap = /** @__PURE__ */ new WeakMap<Fetch, Promise<boolean>>();
93+
const supportsFormDataMap = /* @__PURE__ */ new WeakMap<Fetch, Promise<boolean>>();
9494

9595
/**
9696
* node-fetch doesn't support the global FormData object in recent node versions. Instead of sending

src/internal/utils/log.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const noopLogger = {
5858
debug: noop,
5959
};
6060

61-
let cachedLoggers = /** @__PURE__ */ new WeakMap<Logger, [LogLevel, Logger]>();
61+
let cachedLoggers = /* @__PURE__ */ new WeakMap<Logger, [LogLevel, Logger]>();
6262

6363
export function loggerFor(client: Grid): Logger {
6464
const logger = client.logger;

src/internal/utils/path.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,43 @@ export function encodeURIPath(str: string) {
1212
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
1313
}
1414

15+
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
16+
1517
export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
1618
function path(statics: readonly string[], ...params: readonly unknown[]): string {
1719
// If there are no params, no processing is needed.
1820
if (statics.length === 1) return statics[0]!;
1921

2022
let postPath = false;
23+
const invalidSegments = [];
2124
const path = statics.reduce((previousValue, currentValue, index) => {
2225
if (/[?#]/.test(currentValue)) {
2326
postPath = true;
2427
}
25-
return (
26-
previousValue +
27-
currentValue +
28-
(index === params.length ? '' : (postPath ? encodeURIComponent : pathEncoder)(String(params[index])))
29-
);
28+
const value = params[index];
29+
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
30+
if (
31+
index !== params.length &&
32+
(value == null ||
33+
(typeof value === 'object' &&
34+
// handle values from other realms
35+
value.toString ===
36+
Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY)
37+
?.toString))
38+
) {
39+
encoded = value + '';
40+
invalidSegments.push({
41+
start: previousValue.length + currentValue.length,
42+
length: encoded.length,
43+
error: `Value of type ${Object.prototype.toString
44+
.call(value)
45+
.slice(8, -1)} is not a valid path parameter`,
46+
});
47+
}
48+
return previousValue + currentValue + (index === params.length ? '' : encoded);
3049
}, '');
3150

3251
const pathOnly = path.split(/[?#]/, 1)[0]!;
33-
const invalidSegments = [];
3452
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
3553
let match;
3654

@@ -39,9 +57,12 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
3957
invalidSegments.push({
4058
start: match.index,
4159
length: match[0].length,
60+
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
4261
});
4362
}
4463

64+
invalidSegments.sort((a, b) => a.start - b.start);
65+
4566
if (invalidSegments.length > 0) {
4667
let lastEnd = 0;
4768
const underline = invalidSegments.reduce((acc, segment) => {
@@ -51,7 +72,11 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
5172
return acc + spaces + arrows;
5273
}, '');
5374

54-
throw new GridError(`Path parameters result in path with invalid segments:\n${path}\n${underline}`);
75+
throw new GridError(
76+
`Path parameters result in path with invalid segments:\n${invalidSegments
77+
.map((e) => e.error)
78+
.join('\n')}\n${path}\n${underline}`,
79+
);
5580
}
5681

5782
return path;

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const VERSION = '1.2.1'; // x-release-please-version
1+
export const VERSION = '1.2.2'; // x-release-please-version

0 commit comments

Comments
 (0)