Skip to content

Commit db24604

Browse files
committed
Generate sub resources
1 parent 64cbf3e commit db24604

File tree

8 files changed

+341
-32
lines changed

8 files changed

+341
-32
lines changed

generate-routes.ts

Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import { format, resolveConfig } from 'prettier'
99
const rootClassPath = resolve('src', 'lib', 'seam', 'connect', 'client.ts')
1010
const routeOutputPath = resolve('src', 'lib', 'seam', 'connect', 'routes')
1111

12-
const routePaths: string[] = [
12+
const routePaths = [
1313
'/access_codes',
1414
'/access_codes/unmanaged',
15+
'/acs',
1516
'/acs/access_groups',
1617
'/acs/credentials',
1718
'/acs/systems',
@@ -24,12 +25,23 @@ const routePaths: string[] = [
2425
'/devices/unmanaged',
2526
'/events',
2627
'/locks',
28+
'/noise_sensors',
2729
'/noise_sensors/noise_thresholds',
2830
'/thermostats/climate_setting_schedules',
2931
'/thermostats',
3032
'/webhooks',
3133
'/workspaces',
32-
]
34+
] as const
35+
36+
const routePathSubresources: Partial<
37+
Record<(typeof routePaths)[number], string[]>
38+
> = {
39+
'/access_codes': ['unmanaged'],
40+
'/acs': ['access_groups', 'credentials', 'systems', 'users'],
41+
'/devices': ['unmanaged'],
42+
'/noise_sensors': ['noise_thresholds'],
43+
'/thermostats': ['climate_setting_schedules'],
44+
}
3345

3446
const ignoredEndpointPaths = [
3547
'/access_codes/simulate/create_unmanaged_access_code',
@@ -38,7 +50,7 @@ const ignoredEndpointPaths = [
3850
'/health/get_service_health',
3951
'/health/service/[service_name]',
4052
'/noise_sensors/simulate/trigger_noise_threshold',
41-
]
53+
] as const
4254

4355
const endpointResources: Partial<
4456
Record<
@@ -61,11 +73,12 @@ const endpointResources: Partial<
6173
'/noise_sensors/noise_thresholds/update': null,
6274
'/thermostats/climate_setting_schedules/update': null,
6375
'/workspaces/reset_sandbox': null,
64-
}
76+
} as const
6577

6678
interface Route {
6779
namespace: string
6880
endpoints: Endpoint[]
81+
subresources: string[]
6982
}
7083

7184
interface Endpoint {
@@ -91,7 +104,9 @@ const createRoutes = (): Route[] => {
91104
(path) =>
92105
!routePaths.some((routePath) => isEndpointUnderRoute(path, routePath)),
93106
)
94-
.filter((path) => !ignoredEndpointPaths.includes(path))
107+
.filter(
108+
(path) => !(ignoredEndpointPaths as unknown as string[]).includes(path),
109+
)
95110

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

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

115130
return {
116131
namespace,
132+
subresources: routePathSubresources[routePath] ?? [],
117133
endpoints: endpointPaths.map((endpointPath) =>
118134
createEndpoint(namespace, routePath, endpointPath),
119135
),
@@ -207,14 +223,14 @@ const renderRoute = (route: Route, { constructors }: ClassMeta): string => `
207223
* Do not edit this file or add other files to this directory.
208224
*/
209225
210-
${renderImports()}
226+
${renderImports(route)}
211227
212228
${renderClass(route, { constructors })}
213229
214230
${renderExports(route)}
215231
`
216232

217-
const renderImports = (): string =>
233+
const renderImports = ({ namespace, subresources }: Route): string =>
218234
`
219235
import type { RouteRequestParams, RouteResponse, RouteRequestBody } from '@seamapi/types/connect'
220236
import { Axios } from 'axios'
@@ -232,10 +248,21 @@ import {
232248
type SeamHttpOptionsWithClientSessionToken,
233249
} from 'lib/seam/connect/client-options.js'
234250
import { parseOptions } from 'lib/seam/connect/parse-options.js'
251+
${subresources
252+
.map((subresource) => renderSubresourceImport(subresource, namespace))
253+
.join('\n')}
254+
`
255+
const renderSubresourceImport = (
256+
subresource: string,
257+
namespace: string,
258+
): string => `
259+
import {
260+
SeamHttp${pascalCase(namespace)}${pascalCase(subresource)}
261+
} from './${paramCase(namespace)}-${paramCase(subresource)}.js'
235262
`
236263

237264
const renderClass = (
238-
{ namespace, endpoints }: Route,
265+
{ namespace, endpoints, subresources }: Route,
239266
{ constructors }: ClassMeta,
240267
): string =>
241268
`
@@ -247,6 +274,10 @@ export class SeamHttp${pascalCase(namespace)} {
247274
.replaceAll(': SeamHttp ', `: SeamHttp${pascalCase(namespace)} `)
248275
.replaceAll('new SeamHttp(', `new SeamHttp${pascalCase(namespace)}(`)}
249276
277+
${subresources
278+
.map((subresource) => renderSubresourceMethod(subresource, namespace))
279+
.join('\n')}
280+
250281
${endpoints.map(renderClassMethod).join('\n')}
251282
}
252283
`
@@ -287,6 +318,19 @@ const renderClassMethod = ({
287318
}
288319
`
289320

321+
const renderSubresourceMethod = (
322+
subresource: string,
323+
namespace: string,
324+
): string => `
325+
get ${camelCase(subresource)} (): SeamHttp${pascalCase(
326+
namespace,
327+
)}${pascalCase(subresource)} {
328+
return SeamHttp${pascalCase(namespace)}${pascalCase(
329+
subresource,
330+
)}.fromClient(this.client)
331+
}
332+
`
333+
290334
const renderExports = (route: Route): string =>
291335
route.endpoints.map(renderEndpointExports).join('\n')
292336

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

325-
const write = async (data: string, ...path: string[]): Promise<void> => {
326-
const filePath = resolve(...path)
327-
await writeFile(
328-
filePath,
329-
'// Generated empty file to allow ESLint parsing by filename',
330-
)
331-
const fixedOutput = await eslintFixOutput(data, filePath)
332-
const prettyOutput = await prettierOutput(fixedOutput, filePath)
333-
await writeFile(filePath, prettyOutput)
334-
}
335-
336369
const getClassConstructors = (data: string): string => {
337370
const lines = data.split('\n')
338371

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

387+
const writeRoute = async (route: Route): Promise<void> => {
388+
const rootClass = await readFile(rootClassPath)
389+
const constructors = getClassConstructors(rootClass.toString())
390+
await write(
391+
renderRoute(route, { constructors }),
392+
routeOutputPath,
393+
`${paramCase(route.namespace)}.ts`,
394+
)
395+
}
396+
397+
const writeRoutesIndex = async (routes: Route[]): Promise<void> => {
398+
const exports = routes.map(
399+
(route) => `export * from './${paramCase(route.namespace)}.js'`,
400+
)
401+
await write(exports.join('\n'), routeOutputPath, `index.ts`)
402+
}
403+
404+
const write = async (data: string, ...path: string[]): Promise<void> => {
405+
const filePath = resolve(...path)
406+
await writeFile(
407+
filePath,
408+
'// Generated empty file to allow ESLint parsing by filename',
409+
)
410+
const fixedOutput = await eslintFixOutput(data, filePath)
411+
const prettyOutput = await prettierOutput(fixedOutput, filePath)
412+
await writeFile(filePath, prettyOutput)
413+
}
414+
354415
const prettierOutput = async (
355416
data: string,
356417
filepath: string,
@@ -387,14 +448,6 @@ const eslintFixOutput = async (
387448
return linted.output ?? linted.source ?? data
388449
}
389450

390-
const writeRoute = async (route: Route): Promise<void> => {
391-
const rootClass = await readFile(rootClassPath)
392-
const constructors = getClassConstructors(rootClass.toString())
393-
await write(
394-
renderRoute(route, { constructors }),
395-
routeOutputPath,
396-
`${paramCase(route.namespace)}.ts`,
397-
)
398-
}
399-
400-
await Promise.all(createRoutes().map(writeRoute))
451+
const routes = createRoutes()
452+
await Promise.all(routes.map(writeRoute))
453+
await writeRoutesIndex(routes)

src/lib/seam/connect/client.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,21 @@ import {
1313
} from './client-options.js'
1414
import { SeamHttpLegacyWorkspaces } from './legacy/workspaces.js'
1515
import { parseOptions } from './parse-options.js'
16-
import { SeamHttpWorkspaces } from './routes/workspaces.js'
16+
import {
17+
SeamHttpAccessCodes,
18+
SeamHttpAcs,
19+
SeamHttpActionAttempts,
20+
SeamHttpClientSessions,
21+
SeamHttpConnectedAccounts,
22+
SeamHttpConnectWebviews,
23+
SeamHttpDevices,
24+
SeamHttpEvents,
25+
SeamHttpLocks,
26+
SeamHttpNoiseSensors,
27+
SeamHttpThermostats,
28+
SeamHttpWebhooks,
29+
SeamHttpWorkspaces,
30+
} from './routes/index.js'
1731

1832
export class SeamHttp {
1933
client: Axios
@@ -62,6 +76,54 @@ export class SeamHttp {
6276
return new SeamHttp(opts)
6377
}
6478

79+
get accessCodes(): SeamHttpAccessCodes {
80+
return SeamHttpAccessCodes.fromClient(this.client)
81+
}
82+
83+
get acs(): SeamHttpAcs {
84+
return SeamHttpAcs.fromClient(this.client)
85+
}
86+
87+
get actionAttempts(): SeamHttpActionAttempts {
88+
return SeamHttpActionAttempts.fromClient(this.client)
89+
}
90+
91+
get clientSessions(): SeamHttpClientSessions {
92+
return SeamHttpClientSessions.fromClient(this.client)
93+
}
94+
95+
get connectedAccounts(): SeamHttpConnectedAccounts {
96+
return SeamHttpConnectedAccounts.fromClient(this.client)
97+
}
98+
99+
get connectWebviews(): SeamHttpConnectWebviews {
100+
return SeamHttpConnectWebviews.fromClient(this.client)
101+
}
102+
103+
get devices(): SeamHttpDevices {
104+
return SeamHttpDevices.fromClient(this.client)
105+
}
106+
107+
get events(): SeamHttpEvents {
108+
return SeamHttpEvents.fromClient(this.client)
109+
}
110+
111+
get locks(): SeamHttpLocks {
112+
return SeamHttpLocks.fromClient(this.client)
113+
}
114+
115+
get noiseSensors(): SeamHttpNoiseSensors {
116+
return SeamHttpNoiseSensors.fromClient(this.client)
117+
}
118+
119+
get thermostats(): SeamHttpThermostats {
120+
return SeamHttpThermostats.fromClient(this.client)
121+
}
122+
123+
get webhooks(): SeamHttpWebhooks {
124+
return SeamHttpWebhooks.fromClient(this.client)
125+
}
126+
65127
get workspaces(): SeamHttpWorkspaces {
66128
if (this.#legacy) {
67129
return SeamHttpLegacyWorkspaces.fromClient(this.client)

src/lib/seam/connect/routes/access-codes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
} from 'lib/seam/connect/client-options.js'
2525
import { parseOptions } from 'lib/seam/connect/parse-options.js'
2626

27+
import { SeamHttpAccessCodesUnmanaged } from './access-codes-unmanaged.js'
28+
2729
export class SeamHttpAccessCodes {
2830
client: Axios
2931

@@ -68,6 +70,10 @@ export class SeamHttpAccessCodes {
6870
return new SeamHttpAccessCodes(opts)
6971
}
7072

73+
get unmanaged(): SeamHttpAccessCodesUnmanaged {
74+
return SeamHttpAccessCodesUnmanaged.fromClient(this.client)
75+
}
76+
7177
async create(
7278
body: AccessCodesCreateBody,
7379
): Promise<AccessCodesCreateResponse['access_code']> {

0 commit comments

Comments
 (0)