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
4 changes: 3 additions & 1 deletion packages/swr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"build": "tsdown --config-loader unrun",
"dev": "tsdown --config-loader unrun --watch src",
"lint": "eslint .",
"test": "vitest",
"clean": "rimraf .turbo dist",
"nuke": "rimraf .turbo dist node_modules"
},
Expand All @@ -28,7 +29,8 @@
"devDependencies": {
"eslint": "catalog:",
"rimraf": "catalog:",
"tsdown": "catalog:"
"tsdown": "catalog:",
"vitest": "catalog:"
},
"stableVersion": "8.0.0-rc.1"
}
62 changes: 62 additions & 0 deletions packages/swr/src/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { GetterProp } from '@orval/core';
import { GetterPropType } from '@orval/core';
import { describe, expect, it } from 'vitest';

describe('query parameter type extraction', () => {
it('extracts type name from query param GetterProp', () => {
const queryParamProp: GetterProp = {
name: 'params',
definition: 'params: ListPetsParams',
implementation: 'params: ListPetsParams',
default: false,
required: true,
type: GetterPropType.QUERY_PARAM,
};

const props: GetterProp[] = [queryParamProp];
const queryParam = props.find(
(prop) => prop.type === GetterPropType.QUERY_PARAM,
);
const extractedType = queryParam?.definition.split(': ')[1] ?? 'never';

expect(extractedType).toBe('ListPetsParams');
});

it('extracts type name from optional query param', () => {
const queryParamProp: GetterProp = {
name: 'params',
definition: 'params?: GetUsersParams',
implementation: 'params?: GetUsersParams',
default: false,
required: false,
type: GetterPropType.QUERY_PARAM,
};

const props: GetterProp[] = [queryParamProp];
const queryParam = props.find(
(prop) => prop.type === GetterPropType.QUERY_PARAM,
);
const extractedType = queryParam?.definition.split(': ')[1] ?? 'never';

expect(extractedType).toBe('GetUsersParams');
});

it('returns never when no query param exists', () => {
const pathParamProp: GetterProp = {
name: 'id',
definition: 'id: string',
implementation: 'id: string',
default: false,
required: true,
type: GetterPropType.PARAM,
};

const props: GetterProp[] = [pathParamProp];
const queryParam = props.find(
(prop) => prop.type === GetterPropType.QUERY_PARAM,
);
const extractedType = queryParam?.definition.split(': ')[1] ?? 'never';

expect(extractedType).toBe('never');
});
});
22 changes: 22 additions & 0 deletions packages/swr/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest';

import { builder } from './index';

describe('swr builder', () => {
it('returns a valid builder function', () => {
const result = builder();
expect(result).toBeDefined();
expect(typeof result).toBe('function');
});

it('builder returns client builder with required methods', () => {
const result = builder()();
expect(result).toBeDefined();
expect(result.client).toBeDefined();
expect(result.dependencies).toBeDefined();
expect(result.header).toBeDefined();
expect(typeof result.client).toBe('function');
expect(typeof result.dependencies).toBe('function');
expect(typeof result.header).toBe('function');
});
});
84 changes: 75 additions & 9 deletions packages/swr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ const generateSwrImplementation = ({
props,
doc,
httpClient,
pathOnlyParams,
headerOnlyParams,
hasQueryParams,
queryParamType,
}: {
isRequestOptions: boolean;
operationName: string;
Expand All @@ -176,6 +180,10 @@ const generateSwrImplementation = ({
swrOptions: SwrOptions;
doc?: string;
httpClient: OutputHttpClient;
pathOnlyParams: string;
headerOnlyParams: string;
hasQueryParams: boolean;
queryParamType: string;
}) => {
const swrProps = toObjectString(props, 'implementation');

Expand All @@ -192,7 +200,7 @@ const generateSwrImplementation = ({
: ''
}`;
const swrKeyImplementation = `const swrKey = swrOptions?.swrKey ?? (() => isEnabled ? ${swrKeyFnName}(${swrKeyProperties}) : null);`;
const swrKeyLoaderImplementation = `const swrKeyLoader = swrOptions?.swrKeyLoader ?? (() => isEnabled ? ${swrKeyLoaderFnName}(${swrKeyProperties}) : null);`;
const swrKeyLoaderImplementation = `const swrKeyLoader = swrOptions?.swrKeyLoader ?? (isEnabled ? ${swrKeyLoaderFnName}(${swrKeyProperties}) : () => null);`;

const errorType = getSwrErrorType(response, httpClient, mutator);
const swrRequestSecondArg = getSwrRequestSecondArg(httpClient, mutator);
Expand Down Expand Up @@ -223,9 +231,11 @@ ${doc}export const ${camel(

${enabledImplementation}
${swrKeyLoaderImplementation}
const swrFn = () => ${operationName}(${httpFunctionProps}${
httpFunctionProps && httpRequestSecondArg ? ', ' : ''
}${httpRequestSecondArg})
const swrFn = ${
hasQueryParams
? `([_url, pageParams]: [string, ${queryParamType} & { page: number }]) => ${operationName}(${pathOnlyParams}${pathOnlyParams ? ', ' : ''}pageParams${headerOnlyParams ? ', ' + headerOnlyParams : ''}${httpRequestSecondArg ? ', ' + httpRequestSecondArg : ''})`
: `([_url]: [string]) => ${operationName}(${pathOnlyParams}${headerOnlyParams ? (pathOnlyParams ? ', ' : '') + headerOnlyParams : ''}${httpRequestSecondArg ? (pathOnlyParams || headerOnlyParams ? ', ' : '') + httpRequestSecondArg : ''})`
}

const ${queryResultVarName} = useSWRInfinite<Awaited<ReturnType<typeof swrFn>>, TError>(swrKeyLoader, swrFn, ${
swrOptions.swrInfiniteOptions
Expand Down Expand Up @@ -406,7 +416,8 @@ const generateSwrHook = (
(prop) =>
prop.type === GetterPropType.PARAM ||
prop.type === GetterPropType.QUERY_PARAM ||
prop.type === GetterPropType.NAMED_PATH_PARAMS,
prop.type === GetterPropType.NAMED_PATH_PARAMS ||
prop.type === GetterPropType.HEADER,
),
'implementation',
);
Expand All @@ -416,7 +427,8 @@ const generateSwrHook = (
(prop) =>
prop.type === GetterPropType.PARAM ||
prop.type === GetterPropType.QUERY_PARAM ||
prop.type === GetterPropType.NAMED_PATH_PARAMS,
prop.type === GetterPropType.NAMED_PATH_PARAMS ||
prop.type === GetterPropType.HEADER,
)
.map((param) => {
return param.type === GetterPropType.NAMED_PATH_PARAMS
Expand Down Expand Up @@ -463,6 +475,35 @@ const generateSwrHook = (
})
.join(',');

// For useSWRInfinite: separate path params from query params
const pathOnlyParams = props
.filter(
(prop) =>
prop.type === GetterPropType.PARAM ||
prop.type === GetterPropType.NAMED_PATH_PARAMS,
)
.map((param) => {
return param.type === GetterPropType.NAMED_PATH_PARAMS
? param.destructured
: param.name;
})
.join(',');

const headerOnlyParams = props
.filter((prop) => prop.type === GetterPropType.HEADER)
.map((param) => param.name)
.join(',');

const hasQueryParams = props.some(
(prop) => prop.type === GetterPropType.QUERY_PARAM,
);

// Extract just the type name from definition (e.g., "params: ListPetsParams" -> "ListPetsParams")
const queryParamType =
props
.find((prop) => prop.type === GetterPropType.QUERY_PARAM)
?.definition.split(': ')[1] ?? 'never';

const queryKeyProps = toObjectString(
props.filter((prop) => prop.type !== GetterPropType.HEADER),
'implementation',
Expand All @@ -480,7 +521,7 @@ export const ${swrKeyFnName} = (${queryKeyProps}) => [\`${route}\`${
);
const swrKeyLoader = override.swr.useInfinite
? `export const ${swrKeyLoaderFnName} = (${queryKeyProps}) => {
return (page: number, previousPageData: Awaited<ReturnType<typeof ${operationName}>>) => {
return (page: number, previousPageData?: Awaited<ReturnType<typeof ${operationName}>>) => {
if (previousPageData && !previousPageData.data) return null

return [\`${route}\`${queryParams ? ', ...(params ? [{...params,page}]: [{page}])' : ''}${
Expand All @@ -504,14 +545,18 @@ export const ${swrKeyFnName} = (${queryKeyProps}) => [\`${route}\`${
swrOptions: override.swr,
doc,
httpClient,
pathOnlyParams,
headerOnlyParams,
hasQueryParams,
queryParamType,
});

if (!override.swr.useSWRMutationForGet) {
return swrKeyFn + swrKeyLoader + swrImplementation;
}

// For OutputClient.SWR_GET_MUTATION, generate both useSWR and useSWRMutation
const httpFnPropertiesForGet = props
const httpFnPropertiesForGetWithoutHeaders = props
.filter((prop) => prop.type !== GetterPropType.HEADER)
.map((prop) => {
return prop.type === GetterPropType.NAMED_PATH_PARAMS
Expand All @@ -520,6 +565,18 @@ export const ${swrKeyFnName} = (${queryKeyProps}) => [\`${route}\`${
})
.join(', ');

const headerParamsForGet = props
.filter((prop) => prop.type === GetterPropType.HEADER)
.map((param) => param.name)
.join(', ');

const httpFnPropertiesForGet = [
httpFnPropertiesForGetWithoutHeaders,
headerParamsForGet,
]
.filter(Boolean)
.join(', ');

const swrMutationFetcherType = getSwrMutationFetcherType(
response,
httpClient,
Expand Down Expand Up @@ -575,7 +632,7 @@ export const ${swrMutationFetcherName} = (${queryKeyProps} ${swrMutationFetcherO
swrMutationImplementation
);
} else {
const httpFnProperties = props
const httpFnPropertiesWithoutHeaders = props
.filter((prop) => prop.type !== GetterPropType.HEADER)
.map((prop) => {
if (prop.type === GetterPropType.NAMED_PATH_PARAMS) {
Expand All @@ -588,6 +645,15 @@ export const ${swrMutationFetcherName} = (${queryKeyProps} ${swrMutationFetcherO
})
.join(', ');

const headerParams = props
.filter((prop) => prop.type === GetterPropType.HEADER)
.map((param) => param.name)
.join(', ');

const httpFnProperties = [httpFnPropertiesWithoutHeaders, headerParams]
.filter(Boolean)
.join(', ');

const swrKeyFnName = camel(`get-${operationName}-mutation-key`);
const swrMutationKeyFn = `export const ${swrKeyFnName} = (${queryKeyProps}) => [\`${route}\`${
queryParams ? ', ...(params ? [params]: [])' : ''
Expand Down
3 changes: 3 additions & 0 deletions packages/swr/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({});
16 changes: 16 additions & 0 deletions tests/configs/swr.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ export default defineConfig({
target: '../specifications/petstore.yaml',
},
},
petstoreWithHeaders: {
output: {
target: '../generated/swr/petstore-with-headers/endpoints.ts',
schemas: '../generated/swr/petstore-with-headers/model',
client: 'swr',
headers: true,
override: {
swr: {
useInfinite: true,
},
},
},
input: {
target: '../specifications/petstore.yaml',
},
},
blobFile: {
output: {
target: '../generated/swr/blob-file/endpoints.ts',
Expand Down
Loading