Skip to content

Commit 73cce83

Browse files
committed
feat: Add SDK option to disable auto-creation of the client
1 parent ec9c018 commit 73cce83

File tree

5 files changed

+134
-42
lines changed

5 files changed

+134
-42
lines changed

.changeset/pink-seahorses-cheer.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': minor
3+
---
4+
5+
Add SDK option to disable the auto-creation of the client

packages/openapi-ts/src/generate/client.ts

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export const clientModulePath = ({
2828
};
2929

3030
export const clientApi = {
31+
Client: {
32+
asType: true,
33+
name: 'Client',
34+
},
3135
Options: {
3236
asType: true,
3337
name: 'Options',

packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const defaultConfig: Plugin.Config<Config> = {
3030
},
3131
asClass: false,
3232
auth: true,
33+
autoCreateClient: true,
3334
exportFromIndex: true,
3435
name: '@hey-api/sdk',
3536
operationId: true,

packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts

+112-42
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ import { serviceFunctionIdentifier } from './plugin-legacy';
2828
import type { Config } from './types';
2929

3030
export const operationOptionsType = ({
31+
clientOption,
3132
identifierData,
3233
throwOnError,
3334
}: {
35+
clientOption?: 'required' | 'omitted';
3436
context: IR.Context;
3537
identifierData?: ReturnType<TypeScriptFile['identifier']>;
3638
// TODO: refactor this so we don't need to import error type unless it's used here
@@ -39,13 +41,24 @@ export const operationOptionsType = ({
3941
}) => {
4042
const optionsName = clientApi.Options.name;
4143

44+
let optionsType = identifierData
45+
? `${optionsName}<${identifierData.name}>`
46+
: optionsName;
47+
4248
// TODO: refactor this to be more generic, works for now
4349
if (throwOnError) {
44-
return `${optionsName}<${identifierData?.name || 'unknown'}, ${throwOnError}>`;
50+
optionsType = `${optionsName}<${identifierData?.name || 'unknown'}, ${throwOnError}>`;
4551
}
46-
return identifierData
47-
? `${optionsName}<${identifierData.name}>`
48-
: optionsName;
52+
53+
if (clientOption === 'required') {
54+
optionsType += ` & { client: Client }`;
55+
}
56+
57+
if (clientOption === 'omitted') {
58+
optionsType = `Omit<${optionsType}, 'client'>`;
59+
}
60+
61+
return optionsType;
4962
};
5063

5164
const sdkId = 'sdk';
@@ -377,6 +390,12 @@ const operationStatements = ({
377390
value: operation.path,
378391
});
379392

393+
let clientCall = '(options?.client ?? client)';
394+
395+
if (!plugin.autoCreateClient) {
396+
clientCall = plugin.asClass ? 'this._client' : 'options.client';
397+
}
398+
380399
return [
381400
compiler.returnFunctionCall({
382401
args: [
@@ -385,7 +404,7 @@ const operationStatements = ({
385404
obj: requestOptions,
386405
}),
387406
],
388-
name: `(options?.client ?? client).${operation.method}`,
407+
name: `${clientCall}.${operation.method}`,
389408
types: [
390409
identifierResponse.name || 'unknown',
391410
identifierError.name || 'unknown',
@@ -417,7 +436,7 @@ const generateClassSdk = ({
417436
operation.summary && escapeComment(operation.summary),
418437
operation.description && escapeComment(operation.description),
419438
],
420-
isStatic: true,
439+
isStatic: !!plugin.autoCreateClient, // if client is required, methods are not static
421440
name: serviceFunctionIdentifier({
422441
config: context.config,
423442
handleIllegal: false,
@@ -429,6 +448,7 @@ const generateClassSdk = ({
429448
isRequired: hasOperationDataRequired(operation),
430449
name: 'options',
431450
type: operationOptionsType({
451+
clientOption: !plugin.autoCreateClient ? 'omitted' : undefined,
432452
context,
433453
identifierData,
434454
// identifierError,
@@ -466,9 +486,45 @@ const generateClassSdk = ({
466486

467487
context.subscribe('after', () => {
468488
for (const [name, nodes] of sdks) {
489+
const extraMembers: ts.ClassElement[] = [];
490+
491+
// Add client property and constructor if autoCreateClient is false
492+
if (!plugin.autoCreateClient) {
493+
const clientType = 'Client';
494+
495+
const clientProperty = compiler.propertyDeclaration({
496+
accessLevel: 'private',
497+
comment: ['Client Instance'],
498+
name: '_client',
499+
type: compiler.typeReferenceNode({ typeName: clientType }),
500+
});
501+
502+
const constructor = compiler.constructorDeclaration({
503+
comment: ['@param client - Client Instance'],
504+
parameters: [
505+
{
506+
isRequired: true,
507+
name: 'client',
508+
type: clientType,
509+
},
510+
],
511+
statements: [
512+
compiler.expressionToStatement({
513+
expression: compiler.binaryExpression({
514+
left: compiler.identifier({ text: 'this._client ' }),
515+
operator: '=',
516+
right: compiler.identifier({ text: 'client' }),
517+
}),
518+
}),
519+
],
520+
});
521+
522+
extraMembers.push(clientProperty, constructor);
523+
}
524+
469525
const node = compiler.classDeclaration({
470526
decorator: undefined,
471-
members: nodes,
527+
members: [...extraMembers, ...nodes],
472528
name: transformServiceName({
473529
config: context.config,
474530
name,
@@ -503,9 +559,11 @@ const generateFlatSdk = ({
503559
expression: compiler.arrowFunction({
504560
parameters: [
505561
{
506-
isRequired: hasOperationDataRequired(operation),
562+
isRequired:
563+
hasOperationDataRequired(operation) || !plugin.autoCreateClient,
507564
name: 'options',
508565
type: operationOptionsType({
566+
clientOption: !plugin.autoCreateClient ? 'required' : undefined,
509567
context,
510568
identifierData,
511569
// identifierError,
@@ -550,52 +608,64 @@ export const handler: Plugin.Handler<Config> = ({ context, plugin }) => {
550608
id: sdkId,
551609
path: plugin.output,
552610
});
611+
553612
const sdkOutput = file.nameWithoutExtension();
554613

555614
// import required packages and core files
556615
const clientModule = clientModulePath({
557616
config: context.config,
558617
sourceOutput: sdkOutput,
559618
});
560-
file.import({
561-
module: clientModule,
562-
name: 'createClient',
563-
});
564-
file.import({
565-
module: clientModule,
566-
name: 'createConfig',
567-
});
619+
568620
file.import({
569621
...clientApi.Options,
570622
module: clientModule,
571623
});
572624

573-
// define client first
574-
const statement = compiler.constVariable({
575-
exportConst: true,
576-
expression: compiler.callExpression({
577-
functionName: 'createClient',
578-
parameters: [
579-
compiler.callExpression({
580-
functionName: 'createConfig',
581-
parameters: [
582-
plugin.throwOnError
583-
? compiler.objectExpression({
584-
obj: [
585-
{
586-
key: 'throwOnError',
587-
value: plugin.throwOnError,
588-
},
589-
],
590-
})
591-
: undefined,
592-
],
593-
}),
594-
],
595-
}),
596-
name: 'client',
597-
});
598-
file.add(statement);
625+
if (plugin.autoCreateClient) {
626+
file.import({
627+
module: clientModule,
628+
name: 'createClient',
629+
});
630+
file.import({
631+
module: clientModule,
632+
name: 'createConfig',
633+
});
634+
635+
// define client first
636+
const statement = compiler.constVariable({
637+
exportConst: true,
638+
expression: compiler.callExpression({
639+
functionName: 'createClient',
640+
parameters: [
641+
compiler.callExpression({
642+
functionName: 'createConfig',
643+
parameters: [
644+
plugin.throwOnError
645+
? compiler.objectExpression({
646+
obj: [
647+
{
648+
key: 'throwOnError',
649+
value: plugin.throwOnError,
650+
},
651+
],
652+
})
653+
: undefined,
654+
],
655+
}),
656+
],
657+
}),
658+
name: 'client',
659+
});
660+
661+
file.add(statement);
662+
} else {
663+
// Bring in the client type
664+
file.import({
665+
...clientApi.Client,
666+
module: clientModule,
667+
});
668+
}
599669

600670
if (plugin.asClass) {
601671
generateClassSdk({ context, plugin });

packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts

+12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ export interface Config extends Plugin.Name<'@hey-api/sdk'> {
2525
* @default true
2626
*/
2727
auth?: boolean;
28+
/**
29+
* **This feature works only with the [experimental parser](https://heyapi.dev/openapi-ts/configuration#parser)**
30+
*
31+
* Should the generated SDK do a createClient call automatically? If this is
32+
* set to false, the generated SDK will expect a client to be passed in during:
33+
*
34+
* - instantiation if asClass is set to true (and the client will be passed to the constructor. All methods will not be static either)
35+
* - each method call if asClass is set to false
36+
*
37+
* @default true
38+
*/
39+
autoCreateClient?: boolean;
2840
/**
2941
* **This feature works only with the legacy parser**
3042
*

0 commit comments

Comments
 (0)