Skip to content

Commit

Permalink
feat(typescript): introduce API Promise so that we can return respons…
Browse files Browse the repository at this point in the history
…e headers
  • Loading branch information
dsinghvi committed Nov 19, 2024
1 parent 4d187a6 commit 4ffc5a0
Show file tree
Hide file tree
Showing 40 changed files with 463 additions and 1,586 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -605,21 +605,35 @@ export class GeneratedSdkClientClassImpl implements GeneratedSdkClientClass {
isIdempotent = true;
}

const statements = endpoint.getStatements(context);

const method = serviceClass.addMethod({
name: endpoint.endpoint.name.camelCase.unsafeName,
parameters: signature.parameters,
returnType: getTextOfTsNode(
ts.factory.createTypeReferenceNode("Promise", [signature.returnTypeWithoutPromise])
),
returnType: context.neverThrowErrors
? getTextOfTsNode(
ts.factory.createTypeReferenceNode("Promise", [signature.returnTypeWithoutPromise])
)
: getTextOfTsNode(
context.coreUtilities.apiPromise._getReferenceToType(signature.returnTypeWithoutPromise)
),
scope: Scope.Public,
isAsync: true,
statements: endpoint.getStatements(context).map(getTextOfTsNode),
statements: context.neverThrowErrors
? statements.map(getTextOfTsNode)
: [ts.factory.createReturnStatement(context.coreUtilities.apiPromise.from(statements))].map(
getTextOfTsNode
),
overloads: overloads.map((overload, index) => ({
docs: index === 0 && docs != null ? ["\n" + docs] : undefined,
parameters: overload.parameters,
returnType: getTextOfTsNode(
ts.factory.createTypeReferenceNode("Promise", [overload.returnTypeWithoutPromise])
)
returnType: context.neverThrowErrors
? getTextOfTsNode(
ts.factory.createTypeReferenceNode("Promise", [overload.returnTypeWithoutPromise])
)
: getTextOfTsNode(
context.coreUtilities.apiPromise._getReferenceToType(overload.returnTypeWithoutPromise)
)
}))
});

Expand Down
3 changes: 2 additions & 1 deletion generators/typescript/sdk/generator/src/SdkGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1298,7 +1298,8 @@ export class SdkGenerator {
inlineFileProperties: this.config.inlineFileProperties,
generateOAuthClients: this.generateOAuthClients,
omitUndefined: this.config.omitUndefined,
useBigInt: this.config.useBigInt
useBigInt: this.config.useBigInt,
neverThrowErrors: this.config.neverThrowErrors
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export declare namespace SdkContextImpl {
generateOAuthClients: boolean;
inlineFileProperties: boolean;
omitUndefined: boolean;
neverThrowErrors: boolean;
useBigInt: boolean;
}
}
Expand Down Expand Up @@ -150,6 +151,7 @@ export class SdkContextImpl implements SdkContext {
public readonly inlineFileProperties: boolean;
public readonly generateOAuthClients: boolean;
public readonly omitUndefined: boolean;
public readonly neverThrowErrors: boolean;

constructor({
logger,
Expand Down Expand Up @@ -200,7 +202,8 @@ export class SdkContextImpl implements SdkContext {
inlineFileProperties,
generateOAuthClients,
omitUndefined,
useBigInt
useBigInt,
neverThrowErrors
}: SdkContextImpl.Init) {
this.logger = logger;
this.ir = ir;
Expand All @@ -217,6 +220,7 @@ export class SdkContextImpl implements SdkContext {
this.sdkInstanceReferenceForSnippet = ts.factory.createIdentifier(this.rootClientVariableName);
this.sourceFile = sourceFile;
this.npmPackage = npmPackage;
this.neverThrowErrors = neverThrowErrors;
this.externalDependencies = createExternalDependencies({
dependencyManager,
importsManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Runtime } from "./runtime/Runtime";
import { StreamUtils } from "./stream-utils/StreamUtils";
import { Utils } from "./utils/Utils";
import { Zurg } from "./zurg/Zurg";
import { APIPromise } from "./api-promise/APIPromise";

export interface CoreUtilities {
zurg: Zurg;
Expand All @@ -20,4 +21,5 @@ export interface CoreUtilities {
runtime: Runtime;
pagination: Pagination;
utils: Utils;
apiPromise: APIPromise;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DependencyManager } from "../dependency-manager/DependencyManager";
import { ExportedDirectory, ExportsManager } from "../exports-manager";
import { ImportsManager } from "../imports-manager";
import { getReferenceToExportViaNamespaceImport } from "../referencing";
import { APIPromiseImpl } from "./api-promise/APIPromiseImpl";
import { AuthImpl } from "./auth/AuthImpl";
import { BaseCoreUtilitiesImpl } from "./base/BaseCoreUtilitiesImpl";
import { CallbackQueueImpl } from "./callback-queue/CallbackQueueImpl";
Expand Down Expand Up @@ -53,7 +54,8 @@ export class CoreUtilitiesManager {
formDataUtils: new FormDataUtilsImpl({ getReferenceToExport }),
runtime: new RuntimeImpl({ getReferenceToExport }),
pagination: new PaginationImpl({ getReferenceToExport }),
utils: new UtilsImpl({ getReferenceToExport })
utils: new UtilsImpl({ getReferenceToExport }),
apiPromise: new APIPromiseImpl({ getReferenceToExport })
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ts } from "ts-morph";

export interface APIPromise {
_getReferenceToType: (response: ts.TypeNode) => ts.TypeNode;

from: (statements: ts.Statement[]) => ts.Expression;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AbsoluteFilePath, RelativeFilePath } from "@fern-api/fs-utils";
import { ts } from "ts-morph";
import { DependencyManager } from "../../dependency-manager/DependencyManager";
import { CoreUtility } from "../CoreUtility";
import { APIPromise } from "./APIPromise";

export class APIPromiseImpl extends CoreUtility implements APIPromise {
public readonly MANIFEST = {
name: "api-promise",
repoInfoForTesting: {
path: RelativeFilePath.of("generators/typescript/utils/core-utilities/fetcher/src/runtime")
},
originalPathOnDocker: AbsoluteFilePath.of("/assets/fetcher/api-promise"),
pathInCoreUtilities: [{ nameOnDisk: "api-promise", exportDeclaration: { exportAll: true } }],
addDependencies: (dependencyManager: DependencyManager): void => {
return;
},
};

public _getReferenceToType = (response: ts.TypeNode): ts.TypeNode => {
return ts.factory.createTypeReferenceNode("APIPromise", [response]);
};

public from = (body: ts.Statement[]): ts.Expression => {
return ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier("APIPromise"),
ts.factory.createIdentifier("from")
),
undefined,
[
ts.factory.createCallExpression(
ts.factory.createParenthesizedExpression(
ts.factory.createArrowFunction(
[ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)],
undefined,
[],
undefined,
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
ts.factory.createBlock(body, true)
)
),
undefined,
[]
)
]
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ export interface SdkContext extends ModelContext {
generateOAuthClients: boolean;
inlineFileProperties: boolean;
omitUndefined: boolean;
neverThrowErrors: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { APIResponse } from "../fetcher/APIResponse";

/**
* APIPromise wraps a Promise that resolves with an APIResponse.
* It provides convenient methods for handling both successful responses and errors.
*
* By default, when awaited, it will return just the response body data.
* Use the `asRaw()` method to get access to both the response data and headers.
*
* @example
* // Get just the response data
* const data = await apiPromise;
*
* // Get response with headers
* const { data, headers } = await apiPromise.asRaw();
*
* @template T The type of the successful response body
*/
export class APIPromise<T> extends Promise<T> {
constructor(
private readonly responsePromise: Promise<APIResponse<T, unknown>>,
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void
) {
super(executor);
}

public async asRaw(): Promise<{
data: T;
headers?: Record<string, any>;
}> {
const response = await this.responsePromise;
if (!response.ok) {
throw response.error;
}
return {
data: response.body,
headers: response.headers
};
}

public static from<T>(responsePromise: Promise<APIResponse<T, unknown>>): APIPromise<T> {
return new APIPromise(responsePromise, (resolve, reject) => {
responsePromise
.then((response) => {
if (response.ok) {
resolve(response.body);
} else {
reject(response.error);
}
})
.catch(reject);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { APIPromise } from "./APIPromise";
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./form-data-utils";
export * from "./runtime";
export * from "./stream";
export * from "./pagination";
export * from "./api-promise";
Loading

0 comments on commit 4ffc5a0

Please sign in to comment.