Skip to content

Commit

Permalink
Generate sub resources
Browse files Browse the repository at this point in the history
  • Loading branch information
razor-x committed Sep 28, 2023
1 parent 64cbf3e commit db24604
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 32 deletions.
115 changes: 84 additions & 31 deletions generate-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ 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[] = [
const routePaths = [
'/access_codes',
'/access_codes/unmanaged',
'/acs',
'/acs/access_groups',
'/acs/credentials',
'/acs/systems',
Expand All @@ -24,12 +25,23 @@ const routePaths: string[] = [
'/devices/unmanaged',
'/events',
'/locks',
'/noise_sensors',
'/noise_sensors/noise_thresholds',
'/thermostats/climate_setting_schedules',
'/thermostats',
'/webhooks',
'/workspaces',
]
] as const

const routePathSubresources: Partial<
Record<(typeof routePaths)[number], string[]>
> = {
'/access_codes': ['unmanaged'],
'/acs': ['access_groups', 'credentials', 'systems', 'users'],
'/devices': ['unmanaged'],
'/noise_sensors': ['noise_thresholds'],
'/thermostats': ['climate_setting_schedules'],
}

const ignoredEndpointPaths = [
'/access_codes/simulate/create_unmanaged_access_code',
Expand All @@ -38,7 +50,7 @@ const ignoredEndpointPaths = [
'/health/get_service_health',
'/health/service/[service_name]',
'/noise_sensors/simulate/trigger_noise_threshold',
]
] as const

const endpointResources: Partial<
Record<
Expand All @@ -61,11 +73,12 @@ const endpointResources: Partial<
'/noise_sensors/noise_thresholds/update': null,
'/thermostats/climate_setting_schedules/update': null,
'/workspaces/reset_sandbox': null,
}
} as const

interface Route {
namespace: string
endpoints: Endpoint[]
subresources: string[]
}

interface Endpoint {
Expand All @@ -91,7 +104,9 @@ const createRoutes = (): Route[] => {
(path) =>
!routePaths.some((routePath) => isEndpointUnderRoute(path, routePath)),
)
.filter((path) => !ignoredEndpointPaths.includes(path))
.filter(
(path) => !(ignoredEndpointPaths as unknown as string[]).includes(path),
)

if (unmatchedEndpointPaths.length > 0) {
// eslint-disable-next-line no-console
Expand All @@ -105,7 +120,7 @@ const createRoutes = (): Route[] => {
return routePaths.map(createRoute)
}

const createRoute = (routePath: string): Route => {
const createRoute = (routePath: (typeof routePaths)[number]): Route => {
const endpointPaths = Object.keys(openapi.paths).filter((path) =>
isEndpointUnderRoute(path, routePath),
)
Expand All @@ -114,6 +129,7 @@ const createRoute = (routePath: string): Route => {

return {
namespace,
subresources: routePathSubresources[routePath] ?? [],
endpoints: endpointPaths.map((endpointPath) =>
createEndpoint(namespace, routePath, endpointPath),
),
Expand Down Expand Up @@ -207,14 +223,14 @@ const renderRoute = (route: Route, { constructors }: ClassMeta): string => `
* Do not edit this file or add other files to this directory.
*/
${renderImports()}
${renderImports(route)}
${renderClass(route, { constructors })}
${renderExports(route)}
`

const renderImports = (): string =>
const renderImports = ({ namespace, subresources }: Route): string =>
`
import type { RouteRequestParams, RouteResponse, RouteRequestBody } from '@seamapi/types/connect'
import { Axios } from 'axios'
Expand All @@ -232,10 +248,21 @@ import {
type SeamHttpOptionsWithClientSessionToken,
} from 'lib/seam/connect/client-options.js'
import { parseOptions } from 'lib/seam/connect/parse-options.js'
${subresources
.map((subresource) => renderSubresourceImport(subresource, namespace))
.join('\n')}
`
const renderSubresourceImport = (
subresource: string,
namespace: string,
): string => `
import {
SeamHttp${pascalCase(namespace)}${pascalCase(subresource)}
} from './${paramCase(namespace)}-${paramCase(subresource)}.js'
`

const renderClass = (
{ namespace, endpoints }: Route,
{ namespace, endpoints, subresources }: Route,
{ constructors }: ClassMeta,
): string =>
`
Expand All @@ -247,6 +274,10 @@ export class SeamHttp${pascalCase(namespace)} {
.replaceAll(': SeamHttp ', `: SeamHttp${pascalCase(namespace)} `)
.replaceAll('new SeamHttp(', `new SeamHttp${pascalCase(namespace)}(`)}
${subresources
.map((subresource) => renderSubresourceMethod(subresource, namespace))
.join('\n')}
${endpoints.map(renderClassMethod).join('\n')}
}
`
Expand Down Expand Up @@ -287,6 +318,19 @@ const renderClassMethod = ({
}
`

const renderSubresourceMethod = (
subresource: string,
namespace: string,
): string => `
get ${camelCase(subresource)} (): SeamHttp${pascalCase(
namespace,
)}${pascalCase(subresource)} {
return SeamHttp${pascalCase(namespace)}${pascalCase(
subresource,
)}.fromClient(this.client)
}
`

const renderExports = (route: Route): string =>
route.endpoints.map(renderEndpointExports).join('\n')

Expand Down Expand Up @@ -322,17 +366,6 @@ const renderResponseType = ({
}: Pick<Endpoint, 'name' | 'namespace'>): string =>
[pascalCase(namespace), pascalCase(name), 'Response'].join('')

const write = async (data: string, ...path: string[]): Promise<void> => {
const filePath = resolve(...path)
await writeFile(
filePath,
'// Generated empty file to allow ESLint parsing by filename',
)
const fixedOutput = await eslintFixOutput(data, filePath)
const prettyOutput = await prettierOutput(fixedOutput, filePath)
await writeFile(filePath, prettyOutput)
}

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

Expand All @@ -351,6 +384,34 @@ const getClassConstructors = (data: string): string => {
return lines.slice(startIdx, endIdx).join('\n')
}

const writeRoute = async (route: Route): Promise<void> => {
const rootClass = await readFile(rootClassPath)
const constructors = getClassConstructors(rootClass.toString())
await write(
renderRoute(route, { constructors }),
routeOutputPath,
`${paramCase(route.namespace)}.ts`,
)
}

const writeRoutesIndex = async (routes: Route[]): Promise<void> => {
const exports = routes.map(
(route) => `export * from './${paramCase(route.namespace)}.js'`,
)
await write(exports.join('\n'), routeOutputPath, `index.ts`)
}

const write = async (data: string, ...path: string[]): Promise<void> => {
const filePath = resolve(...path)
await writeFile(
filePath,
'// Generated empty file to allow ESLint parsing by filename',
)
const fixedOutput = await eslintFixOutput(data, filePath)
const prettyOutput = await prettierOutput(fixedOutput, filePath)
await writeFile(filePath, prettyOutput)
}

const prettierOutput = async (
data: string,
filepath: string,
Expand Down Expand Up @@ -387,14 +448,6 @@ const eslintFixOutput = async (
return linted.output ?? linted.source ?? data
}

const writeRoute = async (route: Route): Promise<void> => {
const rootClass = await readFile(rootClassPath)
const constructors = getClassConstructors(rootClass.toString())
await write(
renderRoute(route, { constructors }),
routeOutputPath,
`${paramCase(route.namespace)}.ts`,
)
}

await Promise.all(createRoutes().map(writeRoute))
const routes = createRoutes()
await Promise.all(routes.map(writeRoute))
await writeRoutesIndex(routes)
64 changes: 63 additions & 1 deletion src/lib/seam/connect/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,21 @@ import {
} from './client-options.js'
import { SeamHttpLegacyWorkspaces } from './legacy/workspaces.js'
import { parseOptions } from './parse-options.js'
import { SeamHttpWorkspaces } from './routes/workspaces.js'
import {
SeamHttpAccessCodes,
SeamHttpAcs,
SeamHttpActionAttempts,
SeamHttpClientSessions,
SeamHttpConnectedAccounts,
SeamHttpConnectWebviews,
SeamHttpDevices,
SeamHttpEvents,
SeamHttpLocks,
SeamHttpNoiseSensors,
SeamHttpThermostats,
SeamHttpWebhooks,
SeamHttpWorkspaces,
} from './routes/index.js'

export class SeamHttp {
client: Axios
Expand Down Expand Up @@ -62,6 +76,54 @@ export class SeamHttp {
return new SeamHttp(opts)
}

get accessCodes(): SeamHttpAccessCodes {
return SeamHttpAccessCodes.fromClient(this.client)
}

get acs(): SeamHttpAcs {
return SeamHttpAcs.fromClient(this.client)
}

get actionAttempts(): SeamHttpActionAttempts {
return SeamHttpActionAttempts.fromClient(this.client)
}

get clientSessions(): SeamHttpClientSessions {
return SeamHttpClientSessions.fromClient(this.client)
}

get connectedAccounts(): SeamHttpConnectedAccounts {
return SeamHttpConnectedAccounts.fromClient(this.client)
}

get connectWebviews(): SeamHttpConnectWebviews {
return SeamHttpConnectWebviews.fromClient(this.client)
}

get devices(): SeamHttpDevices {
return SeamHttpDevices.fromClient(this.client)
}

get events(): SeamHttpEvents {
return SeamHttpEvents.fromClient(this.client)
}

get locks(): SeamHttpLocks {
return SeamHttpLocks.fromClient(this.client)
}

get noiseSensors(): SeamHttpNoiseSensors {
return SeamHttpNoiseSensors.fromClient(this.client)
}

get thermostats(): SeamHttpThermostats {
return SeamHttpThermostats.fromClient(this.client)
}

get webhooks(): SeamHttpWebhooks {
return SeamHttpWebhooks.fromClient(this.client)
}

get workspaces(): SeamHttpWorkspaces {
if (this.#legacy) {
return SeamHttpLegacyWorkspaces.fromClient(this.client)
Expand Down
6 changes: 6 additions & 0 deletions src/lib/seam/connect/routes/access-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
} from 'lib/seam/connect/client-options.js'
import { parseOptions } from 'lib/seam/connect/parse-options.js'

import { SeamHttpAccessCodesUnmanaged } from './access-codes-unmanaged.js'

export class SeamHttpAccessCodes {
client: Axios

Expand Down Expand Up @@ -68,6 +70,10 @@ export class SeamHttpAccessCodes {
return new SeamHttpAccessCodes(opts)
}

get unmanaged(): SeamHttpAccessCodesUnmanaged {
return SeamHttpAccessCodesUnmanaged.fromClient(this.client)
}

async create(
body: AccessCodesCreateBody,
): Promise<AccessCodesCreateResponse['access_code']> {
Expand Down
Loading

0 comments on commit db24604

Please sign in to comment.