Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .fern/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"cliVersion": "0.113.1",
"generatorName": "fernapi/fern-typescript-sdk",
"generatorVersion": "3.28.6"
"generatorVersion": "3.28.11"
}
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import { FernAutopilotTestApiClient } from "";
const client = new FernAutopilotTestApiClient({ environment: "YOUR_BASE_URL" });
await client.imdb.createMovie({
title: "title",
rating: 1.1
rating: 1.1,
metadata: "metadata",
more_metadata: "more_metadata",
rank: 1
});
```

Expand Down
13 changes: 13 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## 2.1.0 - 2025-11-25
* chore: update dependencies and improve API structure
* Update the Fern TypeScript SDK generator from 3.28.6 to 3.28.11 and enhance the movie API with additional metadata fields. This includes improvements to the HTTP client implementation, test infrastructure, and documentation updates.
* Key changes:
* Update Fern generator version from 3.28.6 to 3.28.11
* Add metadata, more_metadata, and rank fields to CreateMovieRequest
* Remove description field and add rank field to Movie interface
* Enhance HTTP client with Headers class support for improved header handling
* Add custom test matchers for header validation
* Update package dependencies and lockfile
* Improve code examples in README and API documentation
* 🌿 Generated with Fern

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "",
"version": "2.0.0",
"version": "2.1.0",
"private": false,
"repository": "github:fern-demo/autopilot-typescript-sdk",
"type": "commonjs",
Expand Down
266 changes: 133 additions & 133 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ Add a movie to the database
```typescript
await client.imdb.createMovie({
title: "title",
rating: 1.1
rating: 1.1,
metadata: "metadata",
more_metadata: "more_metadata",
rank: 1
});

```
Expand Down
4 changes: 2 additions & 2 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export class FernAutopilotTestApiClient {
{
"X-Fern-Language": "JavaScript",
"X-Fern-SDK-Name": "",
"X-Fern-SDK-Version": "2.0.0",
"User-Agent": "/2.0.0",
"X-Fern-SDK-Version": "2.1.0",
"User-Agent": "/2.1.0",
"X-Fern-Runtime": core.RUNTIME.type,
"X-Fern-Runtime-Version": core.RUNTIME.version,
},
Expand Down
5 changes: 4 additions & 1 deletion src/api/resources/imdb/client/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export class Imdb {
* @example
* await client.imdb.createMovie({
* title: "title",
* rating: 1.1
* rating: 1.1,
* metadata: "metadata",
* more_metadata: "more_metadata",
* rank: 1
* })
*/
public createMovie(
Expand Down
3 changes: 3 additions & 0 deletions src/api/resources/imdb/types/CreateMovieRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
export interface CreateMovieRequest {
title: string;
rating: number;
metadata: string;
more_metadata: string;
rank: number;
}
2 changes: 1 addition & 1 deletion src/api/resources/imdb/types/Movie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export interface Movie {
title: string;
/** The rating scale out of ten stars */
rating: number;
description: string;
metadata: string;
rank: number;
}
22 changes: 14 additions & 8 deletions src/core/fetcher/Fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getErrorResponseBody } from "./getErrorResponseBody.js";
import { getFetchFn } from "./getFetchFn.js";
import { getRequestBody } from "./getRequestBody.js";
import { getResponseBody } from "./getResponseBody.js";
import { Headers } from "./Headers.js";
import { makeRequest } from "./makeRequest.js";
import { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js";
import { requestWithRetries } from "./requestWithRetries.js";
Expand Down Expand Up @@ -77,9 +78,9 @@ const SENSITIVE_HEADERS = new Set([
"x-access-token",
]);

function redactHeaders(headers: Record<string, string>): Record<string, string> {
function redactHeaders(headers: Headers | Record<string, string>): Record<string, string> {
const filtered: Record<string, string> = {};
for (const [key, value] of Object.entries(headers)) {
for (const [key, value] of headers instanceof Headers ? headers.entries() : Object.entries(headers)) {
if (SENSITIVE_HEADERS.has(key.toLowerCase())) {
filtered[key] = "[REDACTED]";
} else {
Expand Down Expand Up @@ -207,10 +208,15 @@ function redactUrl(url: string): string {
return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd);
}

async function getHeaders(args: Fetcher.Args): Promise<Record<string, string>> {
const newHeaders: Record<string, string> = {};
async function getHeaders(args: Fetcher.Args): Promise<Headers> {
const newHeaders: Headers = new Headers();

newHeaders.set(
"Accept",
args.responseType === "json" ? "application/json" : args.responseType === "text" ? "text/plain" : "*/*",
);
if (args.body !== undefined && args.contentType != null) {
newHeaders["Content-Type"] = args.contentType;
newHeaders.set("Content-Type", args.contentType);
}

if (args.headers == null) {
Expand All @@ -220,13 +226,13 @@ async function getHeaders(args: Fetcher.Args): Promise<Record<string, string>> {
for (const [key, value] of Object.entries(args.headers)) {
const result = await EndpointSupplier.get(value, { endpointMetadata: args.endpointMetadata ?? {} });
if (typeof result === "string") {
newHeaders[key] = result;
newHeaders.set(key, result);
continue;
}
if (result == null) {
continue;
}
newHeaders[key] = `${result}`;
newHeaders.set(key, `${result}`);
}
return newHeaders;
}
Expand Down Expand Up @@ -275,7 +281,7 @@ export async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIR
method: args.method,
url: redactUrl(url),
statusCode: response.status,
responseHeaders: redactHeaders(Object.fromEntries(response.headers.entries())),
responseHeaders: redactHeaders(response.headers),
};
logger.debug("HTTP request succeeded", metadata);
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/fetcher/makeRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const makeRequest = async (
fetchFn: (url: string, init: RequestInit) => Promise<Response>,
url: string,
method: string,
headers: Record<string, string>,
headers: Headers | Record<string, string>,
requestBody: BodyInit | undefined,
timeoutMs?: number,
abortSignal?: AbortSignal,
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const SDK_VERSION = "2.0.0";
export const SDK_VERSION = "2.1.0";
80 changes: 80 additions & 0 deletions tests/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect } from "vitest";

interface CustomMatchers<R = unknown> {
toContainHeaders(expectedHeaders: Record<string, string>): R;
}

declare module "vitest" {
interface Assertion<T = any> extends CustomMatchers<T> {}
interface AsymmetricMatchersContaining extends CustomMatchers {}
}

expect.extend({
toContainHeaders(actual: unknown, expectedHeaders: Record<string, string>) {
const isHeaders = actual instanceof Headers;
const isPlainObject = typeof actual === "object" && actual !== null && !Array.isArray(actual);

if (!isHeaders && !isPlainObject) {
throw new TypeError("Received value must be an instance of Headers or a plain object!");
}

if (typeof expectedHeaders !== "object" || expectedHeaders === null || Array.isArray(expectedHeaders)) {
throw new TypeError("Expected headers must be a plain object!");
}

const missingHeaders: string[] = [];
const mismatchedHeaders: Array<{ key: string; expected: string; actual: string | null }> = [];

for (const [key, value] of Object.entries(expectedHeaders)) {
let actualValue: string | null = null;

if (isHeaders) {
// Headers.get() is already case-insensitive
actualValue = (actual as Headers).get(key);
} else {
// For plain objects, do case-insensitive lookup
const actualObj = actual as Record<string, string>;
const lowerKey = key.toLowerCase();
const foundKey = Object.keys(actualObj).find((k) => k.toLowerCase() === lowerKey);
actualValue = foundKey ? actualObj[foundKey] : null;
}

if (actualValue === null || actualValue === undefined) {
missingHeaders.push(key);
} else if (actualValue !== value) {
mismatchedHeaders.push({ key, expected: value, actual: actualValue });
}
}

const pass = missingHeaders.length === 0 && mismatchedHeaders.length === 0;

const actualType = isHeaders ? "Headers" : "object";

if (pass) {
return {
message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`,
pass: true,
};
} else {
const messages: string[] = [];

if (missingHeaders.length > 0) {
messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`);
}

if (mismatchedHeaders.length > 0) {
const mismatches = mismatchedHeaders.map(
({ key, expected, actual }) =>
`${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`,
);
messages.push(mismatches.join("\n"));
}

return {
message: () =>
`expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`,
pass: false,
};
}
},
});
12 changes: 6 additions & 6 deletions tests/unit/fetcher/Fetcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("Test fetcherImpl", () => {
"https://httpbin.org/post",
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({ "X-Test": "x-test-header" }),
headers: expect.toContainHeaders({ "X-Test": "x-test-header" }),
body: JSON.stringify({ data: "test" }),
}),
);
Expand Down Expand Up @@ -66,7 +66,7 @@ describe("Test fetcherImpl", () => {
url,
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({ "X-Test": "x-test-header" }),
headers: expect.toContainHeaders({ "X-Test": "x-test-header" }),
body: expect.any(fs.ReadStream),
}),
);
Expand Down Expand Up @@ -102,7 +102,7 @@ describe("Test fetcherImpl", () => {
url,
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({ "X-Test": "x-test-header" }),
headers: expect.toContainHeaders({ "X-Test": "x-test-header" }),
}),
);
expect(result.ok).toBe(true);
Expand Down Expand Up @@ -148,7 +148,7 @@ describe("Test fetcherImpl", () => {
url,
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({ "X-Test": "x-test-header" }),
headers: expect.toContainHeaders({ "X-Test": "x-test-header" }),
}),
);
expect(result.ok).toBe(true);
Expand Down Expand Up @@ -194,7 +194,7 @@ describe("Test fetcherImpl", () => {
url,
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({ "X-Test": "x-test-header" }),
headers: expect.toContainHeaders({ "X-Test": "x-test-header" }),
}),
);
expect(result.ok).toBe(true);
Expand Down Expand Up @@ -238,7 +238,7 @@ describe("Test fetcherImpl", () => {
url,
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({ "X-Test": "x-test-header" }),
headers: expect.toContainHeaders({ "X-Test": "x-test-header" }),
}),
);
expect(result.ok).toBe(true);
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/fetcher/logging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe("Fetcher Logging Integration", () => {
expect.objectContaining({
method: "POST",
url: "https://example.com/api",
headers: expect.objectContaining({
headers: expect.toContainHeaders({
"Content-Type": "application/json",
}),
hasBody: true,
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/fetcher/makeRequest.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Mock } from "vitest";
import { makeRequest } from "../../../src/core/fetcher/makeRequest";

describe("Test makeRequest", () => {
Expand All @@ -6,7 +7,7 @@ describe("Test makeRequest", () => {
const mockHeaders = { "Content-Type": "application/json" };
const mockBody = JSON.stringify({ key: "value" });

let mockFetch: import("vitest").Mock;
let mockFetch: Mock;

beforeEach(() => {
mockFetch = vi.fn();
Expand Down
Loading