Skip to content

Commit

Permalink
Generate methods
Browse files Browse the repository at this point in the history
  • Loading branch information
razor-x committed Sep 26, 2023
1 parent 1b3d888 commit 1fe40fc
Show file tree
Hide file tree
Showing 23 changed files with 1,749 additions and 33 deletions.
114 changes: 82 additions & 32 deletions generate-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { resolve } from 'node:path'
import { openapi } from '@seamapi/types/connect'
import { camelCase, paramCase, pascalCase, snakeCase } from 'change-case'
import { ESLint } from 'eslint'
import pluralize from 'pluralize'
import { format, resolveConfig } from 'prettier'

const rootClassPath = resolve('src', 'lib', 'seam', 'connect', 'client.ts')
Expand Down Expand Up @@ -49,36 +50,17 @@ interface Endpoint {
name: string
path: string
namespace: string
resource: string
method: 'GET' | 'POST'
resource: string | null
method: Method
requestFormat: 'params' | 'body'
}

type Method = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'

interface ClassMeta {
constructors: string
}

const exampleRoute: Route = {
namespace: 'workspaces',
endpoints: [
{
name: 'get',
namespace: 'workspaces',
path: '/workspaces/get',
method: 'GET',
resource: 'workspace',
requestFormat: ['GET', 'DELETE'].includes('GET') ? 'params' : 'body',
},
],
}

const isEndpointUnderRoute = (
endpointPath: string,
routePath: string,
): boolean =>
endpointPath.startsWith(routePath) &&
endpointPath.split('/').length - 1 === routePath.split('/').length

const createRoutes = (): Route[] => {
const paths = Object.keys(openapi.paths)

Expand All @@ -102,13 +84,75 @@ const createRoutes = (): Route[] => {
}

const createRoute = (routePath: string): Route => {
const endpointPaths = Object.keys(openapi.paths).filter((path) =>
isEndpointUnderRoute(path, routePath),
)

const namespace = routePath.split('/').join('_').slice(1)

return {
namespace: routePath.split('/').join('_').slice(1),
endpoints: [],
namespace,
endpoints: endpointPaths.map((endpointPath) =>
createEndpoint(namespace, routePath, endpointPath),
),
}
}

const routes = createRoutes()
const createEndpoint = (
namespace: string,
routePath: string,
endpointPath: string,
): Endpoint => {
if (!isOpenApiPath(endpointPath)) {
throw new Error(`Did not find ${endpointPath} in OpenAPI spec`)
}
const spec = openapi.paths[endpointPath]
const method = deriveSemanticMethod(Object.keys(spec))
const name = endpointPath.split(routePath)[1]?.slice(1)
if (name == null) {
throw new Error(`Could not parse name from ${endpointPath}`)
}
return {
name,
namespace,
path: endpointPath,
method,
resource: deriveResource(routePath, name, method),
requestFormat: ['GET', 'DELETE'].includes(method) ? 'params' : 'body',
}
}

const deriveResource = (
routePath: string,
name: string,
method: Method,
): string | null => {
if (['DELETE', 'PATCH', 'PUT'].includes(method)) return null
if (['update', 'delete'].includes(name)) return null
const group = routePath.split('/')[1]
if (group == null) throw new Error(`Could not parse group from ${routePath}`)
if (name === 'list') return group
return pluralize.singular(group)
}

const deriveSemanticMethod = (methods: string[]): Method => {
if (methods.includes('get')) return 'GET'
if (methods.includes('delete')) return 'DELETE'
if (methods.includes('patch')) return 'PATCH'
if (methods.includes('put')) return 'PUT'
if (methods.includes('post')) return 'POST'
throw new Error(`Could not find valid method in ${methods.join(', ')}`)
}

const isOpenApiPath = (key: string): key is keyof typeof openapi.paths =>
key in openapi.paths

const isEndpointUnderRoute = (
endpointPath: string,
routePath: string,
): boolean =>
endpointPath.startsWith(routePath) &&
endpointPath.split('/').length - 1 === routePath.split('/').length

const renderRoute = (route: Route, { constructors }: ClassMeta): string => `
/*
Expand All @@ -125,7 +169,7 @@ ${renderExports(route)}

const renderImports = (): string =>
`
import type { RouteRequestParams, RouteResponse } from '@seamapi/types/connect'
import type { RouteRequestParams, RouteResponse, RouteRequestBody } from '@seamapi/types/connect'
import { Axios } from 'axios'
import type { SetNonNullable } from 'type-fest'
Expand Down Expand Up @@ -173,9 +217,15 @@ const renderClassMethod = ({
name,
namespace,
requestFormat,
})} = {},
): Promise<${renderResponseType({ name, namespace })}['${resource}']> {
const { data } = await this.client.request<${renderResponseType({
})},
): Promise<${
resource === null
? 'void'
: `${renderResponseType({ name, namespace })}['${resource}']`
}> {
${
resource === null ? '' : 'const { data } = '
}await this.client.request<${renderResponseType({
name,
namespace,
})}>({
Expand All @@ -184,7 +234,7 @@ const renderClassMethod = ({
requestFormat === 'params' ? 'params,' : ''
} ${requestFormat === 'body' ? 'data: body,' : ''}
})
return data.${resource}
${resource === null ? '' : `return data.${resource}`}
}
`

Expand Down Expand Up @@ -298,4 +348,4 @@ const writeRoute = async (route: Route): Promise<void> => {
)
}

await Promise.all(routes.map(writeRoute))
await Promise.all(createRoutes().map(writeRoute))
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"@seamapi/types": "^1.14.0",
"@types/eslint": "^8.44.2",
"@types/node": "^18.11.18",
"@types/pluralize": "^0.0.31",
"ava": "^5.0.1",
"c8": "^8.0.0",
"change-case": "^4.1.2",
Expand All @@ -102,6 +103,7 @@
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"landlubber": "^1.0.0",
"pluralize": "^8.0.0",
"prettier": "^3.0.0",
"tsc-alias": "^1.8.2",
"tsup": "^7.2.0",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/seam/connect/legacy/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class SeamHttpLegacyWorkspaces {
}

async get(
params: WorkspacesGetParams = {},
params: WorkspacesGetParams,
): Promise<WorkspacesGetResponse['workspace']> {
const { data } = await this.client.request<WorkspacesGetResponse>({
url: '/workspaces/get',
Expand Down
93 changes: 93 additions & 0 deletions src/lib/seam/connect/routes/access-codes-unmanaged.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
* Do not edit this file or add other files to this directory.
*/

import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect'
import type { Axios } from 'axios'
import type { SetNonNullable } from 'type-fest'

import { createAxiosClient } from 'lib/seam/connect/axios.js'
import {
Expand Down Expand Up @@ -61,4 +63,95 @@ export class SeamHttpAccessCodesUnmanaged {
}
return new SeamHttpAccessCodesUnmanaged(opts)
}

async convertToManaged(
body: AccessCodesUnmanagedConvertToManagedBody,
): Promise<void> {
await this.client.request<AccessCodesUnmanagedConvertToManagedResponse>({
url: '/access_codes/unmanaged/convert_to_managed',
method: 'patch',
data: body,
})
}

async delete(body: AccessCodesUnmanagedDeleteBody): Promise<void> {
await this.client.request<AccessCodesUnmanagedDeleteResponse>({
url: '/access_codes/unmanaged/delete',
method: 'post',
data: body,
})
}

async get(
body: AccessCodesUnmanagedGetBody,
): Promise<AccessCodesUnmanagedGetResponse['access_code']> {
const { data } = await this.client.request<AccessCodesUnmanagedGetResponse>(
{
url: '/access_codes/unmanaged/get',
method: 'post',
data: body,
},
)
return data.access_code
}

async list(
body: AccessCodesUnmanagedListBody,
): Promise<AccessCodesUnmanagedListResponse['access_codes']> {
const { data } =
await this.client.request<AccessCodesUnmanagedListResponse>({
url: '/access_codes/unmanaged/list',
method: 'post',
data: body,
})
return data.access_codes
}

async update(body: AccessCodesUnmanagedUpdateBody): Promise<void> {
await this.client.request<AccessCodesUnmanagedUpdateResponse>({
url: '/access_codes/unmanaged/update',
method: 'patch',
data: body,
})
}
}

type AccessCodesUnmanagedConvertToManagedBody = SetNonNullable<
Required<RouteRequestBody<'/access_codes/unmanaged/convert_to_managed'>>
>

type AccessCodesUnmanagedConvertToManagedResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/convert_to_managed'>>
>

type AccessCodesUnmanagedDeleteBody = SetNonNullable<
Required<RouteRequestBody<'/access_codes/unmanaged/delete'>>
>

type AccessCodesUnmanagedDeleteResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/delete'>>
>

type AccessCodesUnmanagedGetBody = SetNonNullable<
Required<RouteRequestBody<'/access_codes/unmanaged/get'>>
>

type AccessCodesUnmanagedGetResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/get'>>
>

type AccessCodesUnmanagedListBody = SetNonNullable<
Required<RouteRequestBody<'/access_codes/unmanaged/list'>>
>

type AccessCodesUnmanagedListResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/list'>>
>

type AccessCodesUnmanagedUpdateBody = SetNonNullable<
Required<RouteRequestBody<'/access_codes/unmanaged/update'>>
>

type AccessCodesUnmanagedUpdateResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/update'>>
>
Loading

0 comments on commit 1fe40fc

Please sign in to comment.