Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/little-items-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"thirdweb": minor
"@thirdweb-dev/insight": minor
"@thirdweb-dev/engine": minor
"@thirdweb-dev/nebula": minor
---

update hey-api version to 0.76.0
5 changes: 1 addition & 4 deletions packages/engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,9 @@
"dist/*",
"src/*"
],
"dependencies": {
"@hey-api/client-fetch": "0.10.0"
},
"devDependencies": {
"@biomejs/biome": "2.0.4",
"@hey-api/openapi-ts": "0.72.1",
"@hey-api/openapi-ts": "0.76.0",
"rimraf": "6.0.1",
"tslib": "^2.8.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/src/client/client.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
createClient,
createConfig,
type ClientOptions as DefaultClientOptions,
} from "@hey-api/client-fetch";
} from "./client/index.js";
import type { ClientOptions } from "./types.gen.js";

/**
Expand Down
189 changes: 189 additions & 0 deletions packages/engine/src/client/client/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import type { Client, Config, RequestOptions } from "./types.js";
import {
buildUrl,
createConfig,
createInterceptors,
getParseAs,
mergeConfigs,
mergeHeaders,
setAuthParams,
} from "./utils.js";

type ReqInit = Omit<RequestInit, "body" | "headers"> & {
body?: any;
headers: ReturnType<typeof mergeHeaders>;
};

export const createClient = (config: Config = {}): Client => {
let _config = mergeConfigs(createConfig(), config);

const getConfig = (): Config => ({ ..._config });

const setConfig = (config: Config): Config => {
_config = mergeConfigs(_config, config);
return getConfig();
};

const interceptors = createInterceptors<
Request,
Response,
unknown,
RequestOptions
>();

const request: Client["request"] = async (options) => {
const opts = {
..._config,
...options,
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
headers: mergeHeaders(_config.headers, options.headers),
};

if (opts.security) {
await setAuthParams({
...opts,
security: opts.security,
});
}

if (opts.body && opts.bodySerializer) {
opts.body = opts.bodySerializer(opts.body);
}

// remove Content-Type header if body is empty to avoid sending invalid requests
if (opts.body === undefined || opts.body === "") {
opts.headers.delete("Content-Type");
}

const url = buildUrl(opts);
const requestInit: ReqInit = {
redirect: "follow",
...opts,
};

let request = new Request(url, requestInit);

for (const fn of interceptors.request._fns) {
if (fn) {
request = await fn(request, opts);
}
}

// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = opts.fetch!;
let response = await _fetch(request);

for (const fn of interceptors.response._fns) {
if (fn) {
response = await fn(response, request, opts);
}
}

const result = {
request,
response,
};

if (response.ok) {
if (
response.status === 204 ||
response.headers.get("Content-Length") === "0"
) {
return opts.responseStyle === "data"
? {}
: {
data: {},
...result,
};
}

const parseAs =
(opts.parseAs === "auto"
? getParseAs(response.headers.get("Content-Type"))
: opts.parseAs) ?? "json";

let data: any;
switch (parseAs) {
case "arrayBuffer":
case "blob":
case "formData":
case "json":
case "text":
data = await response[parseAs]();
break;
case "stream":
return opts.responseStyle === "data"
? response.body
: {
data: response.body,
...result,
};
}

if (parseAs === "json") {
if (opts.responseValidator) {
await opts.responseValidator(data);
}

if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
}
}

return opts.responseStyle === "data"
? data
: {
data,
...result,
};
}

let error = await response.text();

try {
error = JSON.parse(error);
} catch {
// noop
}

let finalError = error;

for (const fn of interceptors.error._fns) {
if (fn) {
finalError = (await fn(error, response, request, opts)) as string;
}
}

finalError = finalError || ({} as string);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type casting issue with error fallback.

The code casts an empty object as a string, which is incorrect and could lead to runtime type errors.

Apply this diff to fix the type casting:

-    finalError = finalError || ({} as string);
+    finalError = finalError || "";

Alternatively, if you need to preserve object errors:

-    finalError = finalError || ({} as string);
+    finalError = finalError || {};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
finalError = finalError || ({} as string);
finalError = finalError || "";
🤖 Prompt for AI Agents
In packages/engine/src/client/client/client.ts at line 158, the code incorrectly
casts an empty object as a string for the error fallback, which can cause
runtime type errors. Replace the fallback with an empty string literal or a
valid string value instead of casting an object to a string to ensure type
safety and prevent errors.


if (opts.throwOnError) {
throw finalError;
}

// TODO: we probably want to return error and improve types
return opts.responseStyle === "data"
? undefined
: {
error: finalError,
...result,
};
};

return {
buildUrl,
connect: (options) => request({ ...options, method: "CONNECT" }),
delete: (options) => request({ ...options, method: "DELETE" }),
get: (options) => request({ ...options, method: "GET" }),
getConfig,
head: (options) => request({ ...options, method: "HEAD" }),
interceptors,
options: (options) => request({ ...options, method: "OPTIONS" }),
patch: (options) => request({ ...options, method: "PATCH" }),
post: (options) => request({ ...options, method: "POST" }),
put: (options) => request({ ...options, method: "PUT" }),
request,
setConfig,
trace: (options) => request({ ...options, method: "TRACE" }),
};
};
22 changes: 22 additions & 0 deletions packages/engine/src/client/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type { Auth } from "../core/auth.js";
export type { QuerySerializerOptions } from "../core/bodySerializer.js";
export {
formDataBodySerializer,
jsonBodySerializer,
urlSearchParamsBodySerializer,
} from "../core/bodySerializer.js";
export { buildClientParams } from "../core/params.js";
export { createClient } from "./client.js";
export type {
Client,
ClientOptions,
Config,
CreateClientConfig,
Options,
OptionsLegacyParser,
RequestOptions,
RequestResult,
ResponseStyle,
TDataShape,
} from "./types.js";
export { createConfig, mergeHeaders } from "./utils.js";
Loading
Loading