Skip to content

Commit

Permalink
Release 0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
fern-api[bot] committed Dec 11, 2023
1 parent 0aaef9e commit e8224a1
Show file tree
Hide file tree
Showing 23 changed files with 265 additions and 88 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fern-api/node-sdk",
"version": "0.1.1",
"version": "0.2.0",
"private": false,
"repository": "https://github.com/fern-api/node-sdk",
"main": "./index.js",
Expand All @@ -12,12 +12,12 @@
},
"dependencies": {
"url-join": "4.0.1",
"@types/url-join": "4.0.1",
"@ungap/url-search-params": "0.2.2",
"axios": "0.27.2",
"qs": "6.11.2",
"js-base64": "3.7.2"
},
"devDependencies": {
"@types/url-join": "4.0.1",
"@types/qs": "6.9.8",
"@types/node": "17.0.33",
"prettier": "2.7.1",
"typescript": "4.6.4"
Expand Down
1 change: 1 addition & 0 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export declare namespace FernClient {

interface RequestOptions {
timeoutInSeconds?: number;
maxRetries?: number;
}
}

Expand Down
32 changes: 27 additions & 5 deletions src/api/resources/snippets/client/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import * as Fern from "../../..";
import * as serializers from "../../../../serialization";
import urlJoin from "url-join";
import * as errors from "../../../../errors";
import { default as URLSearchParams } from "@ungap/url-search-params";

export declare namespace Snippets {
interface Options {
Expand All @@ -18,6 +17,7 @@ export declare namespace Snippets {

interface RequestOptions {
timeoutInSeconds?: number;
maxRetries?: number;
}
}

Expand All @@ -36,6 +36,14 @@ export class Snippets {
* @throws {@link Fern.snippets.ApiIdNotFound}
* @throws {@link Fern.snippets.EndpointNotFound}
* @throws {@link Fern.snippets.SdkNotFound}
*
* @example
* await fern.snippets.get({
* endpoint: {
* method: Fern.snippets.EndpointMethod.Get,
* path: "/v1/search"
* }
* })
*/
public async get(
request: Fern.snippets.GetSnippetRequest,
Expand All @@ -51,13 +59,14 @@ export class Snippets {
Authorization: await this._getAuthorizationHeader(),
"X-Fern-Language": "JavaScript",
"X-Fern-SDK-Name": "@fern-api/node-sdk",
"X-Fern-SDK-Version": "0.1.1",
"X-Fern-SDK-Version": "0.2.0",
},
contentType: "application/json",
body: await serializers.snippets.GetSnippetRequest.jsonOrThrow(request, {
unrecognizedObjectKeys: "strip",
}),
timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
maxRetries: requestOptions?.maxRetries,
});
if (_response.ok) {
return await serializers.snippets.get.Response.parseOrThrow(_response.body, {
Expand Down Expand Up @@ -187,15 +196,27 @@ export class Snippets {
* @throws {@link Fern.snippets.OrgIdNotFound}
* @throws {@link Fern.snippets.ApiIdNotFound}
* @throws {@link Fern.snippets.SdkNotFound}
*
* @example
* await fern.snippets.load({
* page: 1,
* orgId: "vellum",
* apiId: "vellum-ai",
* sdks: [{
* type: "python",
* package: "vellum-ai",
* version: "1.2.1"
* }]
* })
*/
public async load(
request: Fern.snippets.ListSnippetsRequest = {},
requestOptions?: Snippets.RequestOptions
): Promise<Fern.snippets.SnippetsPage> {
const { page, ..._body } = request;
const _queryParams = new URLSearchParams();
const _queryParams: Record<string, string | string[]> = {};
if (page != null) {
_queryParams.append("page", page.toString());
_queryParams["page"] = page.toString();
}

const _response = await core.fetcher({
Expand All @@ -208,14 +229,15 @@ export class Snippets {
Authorization: await this._getAuthorizationHeader(),
"X-Fern-Language": "JavaScript",
"X-Fern-SDK-Name": "@fern-api/node-sdk",
"X-Fern-SDK-Version": "0.1.1",
"X-Fern-SDK-Version": "0.2.0",
},
contentType: "application/json",
queryParameters: _queryParams,
body: await serializers.snippets.ListSnippetsRequest.jsonOrThrow(_body, {
unrecognizedObjectKeys: "strip",
}),
timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
maxRetries: requestOptions?.maxRetries,
});
if (_response.ok) {
return await serializers.snippets.SnippetsPage.parseOrThrow(_response.body, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

import * as Fern from "../../../..";

/**
* @example
* {
* endpoint: {
* method: Fern.snippets.EndpointMethod.Get,
* path: "/v1/search"
* }
* }
*/
export interface GetSnippetRequest {
/**
* If the same API is defined across multiple organization,
Expand Down
13 changes: 13 additions & 0 deletions src/api/resources/snippets/client/requests/ListSnippetsRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@

import * as Fern from "../../../..";

/**
* @example
* {
* page: 1,
* orgId: "vellum",
* apiId: "vellum-ai",
* sdks: [{
* type: "python",
* package: "vellum-ai",
* version: "1.2.1"
* }]
* }
*/
export interface ListSnippetsRequest {
page?: number;
/**
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/ApiIdNotFound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class ApiIdNotFound extends errors.FernError {
constructor(body: string) {
super({
message: "ApiIdNotFound",
statusCode: 404,
body: body,
});
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/ApiIdRequiredError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class ApiIdRequiredError extends errors.FernError {
constructor(body: string) {
super({
message: "ApiIdRequiredError",
statusCode: 400,
body: body,
});
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/EndpointNotFound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class EndpointNotFound extends errors.FernError {
constructor(body: string) {
super({
message: "EndpointNotFound",
statusCode: 404,
body: body,
});
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/InvalidPageError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class InvalidPageError extends errors.FernError {
constructor(body: string) {
super({
message: "InvalidPageError",
statusCode: 400,
body: body,
});
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/OrgIdAndApiIdNotFound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class OrgIdAndApiIdNotFound extends errors.FernError {
constructor(body: string) {
super({
message: "OrgIdAndApiIdNotFound",
statusCode: 400,
body: body,
});
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/OrgIdNotFound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class OrgIdNotFound extends errors.FernError {
constructor(body: string) {
super({
message: "OrgIdNotFound",
statusCode: 404,
body: body,
});
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/OrgIdRequiredError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class OrgIdRequiredError extends errors.FernError {
constructor(body: string) {
super({
message: "OrgIdRequiredError",
statusCode: 400,
body: body,
});
Expand Down
1 change: 1 addition & 0 deletions src/api/resources/snippets/errors/SdkNotFound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../errors";
export class SdkNotFound extends errors.FernError {
constructor(body: string) {
super({
message: "SDKNotFound",
statusCode: 404,
body: body,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../../../errors";
export class UnauthorizedError extends errors.FernError {
constructor(body: string) {
super({
message: "UnauthorizedError",
statusCode: 401,
body: body,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../../../errors";
export class UnavailableError extends errors.FernError {
constructor(body: string) {
super({
message: "UnavailableError",
statusCode: 503,
body: body,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as errors from "../../../../../../errors";
export class UserNotInOrgError extends errors.FernError {
constructor() {
super({
message: "UserNotInOrgError",
statusCode: 403,
});
Object.setPrototypeOf(this, UserNotInOrgError.prototype);
Expand Down
81 changes: 52 additions & 29 deletions src/core/fetcher/Fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { default as URLSearchParams } from "@ungap/url-search-params";
import axios, { AxiosAdapter, AxiosError } from "axios";
import qs from "qs";
import { APIResponse } from "./APIResponse";

export type FetchFunction = <R = unknown>(args: Fetcher.Args) => Promise<APIResponse<R, Fetcher.Error>>;
Expand All @@ -10,13 +9,12 @@ export declare namespace Fetcher {
method: string;
contentType?: string;
headers?: Record<string, string | undefined>;
queryParameters?: URLSearchParams;
queryParameters?: Record<string, string | string[]>;
body?: unknown;
timeoutMs?: number;
maxRetries?: number;
withCredentials?: boolean;
responseType?: "json" | "blob";
adapter?: AxiosAdapter;
onUploadProgress?: (event: ProgressEvent) => void;
}

export type Error = FailedStatusCodeError | NonJsonError | TimeoutError | UnknownError;
Expand All @@ -43,6 +41,10 @@ export declare namespace Fetcher {
}
}

const INITIAL_RETRY_DELAY = 1;
const MAX_RETRY_DELAY = 60;
const DEFAULT_MAX_RETRIES = 2;

async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse<R, Fetcher.Error>> {
const headers: Record<string, string> = {};
if (args.body !== undefined && args.contentType != null) {
Expand All @@ -57,40 +59,61 @@ async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse
}
}

try {
const response = await axios({
url: args.url,
params: args.queryParameters,
const url =
Object.keys(args.queryParameters ?? {}).length > 0
? `${args.url}?${qs.stringify(args.queryParameters, { arrayFormat: "repeat" })}`
: args.url;

const makeRequest = async (): Promise<Response> => {
const controller = new AbortController();
let abortId = undefined;
if (args.timeoutMs != null) {
abortId = setTimeout(() => controller.abort(), args.timeoutMs);
}
const response = await fetch(url, {
method: args.method,
headers,
data: args.body,
validateStatus: () => true,
transformResponse: (response) => response,
timeout: args.timeoutMs,
transitional: {
clarifyTimeoutError: true,
},
withCredentials: args.withCredentials,
adapter: args.adapter,
onUploadProgress: args.onUploadProgress,
maxBodyLength: Infinity,
maxContentLength: Infinity,
responseType: args.responseType ?? "json",
body: args.body === undefined ? undefined : JSON.stringify(args.body),
signal: controller.signal,
credentials: args.withCredentials ? "same-origin" : undefined,
});
if (abortId != null) {
clearTimeout(abortId);
}
return response;
};

try {
let response = await makeRequest();

for (let i = 0; i < (args.maxRetries ?? DEFAULT_MAX_RETRIES); ++i) {
if (
response.status === 408 ||
response.status === 409 ||
response.status === 429 ||
response.status >= 500
) {
const delay = Math.min(INITIAL_RETRY_DELAY * Math.pow(i, 2), MAX_RETRY_DELAY);
await new Promise((resolve) => setTimeout(resolve, delay));
response = await makeRequest();
} else {
break;
}
}

let body: unknown;
if (args.responseType === "blob") {
body = response.data;
} else if (response.data != null && response.data.length > 0) {
if (response.body != null && args.responseType === "blob") {
body = await response.blob();
} else if (response.body != null) {
try {
body = JSON.parse(response.data) ?? undefined;
body = await response.json();
} catch {
return {
ok: false,
error: {
reason: "non-json",
statusCode: response.status,
rawBody: response.data,
rawBody: await response.text(),
},
};
}
Expand All @@ -112,7 +135,7 @@ async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse
};
}
} catch (error) {
if ((error as AxiosError).code === "ETIMEDOUT") {
if (error instanceof Error && error.name === "AbortError") {
return {
ok: false,
error: {
Expand All @@ -125,7 +148,7 @@ async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse
ok: false,
error: {
reason: "unknown",
errorMessage: (error as AxiosError).message,
errorMessage: JSON.stringify(error),
},
};
}
Expand Down
1 change: 1 addition & 0 deletions src/core/schemas/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const SchemaType = {
ENUM: "enum",
LIST: "list",
STRING_LITERAL: "stringLiteral",
BOOLEAN_LITERAL: "booleanLiteral",
OBJECT: "object",
ANY: "any",
BOOLEAN: "boolean",
Expand Down
Loading

0 comments on commit e8224a1

Please sign in to comment.