Skip to content

Commit

Permalink
Add fromClient static method
Browse files Browse the repository at this point in the history
  • Loading branch information
razor-x committed Sep 26, 2023
1 parent 12671b0 commit 62939dd
Show file tree
Hide file tree
Showing 24 changed files with 1,050 additions and 192 deletions.
62 changes: 47 additions & 15 deletions generate-routes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { writeFile } from 'node:fs/promises'
import { readFile, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'

import { openapi } from '@seamapi/types/connect'
import { camelCase, paramCase, pascalCase, snakeCase } from 'change-case'
import { ESLint } from 'eslint'
import { format, resolveConfig } from 'prettier'

const rootClassPath = resolve('src', 'lib', 'seam', 'connect', 'client.ts')
const routeOutputPath = resolve('src', 'lib', 'seam', 'connect', 'routes')

const routePaths: string[] = [
Expand Down Expand Up @@ -53,6 +54,10 @@ interface Endpoint {
requestFormat: 'params' | 'body'
}

interface ClassMeta {
constructors: string
}

const exampleRoute: Route = {
namespace: 'workspaces',
endpoints: [
Expand Down Expand Up @@ -105,15 +110,15 @@ const createRoute = (routePath: string): Route => {

const routes = createRoutes()

const renderRoute = (route: Route): string => `
const renderRoute = (route: Route, { constructors }: ClassMeta): string => `
/*
* Automatically generated by generate-routes.ts.
* Do not edit this file or add other files to this directory.
*/
${renderImports()}
${renderClass(route)}
${renderClass(route, { constructors })}
${renderExports(route)}
`
Expand All @@ -125,24 +130,31 @@ import { Axios } from 'axios'
import type { SetNonNullable } from 'type-fest'
import { createAxiosClient } from 'lib/seam/connect/axios.js'
import type { SeamHttpOptions } from 'lib/seam/connect/client-options.js'
import {
isSeamHttpOptionsWithApiKey,
isSeamHttpOptionsWithClient,
isSeamHttpOptionsWithClientSessionToken,
SeamHttpInvalidOptionsError,
type SeamHttpOptions,
type SeamHttpOptionsWithApiKey,
type SeamHttpOptionsWithClient,
type SeamHttpOptionsWithClientSessionToken,
} from 'lib/seam/connect/client-options.js'
import { parseOptions } from 'lib/seam/connect/parse-options.js'
`

const renderClass = ({ namespace, endpoints }: Route): string =>
const renderClass = (
{ namespace, endpoints }: Route,
{ constructors }: ClassMeta,
): string =>
`
export class SeamHttp${pascalCase(namespace)} {
client: Axios
constructor(apiKeyOrOptionsOrClient: Axios | string | SeamHttpOptions) {
if (apiKeyOrOptionsOrClient instanceof Axios) {
this.client = apiKeyOrOptionsOrClient
return
}
const options = parseOptions(apiKeyOrOptionsOrClient)
this.client = createAxiosClient(options)
}
${constructors
.replace(/.*this\.#legacy.*\n/, '')
.replaceAll(': SeamHttp ', `: SeamHttp${pascalCase(namespace)} `)
.replaceAll('new SeamHttp(', `new SeamHttp${pascalCase(namespace)}(`)}
${endpoints.map(renderClassMethod).join('\n')}
}
Expand Down Expand Up @@ -222,6 +234,24 @@ const write = async (data: string, ...path: string[]): Promise<void> => {
await writeFile(filePath, prettyOutput)
}

const getClassConstructors = (data: string): string => {
const lines = data.split('\n')

const startIdx = lines.findIndex((line) =>
line.trim().startsWith('constructor('),
)
if (startIdx === -1) {
throw new Error('Could not find start of class constructors')
}

const endIdx = lines.findIndex((line) => line.trim().startsWith('get '))
if (endIdx === -1) {
throw new Error('Could not find end of class constructors')
}

return lines.slice(startIdx, endIdx).join('\n')
}

const prettierOutput = async (
data: string,
filepath: string,
Expand Down Expand Up @@ -259,8 +289,10 @@ const eslintFixOutput = async (
}

const writeRoute = async (route: Route): Promise<void> => {
const rootClass = await readFile(rootClassPath)
const constructors = getClassConstructors(rootClass.toString())
await write(
renderRoute(route),
renderRoute(route, { constructors }),
routeOutputPath,
`${paramCase(route.namespace)}.ts`,
)
Expand Down
30 changes: 29 additions & 1 deletion src/lib/seam/connect/client-options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AxiosRequestConfig } from 'axios'
import type { Axios, AxiosRequestConfig } from 'axios'

export type SeamHttpOptions =
| SeamHttpOptionsWithClient
| SeamHttpOptionsWithApiKey
| SeamHttpOptionsWithClientSessionToken

Expand All @@ -10,6 +11,31 @@ interface SeamHttpCommonOptions {
enableLegacyMethodBehaivor?: boolean
}

export interface SeamHttpOptionsWithClient
extends Pick<SeamHttpCommonOptions, 'enableLegacyMethodBehaivor'> {
client: Axios
}

export const isSeamHttpOptionsWithClient = (
options: SeamHttpOptions,
): options is SeamHttpOptionsWithClient => {
if (!('client' in options)) return false
if (options.client == null) return false

const keys = Object.keys(options).filter(
(k) => !['client', 'enableLegacyMethodBehaivor'].includes(k),
)
if (keys.length > 0) {
throw new SeamHttpInvalidOptionsError(
`The client option cannot be used with any other option except enableLegacyMethodBehaivor, but received: ${keys.join(
', ',
)}`,
)
}

return true
}

export interface SeamHttpOptionsWithApiKey extends SeamHttpCommonOptions {
apiKey: string
}
Expand All @@ -18,6 +44,7 @@ export const isSeamHttpOptionsWithApiKey = (
options: SeamHttpOptions,
): options is SeamHttpOptionsWithApiKey => {
if (!('apiKey' in options)) return false
if (options.apiKey == null) return false

if ('clientSessionToken' in options && options.clientSessionToken != null) {
throw new SeamHttpInvalidOptionsError(
Expand All @@ -37,6 +64,7 @@ export const isSeamHttpOptionsWithClientSessionToken = (
options: SeamHttpOptions,
): options is SeamHttpOptionsWithClientSessionToken => {
if (!('clientSessionToken' in options)) return false
if (options.clientSessionToken == null) return false

if ('apiKey' in options && options.apiKey != null) {
throw new SeamHttpInvalidOptionsError(
Expand Down
32 changes: 23 additions & 9 deletions src/lib/seam/connect/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type { Axios } from 'axios'
import { createAxiosClient } from './axios.js'
import {
isSeamHttpOptionsWithApiKey,
isSeamHttpOptionsWithClient,
isSeamHttpOptionsWithClientSessionToken,
SeamHttpInvalidOptionsError,
type SeamHttpOptions,
type SeamHttpOptionsWithApiKey,
type SeamHttpOptionsWithClient,
type SeamHttpOptionsWithClientSessionToken,
} from './client-options.js'
import { SeamHttpLegacyWorkspaces } from './legacy/workspaces.js'
Expand All @@ -21,7 +23,19 @@ export class SeamHttp {
constructor(apiKeyOrOptions: string | SeamHttpOptions) {
const options = parseOptions(apiKeyOrOptions)
this.#legacy = options.enableLegacyMethodBehaivor
this.client = createAxiosClient(options)
const client = 'client' in options ? options.client : null
this.client = client ?? createAxiosClient(options)
}

static fromClient(
client: SeamHttpOptionsWithClient['client'],
options: Omit<SeamHttpOptionsWithClient, 'client'> = {},
): SeamHttp {
const opts = { ...options, client }
if (!isSeamHttpOptionsWithClient(opts)) {
throw new SeamHttpInvalidOptionsError('Missing client')
}
return new SeamHttp(opts)
}

static fromApiKey(
Expand Down Expand Up @@ -49,15 +63,15 @@ export class SeamHttp {
return new SeamHttp(opts)
}

// TODO
// static fromPublishableKey and deprecate getClientSessionToken

// TODO: Should we keep makeRequest?
// Better to implement error handling and wrapping in an error handler.
// makeRequest

get workspaces(): SeamHttpWorkspaces {
if (this.#legacy) return new SeamHttpLegacyWorkspaces(this.client)
return new SeamHttpWorkspaces(this.client)
return new SeamHttpWorkspaces({ client: this.client })
}
}

// TODO
// static fromPublishableKey and deprecate getClientSessionToken

// TODO: Should we keep makeRequest?
// Better to implement error handling and wrapping in an error handler.
// makeRequest
17 changes: 4 additions & 13 deletions src/lib/seam/connect/legacy/workspaces.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import type { RouteRequestParams, RouteResponse } from '@seamapi/types/connect'
import { Axios } from 'axios'
import type { Axios } from 'axios'
import type { SetNonNullable } from 'type-fest'

import { createAxiosClient } from 'lib/seam/connect/axios.js'
import type { SeamHttpOptions } from 'lib/seam/connect/client-options.js'
import { parseOptions } from 'lib/seam/connect/parse-options.js'

export class SeamHttpLegacyWorkspaces {
client: Axios

constructor(apiKeyOrOptionsOrClient: Axios | string | SeamHttpOptions) {
if (apiKeyOrOptionsOrClient instanceof Axios) {
this.client = apiKeyOrOptionsOrClient
return
}

const options = parseOptions(apiKeyOrOptionsOrClient)
this.client = createAxiosClient(options)
constructor(client: Axios) {
this.client = client
}

async get(
Expand All @@ -31,6 +21,7 @@ export class SeamHttpLegacyWorkspaces {
}
}

// TODO: Import from routes so no need to redefine here
type WorkspacesGetParams = SetNonNullable<
Required<RouteRequestParams<'/workspaces/get'>>
>
Expand Down
18 changes: 16 additions & 2 deletions src/lib/seam/connect/parse-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import type { SeamHttpOptions } from './client-options.js'
import {
isSeamHttpOptionsWithClient,
type SeamHttpOptions,
} from './client-options.js'

const enableLegacyMethodBehaivorDefault = true

export const parseOptions = (
apiKeyOrOptions: string | SeamHttpOptions,
): Required<SeamHttpOptions> => {
Expand All @@ -7,6 +13,13 @@ export const parseOptions = (
? { apiKey: apiKeyOrOptions }
: apiKeyOrOptions

if (isSeamHttpOptionsWithClient(options))
return {
...options,
enableLegacyMethodBehaivor:
options.enableLegacyMethodBehaivor ?? enableLegacyMethodBehaivorDefault,
}

const endpoint =
options.endpoint ??
globalThis.process?.env?.['SEAM_ENDPOINT'] ??
Expand All @@ -23,6 +36,7 @@ export const parseOptions = (
...(apiKey != null ? { apiKey } : {}),
endpoint,
axiosOptions: options.axiosOptions ?? {},
enableLegacyMethodBehaivor: false,
enableLegacyMethodBehaivor:
options.enableLegacyMethodBehaivor ?? enableLegacyMethodBehaivorDefault,
}
}
57 changes: 49 additions & 8 deletions src/lib/seam/connect/routes/access-codes-unmanaged.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,63 @@
* Do not edit this file or add other files to this directory.
*/

import { Axios } from 'axios'
import type { Axios } from 'axios'

import { createAxiosClient } from 'lib/seam/connect/axios.js'
import type { SeamHttpOptions } from 'lib/seam/connect/client-options.js'
import {
isSeamHttpOptionsWithApiKey,
isSeamHttpOptionsWithClient,
isSeamHttpOptionsWithClientSessionToken,
SeamHttpInvalidOptionsError,
type SeamHttpOptions,
type SeamHttpOptionsWithApiKey,
type SeamHttpOptionsWithClient,
type SeamHttpOptionsWithClientSessionToken,
} from 'lib/seam/connect/client-options.js'
import { parseOptions } from 'lib/seam/connect/parse-options.js'

export class SeamHttpAccessCodesUnmanaged {
client: Axios

constructor(apiKeyOrOptionsOrClient: Axios | string | SeamHttpOptions) {
if (apiKeyOrOptionsOrClient instanceof Axios) {
this.client = apiKeyOrOptionsOrClient
return
constructor(apiKeyOrOptions: string | SeamHttpOptions) {
const options = parseOptions(apiKeyOrOptions)
const client = 'client' in options ? options.client : null
this.client = client ?? createAxiosClient(options)
}

static fromClient(
client: SeamHttpOptionsWithClient['client'],
options: Omit<SeamHttpOptionsWithClient, 'client'> = {},
): SeamHttpAccessCodesUnmanaged {
const opts = { ...options, client }
if (!isSeamHttpOptionsWithClient(opts)) {
throw new SeamHttpInvalidOptionsError('Missing client')
}
return new SeamHttpAccessCodesUnmanaged(opts)
}

const options = parseOptions(apiKeyOrOptionsOrClient)
this.client = createAxiosClient(options)
static fromApiKey(
apiKey: SeamHttpOptionsWithApiKey['apiKey'],
options: Omit<SeamHttpOptionsWithApiKey, 'apiKey'> = {},
): SeamHttpAccessCodesUnmanaged {
const opts = { ...options, apiKey }
if (!isSeamHttpOptionsWithApiKey(opts)) {
throw new SeamHttpInvalidOptionsError('Missing apiKey')
}
return new SeamHttpAccessCodesUnmanaged(opts)
}

static fromClientSessionToken(
clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'],
options: Omit<
SeamHttpOptionsWithClientSessionToken,
'clientSessionToken'
> = {},
): SeamHttpAccessCodesUnmanaged {
const opts = { ...options, clientSessionToken }
if (!isSeamHttpOptionsWithClientSessionToken(opts)) {
throw new SeamHttpInvalidOptionsError('Missing clientSessionToken')
}
return new SeamHttpAccessCodesUnmanaged(opts)
}
}
Loading

0 comments on commit 62939dd

Please sign in to comment.