Skip to content
Merged
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
9 changes: 2 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ on:
branches:
- main

env:
GITHUB_TOKEN: ${{ secrets.OPENAPI_TS_BOT_GITHUB_TOKEN }}

permissions:
id-token: write # Required for OIDC
contents: write
Expand All @@ -16,10 +13,6 @@ permissions:
jobs:
changelog:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'openapi-ts' }}
permissions:
contents: write
pull-requests: write
steps:
- name: Git setup
uses: actions/checkout@v6
Expand All @@ -45,3 +38,5 @@ jobs:
publish: pnpm exec changeset publish
commit: "[ci] release"
title: "[ci] release"
env:
GITHUB_TOKEN: ${{ secrets.OPENAPI_TS_BOT_GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"root": true,
"files": {
"includes": ["**", "!**/dist", "!**/package.json"]
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "0.18.2",
"@biomejs/biome": "2.2.6",
"@biomejs/biome": "2.3.14",
"@changesets/changelog-github": "0.5.2",
"@changesets/cli": "2.29.8",
"@playwright/test": "1.56.0",
"@size-limit/preset-small-lib": "11.2.0",
"@size-limit/preset-small-lib": "12.0.0",
"@types/node": "25.2.2",
"prettier": "3.6.2",
"size-limit": "11.2.0",
"turbo": "2.5.8",
"prettier": "3.8.1",
"size-limit": "12.0.0",
"turbo": "2.8.3",
"typescript": "catalog:",
"unbuild": "3.6.1",
"vitest": "4.0.18"
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-fetch/biome.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"root": false,
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"extends": "//",
"files": {
"includes": ["src/**", "test/**", "!**/examples/**", "!test/**/schemas/**", "!test/bench/**/*.min.js"]
Expand Down
38 changes: 18 additions & 20 deletions packages/openapi-fetch/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,12 @@ export type ParamsOption<T> = T extends {
: { params: T["parameters"] }
: DefaultParamsOption;

export type RequestBodyOption<T> = OperationRequestBodyContent<T> extends never
? { body?: never }
: IsOperationRequestBodyOptional<T> extends true
? { body?: OperationRequestBodyContent<T> }
: { body: OperationRequestBodyContent<T> };
export type RequestBodyOption<T> =
OperationRequestBodyContent<T> extends never
? { body?: never }
: IsOperationRequestBodyOptional<T> extends true
? { body?: OperationRequestBodyContent<T> }
: { body: OperationRequestBodyContent<T> };

export type FetchOptions<T> = RequestOptions<T> & Omit<RequestInit, "body" | "headers">;

Expand Down Expand Up @@ -177,19 +178,17 @@ export type Middleware =
};

/** This type helper makes the 2nd function param required if params/requestBody are required; otherwise, optional */
export type MaybeOptionalInit<Params, Location extends keyof Params> = RequiredKeysOf<
FetchOptions<FilterKeys<Params, Location>>
> extends never
? FetchOptions<FilterKeys<Params, Location>> | undefined
: FetchOptions<FilterKeys<Params, Location>>;
export type MaybeOptionalInit<Params, Location extends keyof Params> =
RequiredKeysOf<FetchOptions<FilterKeys<Params, Location>>> extends never
? FetchOptions<FilterKeys<Params, Location>> | undefined
: FetchOptions<FilterKeys<Params, Location>>;

// The final init param to accept.
// - Determines if the param is optional or not.
// - Performs arbitrary [key: string] addition.
// Note: the addition MUST happen after all the inference happens (otherwise TS can’t infer if init is required or not).
type InitParam<Init> = RequiredKeysOf<Init> extends never
? [(Init & { [key: string]: unknown })?]
: [Init & { [key: string]: unknown }];
type InitParam<Init> =
RequiredKeysOf<Init> extends never ? [(Init & { [key: string]: unknown })?] : [Init & { [key: string]: unknown }];

export type ClientMethod<
Paths extends Record<string, Record<HttpMethod, {}>>,
Expand Down Expand Up @@ -240,19 +239,18 @@ export interface Client<Paths extends {}, Media extends MediaType = MediaType> {
eject(...middleware: Middleware[]): void;
}

export type ClientPathsWithMethod<
CreatedClient extends Client<any, any>,
Method extends HttpMethod,
> = CreatedClient extends Client<infer Paths, infer _Media> ? PathsWithMethod<Paths, Method> : never;
export type ClientPathsWithMethod<CreatedClient extends Client<any, any>, Method extends HttpMethod> =
CreatedClient extends Client<infer Paths, infer _Media> ? PathsWithMethod<Paths, Method> : never;

export type MethodResponse<
CreatedClient extends Client<any, any>,
Method extends HttpMethod,
Path extends ClientPathsWithMethod<CreatedClient, Method>,
Options = {},
> = CreatedClient extends Client<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
: never;
> =
CreatedClient extends Client<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
: never;

export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
clientOptions?: ClientOptions,
Expand Down
127 changes: 61 additions & 66 deletions packages/openapi-fetch/test/common/create-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,75 +98,70 @@ describe("createClient options", () => {
BODIES.map((body) => [method, body] as const),
);

test.each(METHOD_BODY_COMBINATIONS)(
"implicit default content-type for body-full requests - %s, %j",
async (method, body) => {
const contentType = await fireRequestAndGetContentType({
method,
fetchOptions: { body },
});

expect(contentType).toBe("application/json");
},
);
test.each(
METHOD_BODY_COMBINATIONS,
)("implicit default content-type for body-full requests - %s, %j", async (method, body) => {
const contentType = await fireRequestAndGetContentType({
method,
fetchOptions: { body },
});

test.each(METHOD_BODY_COMBINATIONS)(
"provided default content-type for body-full requests - %s, %j",
async (method, body) => {
const contentType = await fireRequestAndGetContentType({
defaultHeaders: { "content-type": "application/my-json" },
method,
fetchOptions: { body },
});

expect(contentType).toBe("application/my-json");
},
);
expect(contentType).toBe("application/json");
});

test.each(METHOD_BODY_COMBINATIONS)(
"native-fetch default content-type for body-full requests, when default is suppressed - %s, %j",
async (method, body) => {
const contentType = await fireRequestAndGetContentType({
defaultHeaders: { "content-type": null },
method,
fetchOptions: { body },
});
// the fetch implementation won't allow sending a body without content-type,
// and it defaults to `text/plain;charset=UTF-8`, however the actual default value
// is irrelevant and might be flaky across different fetch implementations
// for us, it's important that it's not `application/json`
expect(contentType).not.toBe("application/json");
},
);
test.each(
METHOD_BODY_COMBINATIONS,
)("provided default content-type for body-full requests - %s, %j", async (method, body) => {
const contentType = await fireRequestAndGetContentType({
defaultHeaders: { "content-type": "application/my-json" },
method,
fetchOptions: { body },
});

test.each(METHOD_BODY_COMBINATIONS)(
"specified content-type for body-full requests - %s, %j",
async (method, body) => {
const contentType = await fireRequestAndGetContentType({
method,
fetchOptions: {
body,
headers: { "content-type": "application/my-json" },
},
});

expect(contentType).toBe("application/my-json");
},
);
expect(contentType).toBe("application/my-json");
});

test.each(METHOD_BODY_COMBINATIONS)(
"specified content-type for body-full requests, even when default is suppressed - %s, %j",
async (method, body) => {
const contentType = await fireRequestAndGetContentType({
method,
fetchOptions: {
body,
headers: { "content-type": "application/my-json" },
},
});

expect(contentType).toBe("application/my-json");
},
);
test.each(
METHOD_BODY_COMBINATIONS,
)("native-fetch default content-type for body-full requests, when default is suppressed - %s, %j", async (method, body) => {
const contentType = await fireRequestAndGetContentType({
defaultHeaders: { "content-type": null },
method,
fetchOptions: { body },
});
// the fetch implementation won't allow sending a body without content-type,
// and it defaults to `text/plain;charset=UTF-8`, however the actual default value
// is irrelevant and might be flaky across different fetch implementations
// for us, it's important that it's not `application/json`
expect(contentType).not.toBe("application/json");
});

test.each(
METHOD_BODY_COMBINATIONS,
)("specified content-type for body-full requests - %s, %j", async (method, body) => {
const contentType = await fireRequestAndGetContentType({
method,
fetchOptions: {
body,
headers: { "content-type": "application/my-json" },
},
});

expect(contentType).toBe("application/my-json");
});

test.each(
METHOD_BODY_COMBINATIONS,
)("specified content-type for body-full requests, even when default is suppressed - %s, %j", async (method, body) => {
const contentType = await fireRequestAndGetContentType({
method,
fetchOptions: {
body,
headers: { "content-type": "application/my-json" },
},
});

expect(contentType).toBe("application/my-json");
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ test("baseUrl can be overridden", async () => {
});

test("auth header", async () => {
let accessToken: string | undefined = undefined;
let accessToken: string | undefined;
const authMiddleware: Middleware = {
async onRequest({ request }) {
if (accessToken) {
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-react-query/biome.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"root": false,
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"extends": "//",
"files": {
"includes": ["**", "!dist/**", "!test/fixtures/**"]
Expand Down
7 changes: 4 additions & 3 deletions packages/openapi-react-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,10 @@ export type MethodResponse<
? PathsWithMethod<Paths, Method>
: never,
Options = object,
> = CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
: never;
> =
CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
: never;

// TODO: Add the ability to bring queryClient as argument
export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-typescript-helpers/biome.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"root": false,
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"extends": "//",
"files": {
"includes": ["**/*"]
Expand Down
14 changes: 8 additions & 6 deletions packages/openapi-typescript-helpers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,16 @@ type PickRequestBody<T> = "requestBody" extends keyof T ? Pick<T, "requestBody">
export type IsOperationRequestBodyOptional<T> = RequiredKeysOf<PickRequestBody<T>> extends never ? true : false;

/** Internal helper used in OperationRequestBodyContent */
export type OperationRequestBodyMediaContent<T> = IsOperationRequestBodyOptional<T> extends true
? ResponseContent<NonNullable<OperationRequestBody<T>>> | undefined
: ResponseContent<OperationRequestBody<T>>;
export type OperationRequestBodyMediaContent<T> =
IsOperationRequestBodyOptional<T> extends true
? ResponseContent<NonNullable<OperationRequestBody<T>>> | undefined
: ResponseContent<OperationRequestBody<T>>;

/** Return first `content` from a Request Object Mapping, allowing any media type */
export type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaContent<T>, MediaType> extends never
? FilterKeys<NonNullable<OperationRequestBodyMediaContent<T>>, MediaType> | undefined
: FilterKeys<OperationRequestBodyMediaContent<T>, MediaType>;
export type OperationRequestBodyContent<T> =
FilterKeys<OperationRequestBodyMediaContent<T>, MediaType> extends never
? FilterKeys<NonNullable<OperationRequestBodyMediaContent<T>>, MediaType> | undefined
: FilterKeys<OperationRequestBodyMediaContent<T>, MediaType>;

/** Return all 2XX responses from a Response Object Map */
export type SuccessResponse<
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-typescript/biome.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"root": false,
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"extends": "//",
"files": {
"includes": ["bin/**", "!examples/**", "src/**", "test/**", "!**/fixtures/**/*"]
Expand Down
4 changes: 1 addition & 3 deletions packages/openapi-typescript/scripts/update-examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ async function generateSchemas() {
const cwd =
process.platform === "win32"
? // execa/cross-spawn can not handle URL objects on Windows, so convert it to string and cut away the protocol
rootCWD
.toString()
.slice("file:///".length)
rootCWD.toString().slice("file:///".length)
: rootCWD;

try {
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-typescript/src/lib/redoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export async function parseSchema(schema: unknown, { absoluteRef, resolver }: Pa

function _processProblems(problems: NormalizedProblem[], options: { silent: boolean }) {
if (problems.length) {
let errorMessage: string | undefined = undefined;
let errorMessage: string | undefined;
for (const problem of problems) {
const problemLocation = problem.location?.[0].pointer;
const problemMessage = problemLocation ? `${problem.message} at ${problemLocation}` : problem.message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export function transformParametersArray(
if (resolved?.in !== paramIn) {
continue;
}
let optional: ts.QuestionToken | undefined = undefined;
let optional: ts.QuestionToken | undefined;
if (paramIn !== "path" && !(resolved as ParameterObject).required) {
optional = QUESTION_TOKEN;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export function transformSchemaObjectWithComposition(
}

// compile final type
let finalType: ts.TypeNode | undefined = undefined;
let finalType: ts.TypeNode | undefined;

// core + allOf: intersect
const coreObjectType = transformSchemaObjectCore(schemaObject, options);
Expand Down
Loading