From cd14e0111ce921e6b65f1cd5447f1006023f6b54 Mon Sep 17 00:00:00 2001 From: Zach Kirsch Date: Wed, 6 Jul 2022 21:48:59 -0700 Subject: [PATCH] Implement headers in client and server (#345) * Implement headers in client and server * Fix tests * Fix test * Fix tests --- packages/add-generator/src/addGenerator.ts | 4 +- .../add/__snapshots__/add.test.ts.snap | 4 +- .../__snapshots__/generate.test.ts.snap | 146 +++++++++++++----- .../generateClientProject.test.ts.snap | 53 +++++-- .../src/__test__/fixtures/posts/src/posts.yml | 4 + .../fern-typescript/client/src/constants.ts | 23 ++- .../client/src/http/addServiceConstructor.ts | 133 ++++++++++++++++ .../client/src/http/addServiceNamespace.ts | 128 +++++++++++++++ .../generateFetcherCall.ts | 58 ++++++- .../client/src/http/generateHttpService.ts | 129 +++------------- .../fern-typescript/client/src/http/utils.ts | 23 +++ .../getReferenceToFernServiceUtils.ts | 30 +--- packages/fern-typescript/commons/src/index.ts | 1 - .../generateServerProject.test.ts.snap | 39 +++-- .../src/__test__/fixtures/posts/src/posts.yml | 2 + .../fern-typescript/server/src/constants.ts | 5 +- .../src/http/addExpressRouteStatement.ts | 12 +- .../server/src/http/generateHttpService.ts | 2 - .../server/src/http/generateImplCall.ts | 57 +++++-- .../src/utils.ts/generateMaybePromise.ts | 23 +-- .../src/http/generateRequestTypes.ts | 18 ++- .../service-utils/src/Fetcher.ts | 2 +- .../service-utils/src/MaybePromise.ts | 1 - .../service-utils/src/index.ts | 1 - vercel.json | 5 + 25 files changed, 648 insertions(+), 255 deletions(-) create mode 100644 packages/fern-typescript/client/src/http/addServiceConstructor.ts create mode 100644 packages/fern-typescript/client/src/http/addServiceNamespace.ts create mode 100644 packages/fern-typescript/client/src/http/utils.ts delete mode 100644 packages/fern-typescript/service-utils/src/MaybePromise.ts create mode 100644 vercel.json diff --git a/packages/add-generator/src/addGenerator.ts b/packages/add-generator/src/addGenerator.ts index 870603468c5..33a2d27279d 100644 --- a/packages/add-generator/src/addGenerator.ts +++ b/packages/add-generator/src/addGenerator.ts @@ -12,7 +12,7 @@ const JAVA_GENERATOR_INVOCATION: GeneratorInvocationSchema = { const TYPESCRIPT_GENERATOR_INVOCATION: GeneratorInvocationSchema = { name: "fernapi/fern-typescript", - version: "0.0.123", + version: "0.0.125", generate: true, config: { mode: "server", @@ -21,7 +21,7 @@ const TYPESCRIPT_GENERATOR_INVOCATION: GeneratorInvocationSchema = { const POSTMAN_GENERATOR_INVOCATION: GeneratorInvocationSchema = { name: "fernapi/fern-postman", - version: "0.0.8", + version: "0.0.10", generate: { enabled: true, output: "./generated-postman.json", diff --git a/packages/ete-tests/src/__test__/add/__snapshots__/add.test.ts.snap b/packages/ete-tests/src/__test__/add/__snapshots__/add.test.ts.snap index d7cf33f4c21..fba2a77cc9d 100644 --- a/packages/ete-tests/src/__test__/add/__snapshots__/add.test.ts.snap +++ b/packages/ete-tests/src/__test__/add/__snapshots__/add.test.ts.snap @@ -11,12 +11,12 @@ generators: packagePrefix: com mode: client_and_server - name: fernapi/fern-typescript - version: 0.0.123 + version: 0.0.125 generate: true config: mode: server - name: fernapi/fern-postman - version: 0.0.8 + version: 0.0.10 generate: enabled: true output: ./generated-postman.json diff --git a/packages/fern-typescript/cli/src/__test__/__snapshots__/generate.test.ts.snap b/packages/fern-typescript/cli/src/__test__/__snapshots__/generate.test.ts.snap index 36e3b4cfd00..f4182ec346b 100644 --- a/packages/fern-typescript/cli/src/__test__/__snapshots__/generate.test.ts.snap +++ b/packages/fern-typescript/cli/src/__test__/__snapshots__/generate.test.ts.snap @@ -6715,7 +6715,7 @@ export * from \\"./_service-types\\"; \\"build\\": \\"esbuild $(find . -name '*.ts' -not -path './node_modules/*') --format=cjs --sourcemap --outdir=. && tsc\\" }, \\"dependencies\\": { - \\"@fern-typescript/service-utils\\": \\"0.0.114\\", + \\"@fern-typescript/service-utils\\": \\"0.0.124\\", \\"url-join\\": \\"5.0.0\\" }, \\"devDependencies\\": { @@ -6730,7 +6730,7 @@ export * from \\"./_service-types\\"; Object { "contents": Array [ Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -6745,15 +6745,21 @@ export interface AdminService { storeTracedWorkspaceV2(request: model.StoreTracedWorkspaceV2Request): Promise; } +export declare namespace AdminService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class AdminService implements AdminService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: AdminService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/admin\\"); - this.token = args.token; } public async updateTestSubmissionStatus(request: model.UpdateTestSubmissionStatusRequest): Promise { @@ -6969,7 +6975,7 @@ export class AdminService implements AdminService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -6980,6 +6986,13 @@ export interface ExecutionSesssionManagementService { getExecutionSessionsState(): Promise; } +export declare namespace ExecutionSesssionManagementService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + /** * Responsible for spinning up and spinning down execution. */ @@ -6988,10 +7001,9 @@ export class ExecutionSesssionManagementService implements ExecutionSesssionMana private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: ExecutionSesssionManagementService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/sessions\\"); - this.token = args.token; } public async createExecutionSession(request: model.CreateExecutionSessionRequest): Promise { @@ -7093,7 +7105,7 @@ export class ExecutionSesssionManagementService implements ExecutionSesssionMana "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7102,15 +7114,21 @@ export interface HomepageProblemService { setHomepageProblems(request: model.ProblemId[]): Promise; } +export declare namespace HomepageProblemService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class HomepageProblemService implements HomepageProblemService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: HomepageProblemService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/homepage-problems\\"); - this.token = args.token; } public async getHomepageProblems(): Promise { @@ -7168,7 +7186,7 @@ export class HomepageProblemService implements HomepageProblemService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7176,15 +7194,21 @@ export interface MigrationInfoService { getAttemptedMigrations(): Promise; } +export declare namespace MigrationInfoService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class MigrationInfoService implements MigrationInfoService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: MigrationInfoService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/migration-info\\"); - this.token = args.token; } public async getAttemptedMigrations(): Promise { @@ -7216,7 +7240,7 @@ export class MigrationInfoService implements MigrationInfoService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7228,12 +7252,20 @@ export interface PlaylistCrudService { deletePlaylist(request: model.DeletePlaylistRequest): Promise; } +export declare namespace PlaylistCrudService { + interface Init { + origin: string; + fetcher?: Fetcher; + token?: MaybeGetter; + } +} + export class PlaylistCrudService implements PlaylistCrudService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: PlaylistCrudService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/v2/playlist\\"); this.token = args.token; @@ -7370,7 +7402,7 @@ export class PlaylistCrudService implements PlaylistCrudService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7381,15 +7413,21 @@ export interface ProblemCrudService { getDefaultStarterFiles(request: model.GetDefaultStarterFilesRequest): Promise; } +export declare namespace ProblemCrudService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class ProblemCrudService implements ProblemCrudService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: ProblemCrudService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/problem-crud\\"); - this.token = args.token; } public async createProblem(request: model.CreateProblemRequest): Promise { @@ -7503,7 +7541,7 @@ export class ProblemCrudService implements ProblemCrudService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7514,6 +7552,13 @@ export interface ProblemInfoService { getExpectedResult(request: model.GetExpectedResultRequest): Promise; } +export declare namespace ProblemInfoService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + /** * Information about different problems */ @@ -7522,10 +7567,9 @@ export class ProblemInfoService implements ProblemInfoService { private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: ProblemInfoService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/problems\\"); - this.token = args.token; } public async getProblems(): Promise { @@ -7629,7 +7673,7 @@ export class ProblemInfoService implements ProblemInfoService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7642,6 +7686,13 @@ export interface SubmissionInfoService { getTraceResponsesV2PageForWorkspace(request: model.GetTraceResponsesV2PageForWorkspaceRequest): Promise; } +export declare namespace SubmissionInfoService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + /** * Responsible for serving information about submissions */ @@ -7650,10 +7701,9 @@ export class SubmissionInfoService implements SubmissionInfoService { private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: SubmissionInfoService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/submissions\\"); - this.token = args.token; } public async getSubmissionState(request: model.GetSubmissionStateRequest): Promise { @@ -7821,7 +7871,7 @@ export class SubmissionInfoService implements SubmissionInfoService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7830,15 +7880,21 @@ export interface SysPropCrudService { getNumWarmInstances(): Promise; } +export declare namespace SysPropCrudService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class SysPropCrudService implements SysPropCrudService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: SysPropCrudService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/sysprop\\"); - this.token = args.token; } public async setNumWarmInstances(request: model.SetNumWarmInstancesRequest): Promise { @@ -7892,7 +7948,7 @@ export class SysPropCrudService implements SysPropCrudService { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -7901,15 +7957,21 @@ export interface WorkspaceInfoService { getWorkspaceStarterFilesV2(): Promise; } +export declare namespace WorkspaceInfoService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class WorkspaceInfoService implements WorkspaceInfoService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: WorkspaceInfoService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/workspace\\"); - this.token = args.token; } public async getWorkspaceStarterFiles(): Promise { @@ -7983,7 +8045,7 @@ export * from \\"./SysPropCrudService\\"; Object { "contents": Array [ Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../../model\\"; @@ -7998,15 +8060,21 @@ export interface ProblemCrudServiceV2 { getGeneratedTestCaseTemplateFile(request: model.problemV2.GetGeneratedTestCaseTemplateFileRequest): Promise; } +export declare namespace ProblemCrudServiceV2 { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class ProblemCrudServiceV2 implements ProblemCrudServiceV2 { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: ProblemCrudServiceV2.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/problem-crud-v2\\"); - this.token = args.token; } public async createProblem(request: model.problemV2.CreateProblemRequestV2): Promise { @@ -8228,7 +8296,7 @@ export class ProblemCrudServiceV2 implements ProblemCrudServiceV2 { "type": "file", }, Object { - "contents": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "contents": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../../model\\"; @@ -8239,15 +8307,21 @@ export interface ProblemInfoServicV2 { getProblemVersion(request: model.problemV2.GetProblemVersionRequest): Promise; } +export declare namespace ProblemInfoServicV2 { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class ProblemInfoServicV2 implements ProblemInfoServicV2 { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: ProblemInfoServicV2.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/problems-v2\\"); - this.token = args.token; } public async getLightweightProblems(): Promise { diff --git a/packages/fern-typescript/client/src/__test__/__snapshots__/generateClientProject.test.ts.snap b/packages/fern-typescript/client/src/__test__/__snapshots__/generateClientProject.test.ts.snap index 61a1eb92561..9194c28ca36 100644 --- a/packages/fern-typescript/client/src/__test__/__snapshots__/generateClientProject.test.ts.snap +++ b/packages/fern-typescript/client/src/__test__/__snapshots__/generateClientProject.test.ts.snap @@ -276,7 +276,7 @@ export * from \\"./GetResponse\\"; \\"build\\": \\"esbuild $(find . -name '*.ts' -not -path './node_modules/*') --format=cjs --sourcemap --outdir=. && tsc\\" }, \\"dependencies\\": { - \\"@fern-typescript/service-utils\\": \\"0.0.114\\", + \\"@fern-typescript/service-utils\\": \\"0.0.124\\", \\"url-join\\": \\"5.0.0\\" }, \\"devDependencies\\": { @@ -285,7 +285,7 @@ export * from \\"./GetResponse\\"; \\"typescript\\": \\"4.6.4\\" } }", - "/services/NoErrorsService.ts": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "/services/NoErrorsService.ts": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -293,15 +293,21 @@ export interface NoErrorsService { get(): Promise; } +export declare namespace NoErrorsService { + interface Init { + origin: string; + fetcher?: Fetcher; + } +} + export class NoErrorsService implements NoErrorsService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; - constructor(args: Service.Init) { + constructor(args: NoErrorsService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"no-errors-service\\"); - this.token = args.token; } public async get(): Promise { @@ -378,6 +384,13 @@ export const CreatePostErrorBody = { "/model/_service-types/PostsService/CreatePostRequest.ts": "import * as model from \\"../..\\"; export interface CreatePostRequest { + \\"X-Endpoint-Header\\": string; + body: model.CreatePostRequestBody; +} +", + "/model/_service-types/PostsService/CreatePostRequestBody.ts": "import * as model from \\"../..\\"; + +export interface CreatePostRequestBody { type: model.PostType; title: string; author: model.Author; @@ -510,7 +523,8 @@ namespace GetPostV2Response { } } ", - "/model/_service-types/PostsService/index.ts": "export * from \\"./CreatePostRequest\\"; + "/model/_service-types/PostsService/index.ts": "export * from \\"./CreatePostRequestBody\\"; +export * from \\"./CreatePostRequest\\"; export * from \\"./CreatePostErrorBody\\"; export * from \\"./CreatePostResponse\\"; export * from \\"./GetPostRequest\\"; @@ -638,7 +652,7 @@ export * from \\"./_service-types\\"; \\"dependencies\\": { \\"uuid\\": \\"8.3.2\\", \\"@types/uuid\\": \\"8.3.4\\", - \\"@fern-typescript/service-utils\\": \\"0.0.114\\", + \\"@fern-typescript/service-utils\\": \\"0.0.124\\", \\"url-join\\": \\"5.0.0\\" }, \\"devDependencies\\": { @@ -647,7 +661,7 @@ export * from \\"./_service-types\\"; \\"typescript\\": \\"4.6.4\\" } }", - "/services/PostsService.ts": "import { isResponseOk, defaultFetcher, Service, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; + "/services/PostsService.ts": "import { isResponseOk, defaultFetcher, MaybeGetter, Token, Fetcher } from \\"@fern-typescript/service-utils\\"; import urlJoin from \\"url-join\\"; import * as model from \\"../model\\"; @@ -657,25 +671,40 @@ export interface PostsService { getPostV2(request: model.PostId): Promise; } +export declare namespace PostsService { + interface Init { + origin: string; + fetcher?: Fetcher; + token: MaybeGetter; + headers: Headers; + } + + interface Headers { + \\"X-Service-Header\\": string; + } +} + export class PostsService implements PostsService { private baseUrl: string; private fetcher: Fetcher; private token: MaybeGetter; + private headers: PostsService.Headers; - constructor(args: Service.Init) { + constructor(args: PostsService.Init) { this.fetcher = args.fetcher ?? defaultFetcher; this.baseUrl = urlJoin(args.origin, \\"/posts\\"); this.token = args.token; + this.headers = args.headers; } public async createPost(request: model.CreatePostRequest): Promise { const encodedResponse = await this.fetcher({ url: urlJoin(this.baseUrl, \\"/\\"), method: \\"POST\\", - headers: {}, + headers: { ...this.headers, \\"X-Endpoint-Header\\": request[\\"X-Endpoint-Header\\"] }, token: this.token, body: { - content: JSON.stringify(request), + content: JSON.stringify(request.body), contentType: \\"application/json\\" } }); @@ -706,7 +735,7 @@ export class PostsService implements PostsService { const encodedResponse = await this.fetcher({ url: urlJoin(this.baseUrl, \`/\${request.postId}\`), method: \\"GET\\", - headers: {}, + headers: { ...this.headers }, token: this.token, queryParameters }); @@ -731,7 +760,7 @@ export class PostsService implements PostsService { const encodedResponse = await this.fetcher({ url: urlJoin(this.baseUrl, \\"/get\\"), method: \\"GET\\", - headers: {}, + headers: { ...this.headers }, token: this.token, body: { content: JSON.stringify(request), diff --git a/packages/fern-typescript/client/src/__test__/fixtures/posts/src/posts.yml b/packages/fern-typescript/client/src/__test__/fixtures/posts/src/posts.yml index 9c8c2fe243b..c6a05d0edbf 100644 --- a/packages/fern-typescript/client/src/__test__/fixtures/posts/src/posts.yml +++ b/packages/fern-typescript/client/src/__test__/fixtures/posts/src/posts.yml @@ -35,10 +35,14 @@ services: PostsService: base-path: /posts auth: bearer + headers: + X-Service-Header: string endpoints: createPost: method: POST path: / + headers: + X-Endpoint-Header: string request: type: properties: diff --git a/packages/fern-typescript/client/src/constants.ts b/packages/fern-typescript/client/src/constants.ts index 61e215cb776..9eb67174ac7 100644 --- a/packages/fern-typescript/client/src/constants.ts +++ b/packages/fern-typescript/client/src/constants.ts @@ -40,13 +40,6 @@ export const ClientConstants = { // TODO this should probably live with the fern service-utils // helpers in fern-typescript commons ServiceUtils: { - ServiceInit: { - Properties: { - FETCHER: "fetcher", - ORIGIN: "origin", - TOKEN: "token", - }, - }, Fetcher: { Parameters: { URL: "url", @@ -69,10 +62,26 @@ export const ClientConstants = { }, }, }, + ServiceNamespace: { + Init: { + TYPE_NAME: "Init", + Properties: { + FETCHER: "fetcher", + ORIGIN: "origin", + TOKEN: "token", + HEADERS: "headers", + }, + }, + + Headers: { + TYPE_NAME: "Headers", + }, + }, PrivateMembers: { BASE_URL: "baseUrl", FETCHER: "fetcher", TOKEN: "token", + HEADERS: "headers", }, Endpoint: { Signature: { diff --git a/packages/fern-typescript/client/src/http/addServiceConstructor.ts b/packages/fern-typescript/client/src/http/addServiceConstructor.ts new file mode 100644 index 00000000000..4f7e4cfed3e --- /dev/null +++ b/packages/fern-typescript/client/src/http/addServiceConstructor.ts @@ -0,0 +1,133 @@ +import { HttpService } from "@fern-fern/ir-model/services"; +import { DependencyManager, getReferenceToFernServiceUtilsValue, getTextOfTsNode } from "@fern-typescript/commons"; +import { ClassDeclaration, ts } from "ts-morph"; +import { ClientConstants } from "../constants"; +import { generateJoinUrlPathsCall } from "../utils/generateJoinPathsCall"; +import { doesServiceHaveAuth, doesServiceHaveHeaders } from "./utils"; + +const SERVICE_INIT_PARAMETER_NAME = "args"; + +export function addServiceConstructor({ + serviceClass, + serviceDefinition, + dependencyManager, +}: { + serviceClass: ClassDeclaration; + serviceDefinition: HttpService; + dependencyManager: DependencyManager; +}): void { + serviceClass.addConstructor({ + parameters: [ + { + name: SERVICE_INIT_PARAMETER_NAME, + type: getTextOfTsNode( + ts.factory.createTypeReferenceNode( + ts.factory.createQualifiedName( + ts.factory.createIdentifier(serviceDefinition.name.name), + ts.factory.createIdentifier(ClientConstants.HttpService.ServiceNamespace.Init.TYPE_NAME) + ) + ) + ), + }, + ], + statements: getConstructorStatements({ + serviceClass, + serviceDefinition, + dependencyManager, + }).map(getTextOfTsNode), + }); +} + +function getConstructorStatements({ + serviceClass, + serviceDefinition, + dependencyManager, +}: { + serviceClass: ClassDeclaration; + serviceDefinition: HttpService; + dependencyManager: DependencyManager; +}): ts.Statement[] { + const statements: ts.Statement[] = [ + createClassMemberAssignment({ + member: ClientConstants.HttpService.PrivateMembers.FETCHER, + initialValue: ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), + ts.factory.createIdentifier(ClientConstants.HttpService.ServiceNamespace.Init.Properties.FETCHER) + ), + ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), + getReferenceToFernServiceUtilsValue({ + value: "defaultFetcher", + dependencyManager, + referencedIn: serviceClass.getSourceFile(), + }) + ), + }), + createClassMemberAssignment({ + member: ClientConstants.HttpService.PrivateMembers.BASE_URL, + initialValue: + serviceDefinition.basePath != null + ? generateJoinUrlPathsCall({ + file: serviceClass.getSourceFile(), + dependencyManager, + paths: [ + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), + ts.factory.createIdentifier( + ClientConstants.HttpService.ServiceNamespace.Init.Properties.ORIGIN + ) + ), + ts.factory.createStringLiteral(serviceDefinition.basePath), + ], + }) + : ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), + ts.factory.createIdentifier( + ClientConstants.HttpService.ServiceNamespace.Init.Properties.ORIGIN + ) + ), + }), + ]; + + if (doesServiceHaveAuth(serviceDefinition).hasAuth) { + statements.push( + createClassMemberAssignment({ + member: ClientConstants.HttpService.PrivateMembers.TOKEN, + initialValue: ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), + ts.factory.createIdentifier(ClientConstants.HttpService.ServiceNamespace.Init.Properties.TOKEN) + ), + }) + ); + } + + if (doesServiceHaveHeaders(serviceDefinition)) { + statements.push( + createClassMemberAssignment({ + member: ClientConstants.HttpService.PrivateMembers.HEADERS, + initialValue: ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), + ts.factory.createIdentifier(ClientConstants.HttpService.ServiceNamespace.Init.Properties.HEADERS) + ), + }) + ); + } + + return statements; +} + +function createClassMemberAssignment({ + member, + initialValue, +}: { + member: string; + initialValue: ts.Expression; +}): ts.ExpressionStatement { + return ts.factory.createExpressionStatement( + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier(member)), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + initialValue + ) + ); +} diff --git a/packages/fern-typescript/client/src/http/addServiceNamespace.ts b/packages/fern-typescript/client/src/http/addServiceNamespace.ts new file mode 100644 index 00000000000..0d5186ae5c1 --- /dev/null +++ b/packages/fern-typescript/client/src/http/addServiceNamespace.ts @@ -0,0 +1,128 @@ +import { HttpService } from "@fern-fern/ir-model/services"; +import { + DependencyManager, + getReferenceToFernServiceUtilsType, + getTextOfTsKeyword, + getTextOfTsNode, +} from "@fern-typescript/commons"; +import { ModelContext } from "@fern-typescript/model-context"; +import { InterfaceDeclaration, ModuleDeclaration, SourceFile, ts } from "ts-morph"; +import { ClientConstants } from "../constants"; +import { doesServiceHaveAuth, doesServiceHaveHeaders } from "./utils"; + +export function addServiceNamespace({ + serviceFile, + service, + modelContext, + dependencyManager, +}: { + serviceFile: SourceFile; + service: HttpService; + modelContext: ModelContext; + dependencyManager: DependencyManager; +}): void { + const module = serviceFile.addModule({ + name: service.name.name, + isExported: true, + hasDeclareKeyword: true, + }); + + const initInterface = module.addInterface({ + name: ClientConstants.HttpService.ServiceNamespace.Init.TYPE_NAME, + properties: [ + { + name: ClientConstants.HttpService.ServiceNamespace.Init.Properties.ORIGIN, + type: getTextOfTsKeyword(ts.SyntaxKind.StringKeyword), + }, + { + name: ClientConstants.HttpService.ServiceNamespace.Init.Properties.FETCHER, + type: getTextOfTsNode( + getReferenceToFernServiceUtilsType({ + type: "Fetcher", + dependencyManager, + referencedIn: serviceFile, + }) + ), + hasQuestionToken: true, + }, + ], + }); + + maybeAddTokenProperty({ service, dependencyManager, serviceFile, initInterface }); + maybeAddHeadersProperty({ service, initInterface, module, modelContext }); +} + +function maybeAddTokenProperty({ + service, + dependencyManager, + serviceFile, + initInterface, +}: { + service: HttpService; + dependencyManager: DependencyManager; + serviceFile: SourceFile; + initInterface: InterfaceDeclaration; +}) { + const authInfo = doesServiceHaveAuth(service); + if (!authInfo.hasAuth) { + return; + } + + const referenceToTokenType = getReferenceToFernServiceUtilsType({ + type: "Token", + dependencyManager, + referencedIn: serviceFile, + }); + + initInterface.addProperty({ + name: ClientConstants.HttpService.ServiceNamespace.Init.Properties.TOKEN, + hasQuestionToken: authInfo.isOptional, + type: getTextOfTsNode( + getReferenceToFernServiceUtilsType({ + type: "MaybeGetter", + typeArguments: [ + authInfo.isOptional + ? ts.factory.createUnionTypeNode([ + referenceToTokenType, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword), + ]) + : referenceToTokenType, + ], + dependencyManager, + referencedIn: serviceFile, + }) + ), + }); +} + +function maybeAddHeadersProperty({ + service, + initInterface, + module, + modelContext, +}: { + service: HttpService; + initInterface: InterfaceDeclaration; + module: ModuleDeclaration; + modelContext: ModelContext; +}) { + if (doesServiceHaveHeaders(service)) { + initInterface.addProperty({ + name: ClientConstants.HttpService.ServiceNamespace.Init.Properties.HEADERS, + type: ClientConstants.HttpService.ServiceNamespace.Headers.TYPE_NAME, + }); + + module.addInterface({ + name: ClientConstants.HttpService.ServiceNamespace.Headers.TYPE_NAME, + properties: service.headers.map((header) => ({ + name: `"${header.header}"`, + type: getTextOfTsNode( + modelContext.getReferenceToType({ + reference: header.valueType, + referencedIn: module.getSourceFile(), + }) + ), + })), + }); + } +} diff --git a/packages/fern-typescript/client/src/http/endpoints/endpoint-method-body/generateFetcherCall.ts b/packages/fern-typescript/client/src/http/endpoints/endpoint-method-body/generateFetcherCall.ts index bb21607a749..05bbe81f7f5 100644 --- a/packages/fern-typescript/client/src/http/endpoints/endpoint-method-body/generateFetcherCall.ts +++ b/packages/fern-typescript/client/src/http/endpoints/endpoint-method-body/generateFetcherCall.ts @@ -5,6 +5,7 @@ import { GeneratedHttpEndpointTypes } from "@fern-typescript/model-context"; import { SourceFile, StatementStructures, StructureKind, ts, VariableDeclarationKind } from "ts-morph"; import { ClientConstants } from "../../../constants"; import { generateJoinUrlPathsCall } from "../../../utils/generateJoinPathsCall"; +import { doesServiceHaveHeaders } from "../../utils"; import { convertPathToTemplateString } from "./convertPathToTemplateString"; import { generateEncoderCall } from "./generateEncoderCall"; @@ -44,10 +45,7 @@ export async function generateFetcherCall({ ts.factory.createIdentifier(ClientConstants.HttpService.ServiceUtils.Fetcher.Parameters.METHOD), ts.factory.createStringLiteral(endpoint.method) ), - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier(ClientConstants.HttpService.ServiceUtils.Fetcher.Parameters.HEADERS), - ts.factory.createObjectLiteralExpression([]) - ), + getHeadersPropertyAssignment({ service: serviceDefinition, endpoint }), ts.factory.createPropertyAssignment( ts.factory.createIdentifier(ClientConstants.HttpService.ServiceUtils.Fetcher.Parameters.TOKEN), ts.factory.createPropertyAccessExpression( @@ -150,3 +148,55 @@ export async function generateFetcherCall({ ], }; } + +function getHeadersPropertyAssignment({ + service, + endpoint, +}: { + service: HttpService; + endpoint: HttpEndpoint; +}): ts.ObjectLiteralElementLike { + return ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(ClientConstants.HttpService.ServiceUtils.Fetcher.Parameters.HEADERS), + getHeadersPropertyValue({ service, endpoint }) + ); +} + +function getHeadersPropertyValue({ + service, + endpoint, +}: { + service: HttpService; + endpoint: HttpEndpoint; +}): ts.Expression { + if (!doesServiceHaveHeaders(service) && endpoint.headers.length === 0) { + return ts.factory.createObjectLiteralExpression([]); + } + + const properties: ts.ObjectLiteralElementLike[] = []; + + if (doesServiceHaveHeaders(service)) { + properties.push( + ts.factory.createSpreadAssignment( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(ClientConstants.HttpService.PrivateMembers.HEADERS) + ) + ) + ); + } + + for (const header of endpoint.headers) { + properties.push( + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(header.header), + ts.factory.createElementAccessExpression( + ts.factory.createIdentifier(ClientConstants.HttpService.Endpoint.Signature.REQUEST_PARAMETER), + ts.factory.createStringLiteral(header.header) + ) + ) + ); + } + + return ts.factory.createObjectLiteralExpression(properties); +} diff --git a/packages/fern-typescript/client/src/http/generateHttpService.ts b/packages/fern-typescript/client/src/http/generateHttpService.ts index 0f46a9502d0..d9d8884cae6 100644 --- a/packages/fern-typescript/client/src/http/generateHttpService.ts +++ b/packages/fern-typescript/client/src/http/generateHttpService.ts @@ -3,19 +3,19 @@ import { createDirectoriesForFernFilepath, createSourceFileAndExportFromModule, DependencyManager, - getReferenceToFernServiceUtilsServiceNamespaceType, getReferenceToFernServiceUtilsType, - getReferenceToFernServiceUtilsValue, getTextOfTsKeyword, getTextOfTsNode, maybeAddDocs, } from "@fern-typescript/commons"; import { HelperManager } from "@fern-typescript/helper-manager"; import { ModelContext } from "@fern-typescript/model-context"; -import { ClassDeclaration, Directory, Scope, ts } from "ts-morph"; +import { Directory, Scope, ts } from "ts-morph"; import { ClientConstants } from "../constants"; -import { generateJoinUrlPathsCall } from "../utils/generateJoinPathsCall"; +import { addServiceConstructor } from "./addServiceConstructor"; +import { addServiceNamespace } from "./addServiceNamespace"; import { addEndpointToService } from "./endpoints/addEndpointToService"; +import { doesServiceHaveHeaders } from "./utils"; export async function generateHttpService({ servicesDirectory, @@ -38,6 +38,8 @@ export async function generateHttpService({ isExported: true, }); + addServiceNamespace({ serviceFile, service, modelContext, dependencyManager }); + const serviceClass = serviceFile.addClass({ name: service.name.name, implements: [service.name.name], @@ -81,7 +83,22 @@ export async function generateHttpService({ ), }); - addConstructor({ serviceClass, serviceDefinition: service, dependencyManager }); + if (doesServiceHaveHeaders(service)) { + serviceClass.addProperty({ + name: ClientConstants.HttpService.PrivateMembers.HEADERS, + scope: Scope.Private, + type: getTextOfTsNode( + ts.factory.createTypeReferenceNode( + ts.factory.createQualifiedName( + ts.factory.createIdentifier(service.name.name), + ts.factory.createIdentifier(ClientConstants.HttpService.ServiceNamespace.Headers.TYPE_NAME) + ) + ) + ), + }); + } + + addServiceConstructor({ serviceClass, serviceDefinition: service, dependencyManager }); for (const endpoint of service.endpoints) { await addEndpointToService({ @@ -95,105 +112,3 @@ export async function generateHttpService({ }); } } -function addConstructor({ - serviceClass, - serviceDefinition, - dependencyManager, -}: { - serviceClass: ClassDeclaration; - serviceDefinition: HttpService; - dependencyManager: DependencyManager; -}) { - const SERVICE_INIT_PARAMETER_NAME = "args"; - - const serviceFile = serviceClass.getSourceFile(); - - serviceClass.addConstructor({ - parameters: [ - { - name: SERVICE_INIT_PARAMETER_NAME, - type: getTextOfTsNode( - getReferenceToFernServiceUtilsServiceNamespaceType({ - type: "Init", - dependencyManager, - referencedIn: serviceFile, - }) - ), - }, - ], - statements: [ - getTextOfTsNode( - createClassMemberAssignment({ - member: ClientConstants.HttpService.PrivateMembers.FETCHER, - initialValue: ts.factory.createBinaryExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), - ts.factory.createIdentifier( - ClientConstants.HttpService.ServiceUtils.ServiceInit.Properties.FETCHER - ) - ), - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), - getReferenceToFernServiceUtilsValue({ - value: "defaultFetcher", - dependencyManager, - referencedIn: serviceFile, - }) - ), - }) - ), - getTextOfTsNode( - createClassMemberAssignment({ - member: ClientConstants.HttpService.PrivateMembers.BASE_URL, - initialValue: - serviceDefinition.basePath != null - ? generateJoinUrlPathsCall({ - file: serviceClass.getSourceFile(), - dependencyManager, - paths: [ - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), - ts.factory.createIdentifier( - ClientConstants.HttpService.ServiceUtils.ServiceInit.Properties.ORIGIN - ) - ), - ts.factory.createStringLiteral(serviceDefinition.basePath), - ], - }) - : ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), - ts.factory.createIdentifier( - ClientConstants.HttpService.ServiceUtils.ServiceInit.Properties.ORIGIN - ) - ), - }) - ), - getTextOfTsNode( - createClassMemberAssignment({ - member: ClientConstants.HttpService.PrivateMembers.TOKEN, - initialValue: ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(SERVICE_INIT_PARAMETER_NAME), - ts.factory.createIdentifier( - ClientConstants.HttpService.ServiceUtils.ServiceInit.Properties.TOKEN - ) - ), - }) - ), - ], - }); -} - -function createClassMemberAssignment({ - member, - initialValue, -}: { - member: string; - initialValue: ts.Expression; -}): ts.ExpressionStatement { - return ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier(member)), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - initialValue - ) - ); -} diff --git a/packages/fern-typescript/client/src/http/utils.ts b/packages/fern-typescript/client/src/http/utils.ts new file mode 100644 index 00000000000..f0ea7d33b67 --- /dev/null +++ b/packages/fern-typescript/client/src/http/utils.ts @@ -0,0 +1,23 @@ +import { HttpAuth, HttpService } from "@fern-fern/ir-model/services"; + +export function doesServiceHaveHeaders(service: HttpService): boolean { + return service.headers.length > 0; +} + +export function doesServiceHaveAuth(service: HttpService): { hasAuth: false } | { hasAuth: true; isOptional: boolean } { + let someEndpointHasAuth = false; + let allEndpointsHaveAuth = true; + for (const endpoint of service.endpoints) { + if (endpoint.auth === HttpAuth.None) { + allEndpointsHaveAuth = false; + } else { + someEndpointHasAuth = true; + } + } + + if (!someEndpointHasAuth) { + return { hasAuth: false }; + } else { + return { hasAuth: true, isOptional: !allEndpointsHaveAuth }; + } +} diff --git a/packages/fern-typescript/commons/src/dependencies/getReferenceToFernServiceUtils.ts b/packages/fern-typescript/commons/src/dependencies/getReferenceToFernServiceUtils.ts index 26ab2663707..fab68e79ce0 100644 --- a/packages/fern-typescript/commons/src/dependencies/getReferenceToFernServiceUtils.ts +++ b/packages/fern-typescript/commons/src/dependencies/getReferenceToFernServiceUtils.ts @@ -2,14 +2,11 @@ import { SourceFile, ts } from "ts-morph"; import { DependencyManager } from "./DependencyManager"; const PACKAGE_NAME = "@fern-typescript/service-utils"; -const VERSION = "0.0.114"; +const VERSION = "0.0.124"; -type FernServiceUtilsExport = - | ExportedFernServiceUtilsType - | ExportedFernServiceUtilsValue - | typeof SERVICE_NAMESPACE_NAME; +type FernServiceUtilsExport = ExportedFernServiceUtilsType | ExportedFernServiceUtilsValue; -export type ExportedFernServiceUtilsType = "Fetcher" | "Token" | "MaybePromise" | "MaybeGetter"; +export type ExportedFernServiceUtilsType = "Fetcher" | "Token" | "MaybeGetter"; export function getReferenceToFernServiceUtilsType({ type, @@ -41,27 +38,6 @@ export function getReferenceToFernServiceUtilsValue({ return ts.factory.createIdentifier(value); } -const SERVICE_NAMESPACE_NAME = "Service"; -export type ExportedServiceNamespaceType = "Init"; - -export function getReferenceToFernServiceUtilsServiceNamespaceType({ - type, - dependencyManager, - referencedIn, -}: { - type: ExportedServiceNamespaceType; - dependencyManager: DependencyManager; - referencedIn: SourceFile; -}): ts.TypeNode { - addFernServiceUtilsImport({ imports: [SERVICE_NAMESPACE_NAME], file: referencedIn, dependencyManager }); - return ts.factory.createTypeReferenceNode( - ts.factory.createQualifiedName( - ts.factory.createIdentifier(SERVICE_NAMESPACE_NAME), - ts.factory.createIdentifier(type) - ) - ); -} - const TOKEN_UTILS_NAME = "Token"; type TokenUtil = "of" | "fromAuthorizationHeader"; diff --git a/packages/fern-typescript/commons/src/index.ts b/packages/fern-typescript/commons/src/index.ts index 1d987f059de..737301f6882 100644 --- a/packages/fern-typescript/commons/src/index.ts +++ b/packages/fern-typescript/commons/src/index.ts @@ -1,7 +1,6 @@ export { DependencyManager, DependencyType } from "./dependencies/DependencyManager"; export { generateUuidCall } from "./dependencies/generateUuidCall"; export { - getReferenceToFernServiceUtilsServiceNamespaceType, getReferenceToFernServiceUtilsTokenMethod, getReferenceToFernServiceUtilsType, getReferenceToFernServiceUtilsValue, diff --git a/packages/fern-typescript/server/src/__test__/__snapshots__/generateServerProject.test.ts.snap b/packages/fern-typescript/server/src/__test__/__snapshots__/generateServerProject.test.ts.snap index 5c269e1fbeb..18106f6ba99 100644 --- a/packages/fern-typescript/server/src/__test__/__snapshots__/generateServerProject.test.ts.snap +++ b/packages/fern-typescript/server/src/__test__/__snapshots__/generateServerProject.test.ts.snap @@ -33,6 +33,13 @@ export const CreatePostErrorBody = { "/model/_service-types/PostsService/CreatePostRequest.ts": "import * as model from \\"../..\\"; export interface CreatePostRequest { + \\"X-Endpoint-Header\\": string; + body: model.CreatePostRequestBody; +} +", + "/model/_service-types/PostsService/CreatePostRequestBody.ts": "import * as model from \\"../..\\"; + +export interface CreatePostRequestBody { type: model.PostType; title: string; author: model.Author; @@ -264,7 +271,8 @@ namespace NoopWithoutAuthResponse { } } ", - "/model/_service-types/PostsService/index.ts": "export * from \\"./CreatePostRequest\\"; + "/model/_service-types/PostsService/index.ts": "export * from \\"./CreatePostRequestBody\\"; +export * from \\"./CreatePostRequest\\"; export * from \\"./CreatePostErrorBody\\"; export * from \\"./CreatePostResponse\\"; export * from \\"./GetPostRequest\\"; @@ -399,7 +407,7 @@ export * from \\"./_service-types\\"; \\"dependencies\\": { \\"uuid\\": \\"8.3.2\\", \\"@types/uuid\\": \\"8.3.4\\", - \\"@fern-typescript/service-utils\\": \\"0.0.114\\", + \\"@fern-typescript/service-utils\\": \\"0.0.124\\", \\"express\\": \\"4.18.1\\", \\"@types/express\\": \\"4.17.13\\" }, @@ -414,16 +422,16 @@ export * from \\"./_service-types\\"; } }", "/services/PostsService.ts": "import * as model from \\"../model\\"; -import { MaybePromise, Token } from \\"@fern-typescript/service-utils\\"; +import { Token } from \\"@fern-typescript/service-utils\\"; import express, { Express } from \\"express\\"; export interface PostsService { - createPost(request: model.CreatePostRequest): MaybePromise; - getPost(token: Token, request: model.GetPostRequest): MaybePromise; - getPostV2(token: Token, request: model.PostId): MaybePromise; - deletePost(token: Token, request: model.DeletePostRequest): MaybePromise; - noopWithAuth(token: Token): MaybePromise; - noopWithoutAuth(): MaybePromise; + createPost(request: model.CreatePostRequest): model.CreatePostResponse | Promise; + getPost(token: Token, request: model.GetPostRequest): model.GetPostResponse | Promise; + getPostV2(token: Token, request: model.PostId): model.GetPostV2Response | Promise; + deletePost(token: Token, request: model.DeletePostRequest): model.DeletePostResponse | Promise; + noopWithAuth(token: Token): model.NoopWithAuthResponse | Promise; + noopWithoutAuth(): model.NoopWithoutAuthResponse | Promise; } export const PostsService = { @@ -431,7 +439,10 @@ export const PostsService = { const app = express(); app.use(express.json()); app.post(\\"/posts/\\", async (request, response) => { - const result = await impl.createPost(request.body); + const result = await impl.createPost({ + \\"X-Endpoint-Header\\": request.header(\\"X-Endpoint-Header\\")!, + body: request.body + }); if (result.ok) { response.send(result.body); } @@ -440,7 +451,7 @@ export const PostsService = { } }); app.get(\\"/posts/:postId\\", async (request, response) => { - const result = await impl.getPost(Token.fromAuthorizationHeader(request.headers[\\"Authorization\\"] as string), { + const result = await impl.getPost(Token.fromAuthorizationHeader(request.header(\\"Authorization\\")!), { postId: model.PostId.of(request.params.postId), page: request.query.page != null ? Number(request.query.page) : undefined, otherParam: Number(request.query.otherParam) @@ -457,7 +468,7 @@ export const PostsService = { } }); app.get(\\"/posts/get\\", async (request, response) => { - const result = await impl.getPostV2(Token.fromAuthorizationHeader(request.headers[\\"Authorization\\"] as string), request.body); + const result = await impl.getPostV2(Token.fromAuthorizationHeader(request.header(\\"Authorization\\")!), request.body); if (result.ok) { response.send(result.body); } @@ -470,7 +481,7 @@ export const PostsService = { } }); app.delete(\\"/posts/:postId\\", async (request, response) => { - const result = await impl.deletePost(Token.fromAuthorizationHeader(request.headers[\\"Authorization\\"] as string), { + const result = await impl.deletePost(Token.fromAuthorizationHeader(request.header(\\"Authorization\\")!), { postId: model.PostId.of(request.params.postId) }); if (result.ok) { @@ -481,7 +492,7 @@ export const PostsService = { } }); app.get(\\"/posts/\\", async (request, response) => { - const result = await impl.noopWithAuth(Token.fromAuthorizationHeader(request.headers[\\"Authorization\\"] as string)); + const result = await impl.noopWithAuth(Token.fromAuthorizationHeader(request.header(\\"Authorization\\")!)); if (result.ok) { response.end(); } diff --git a/packages/fern-typescript/server/src/__test__/fixtures/posts/src/posts.yml b/packages/fern-typescript/server/src/__test__/fixtures/posts/src/posts.yml index b924ef33693..05eff4cf269 100644 --- a/packages/fern-typescript/server/src/__test__/fixtures/posts/src/posts.yml +++ b/packages/fern-typescript/server/src/__test__/fixtures/posts/src/posts.yml @@ -40,6 +40,8 @@ services: method: POST path: / auth-override: none + headers: + X-Endpoint-Header: string request: type: properties: diff --git a/packages/fern-typescript/server/src/constants.ts b/packages/fern-typescript/server/src/constants.ts index 1ebffaa56d4..94304aa0a21 100644 --- a/packages/fern-typescript/server/src/constants.ts +++ b/packages/fern-typescript/server/src/constants.ts @@ -15,7 +15,10 @@ export const ServerConstants = { PARAMS: "params", QUERY_PARAMS: "query", BODY: "body", - HEADERS: "headers", + }, + + RequestMethods: { + HEADER: "header", }, ResponseMethods: { diff --git a/packages/fern-typescript/server/src/http/addExpressRouteStatement.ts b/packages/fern-typescript/server/src/http/addExpressRouteStatement.ts index 4ff7d9e7e5b..e6d15a708b5 100644 --- a/packages/fern-typescript/server/src/http/addExpressRouteStatement.ts +++ b/packages/fern-typescript/server/src/http/addExpressRouteStatement.ts @@ -67,6 +67,7 @@ export function getExpressRouteStatement({ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createBlock( generateEndpointBody({ + service, endpoint, generatedEndpointTypes, modelContext, @@ -118,12 +119,14 @@ function generateExpressRoutePath({ } function generateEndpointBody({ + service, endpoint, generatedEndpointTypes, modelContext, file, dependencyManager, }: { + service: HttpService; endpoint: HttpEndpoint; generatedEndpointTypes: GeneratedHttpEndpointTypes; modelContext: ModelContext; @@ -141,7 +144,14 @@ function generateEndpointBody({ ), undefined, undefined, - generateImplCall({ endpoint, generatedEndpointTypes, modelContext, dependencyManager, file }) + generateImplCall({ + service, + endpoint, + generatedEndpointTypes, + modelContext, + dependencyManager, + file, + }) ), ], ts.NodeFlags.Const diff --git a/packages/fern-typescript/server/src/http/generateHttpService.ts b/packages/fern-typescript/server/src/http/generateHttpService.ts index 6f89a394af6..eea46b9574e 100644 --- a/packages/fern-typescript/server/src/http/generateHttpService.ts +++ b/packages/fern-typescript/server/src/http/generateHttpService.ts @@ -99,8 +99,6 @@ function addEndpointToService({ reference: generatedEndpointTypes.response.reference, referencedIn: serviceFile, }), - dependencyManager, - file: serviceFile, }) ), }); diff --git a/packages/fern-typescript/server/src/http/generateImplCall.ts b/packages/fern-typescript/server/src/http/generateImplCall.ts index 8bfe6123596..1ab24b10c16 100644 --- a/packages/fern-typescript/server/src/http/generateImplCall.ts +++ b/packages/fern-typescript/server/src/http/generateImplCall.ts @@ -1,4 +1,4 @@ -import { HttpAuth, HttpEndpoint } from "@fern-fern/ir-model/services"; +import { HttpAuth, HttpEndpoint, HttpService } from "@fern-fern/ir-model/services"; import { DependencyManager, getReferenceToFernServiceUtilsTokenMethod } from "@fern-typescript/commons"; import { GeneratedHttpEndpointTypes, ModelContext } from "@fern-typescript/model-context"; import { SourceFile, ts } from "ts-morph"; @@ -6,19 +6,28 @@ import { ServerConstants } from "../constants"; import { convertParamValueForExpectedType } from "./convertParamValueForExpectedType"; export function generateImplCall({ + service, endpoint, generatedEndpointTypes, modelContext, file, dependencyManager, }: { + service: HttpService; endpoint: HttpEndpoint; generatedEndpointTypes: GeneratedHttpEndpointTypes; modelContext: ModelContext; file: SourceFile; dependencyManager: DependencyManager; }): ts.Expression { - const args = generateImplCallArguments({ endpoint, generatedEndpointTypes, modelContext, dependencyManager, file }); + const args = generateImplCallArguments({ + service, + endpoint, + generatedEndpointTypes, + modelContext, + dependencyManager, + file, + }); return ts.factory.createAwaitExpression( ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( @@ -32,12 +41,14 @@ export function generateImplCall({ } function generateImplCallArguments({ + service, endpoint, generatedEndpointTypes, modelContext, file, dependencyManager, }: { + service: HttpService; endpoint: HttpEndpoint; generatedEndpointTypes: GeneratedHttpEndpointTypes; modelContext: ModelContext; @@ -46,7 +57,7 @@ function generateImplCallArguments({ }): ts.Expression[] { return [ ...generateImplAuthArguments({ endpoint, dependencyManager, file }), - ...generateImplRequestArguments({ endpoint, generatedEndpointTypes, modelContext, file }), + ...generateImplRequestArguments({ service, endpoint, generatedEndpointTypes, modelContext, file }), ]; } @@ -69,20 +80,7 @@ function generateImplAuthArguments({ referencedIn: file, }), undefined, - [ - ts.factory.createAsExpression( - ts.factory.createElementAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier( - ServerConstants.Middleware.EndpointImplementation.Request.PARAMETER_NAME - ), - ts.factory.createIdentifier(ServerConstants.Express.RequestProperties.HEADERS) - ), - ts.factory.createStringLiteral("Authorization") - ), - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) - ), - ] + [ts.factory.createNonNullExpression(getRequestHeader("Authorization"))] ), ]; }, @@ -94,11 +92,13 @@ function generateImplAuthArguments({ } function generateImplRequestArguments({ + service, endpoint, generatedEndpointTypes, modelContext, file, }: { + service: HttpService; endpoint: HttpEndpoint; generatedEndpointTypes: GeneratedHttpEndpointTypes; modelContext: ModelContext; @@ -148,6 +148,18 @@ function generateImplRequestArguments({ }) ) ), + ...[...service.headers, ...endpoint.headers].map((header) => + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(header.header), + convertParamValueForExpectedType({ + valueReference: ts.factory.createNonNullExpression(getRequestHeader(header.header)), + isValueReferenceTypedAsString: true, + modelContext, + expectedType: header.valueType, + file, + }) + ) + ), ]; if (generatedEndpointTypes.request.body != null) { @@ -178,3 +190,14 @@ function generateImplRequestArguments({ return []; } + +function getRequestHeader(header: string) { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(ServerConstants.Middleware.EndpointImplementation.Request.PARAMETER_NAME), + ts.factory.createIdentifier(ServerConstants.Express.RequestMethods.HEADER) + ), + undefined, + [ts.factory.createStringLiteral(header)] + ); +} diff --git a/packages/fern-typescript/server/src/utils.ts/generateMaybePromise.ts b/packages/fern-typescript/server/src/utils.ts/generateMaybePromise.ts index f4f9cbdf36c..ea8a1557786 100644 --- a/packages/fern-typescript/server/src/utils.ts/generateMaybePromise.ts +++ b/packages/fern-typescript/server/src/utils.ts/generateMaybePromise.ts @@ -1,19 +1,8 @@ -import { DependencyManager, getReferenceToFernServiceUtilsType } from "@fern-typescript/commons"; -import { SourceFile, ts } from "ts-morph"; +import { ts } from "ts-morph"; -export function generateMaybePromise({ - type, - dependencyManager, - file, -}: { - type: ts.TypeNode; - dependencyManager: DependencyManager; - file: SourceFile; -}): ts.TypeNode { - return getReferenceToFernServiceUtilsType({ - type: "MaybePromise", - dependencyManager, - referencedIn: file, - typeArguments: [type], - }); +export function generateMaybePromise({ type }: { type: ts.TypeNode }): ts.TypeNode { + return ts.factory.createUnionTypeNode([ + type, + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Promise"), [type]), + ]); } diff --git a/packages/fern-typescript/service-types/src/http/generateRequestTypes.ts b/packages/fern-typescript/service-types/src/http/generateRequestTypes.ts index 935e61d924f..1985a504c9d 100644 --- a/packages/fern-typescript/service-types/src/http/generateRequestTypes.ts +++ b/packages/fern-typescript/service-types/src/http/generateRequestTypes.ts @@ -18,11 +18,12 @@ export function generateRequestTypes({ serviceName, modelContext, }: generateRequestTypes.Args): GeneratedRequest { - const getAdditionalProperties = [ + const additionalProperties = [ ...[...endpoint.pathParameters, ...endpoint.queryParameters].map( (parameter) => (requestFile: SourceFile): OptionalKind => ({ name: parameter.key, + docs: parameter.docs != null ? [parameter.docs] : undefined, type: getTextOfTsNode( modelContext.getReferenceToType({ reference: parameter.valueType, @@ -31,6 +32,19 @@ export function generateRequestTypes({ ), }) ), + ...endpoint.headers.map( + (header) => + (requestFile: SourceFile): OptionalKind => ({ + name: `"${header.header}"`, + docs: header.docs != null ? [header.docs] : undefined, + type: getTextOfTsNode( + modelContext.getReferenceToType({ + reference: header.valueType, + referencedIn: requestFile, + }) + ), + }) + ), ]; return generateRequest({ @@ -44,7 +58,7 @@ export function generateRequestTypes({ type: endpoint.request.type, docs: endpoint.request.docs, }, - additionalProperties: getAdditionalProperties, + additionalProperties, writeServiceTypeFile: createHttpServiceTypeFileWriter({ modelContext, serviceName, endpoint }), }); } diff --git a/packages/fern-typescript/service-utils/src/Fetcher.ts b/packages/fern-typescript/service-utils/src/Fetcher.ts index d3ab3630446..49e5cff4d16 100644 --- a/packages/fern-typescript/service-utils/src/Fetcher.ts +++ b/packages/fern-typescript/service-utils/src/Fetcher.ts @@ -6,7 +6,7 @@ export declare namespace Fetcher { export interface Args { url: string; method: string; - headers?: Record; + headers: Record; token?: MaybeGetter; queryParameters?: URLSearchParams; body?: { diff --git a/packages/fern-typescript/service-utils/src/MaybePromise.ts b/packages/fern-typescript/service-utils/src/MaybePromise.ts deleted file mode 100644 index 9cd354b3418..00000000000 --- a/packages/fern-typescript/service-utils/src/MaybePromise.ts +++ /dev/null @@ -1 +0,0 @@ -export type MaybePromise = T | Promise; diff --git a/packages/fern-typescript/service-utils/src/index.ts b/packages/fern-typescript/service-utils/src/index.ts index cb3080dca91..561e9218887 100644 --- a/packages/fern-typescript/service-utils/src/index.ts +++ b/packages/fern-typescript/service-utils/src/index.ts @@ -2,6 +2,5 @@ export { defaultFetcher } from "./defaultFetcher"; export { type Fetcher } from "./Fetcher"; export { isResponseOk } from "./isResponseOk"; export { type MaybeGetter } from "./MaybeGetter"; -export { type MaybePromise } from "./MaybePromise"; export { type Service } from "./Service"; export { Token } from "./Token"; diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000000..7ae9a3de54d --- /dev/null +++ b/vercel.json @@ -0,0 +1,5 @@ +{ + "github": { + "silent": true + } +}