Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,18 @@ const devices = await seam.client.get<DevicesListResponse>('/devices/list')
An Axios compatible client may be provided to create a `SeamHttp` instance.
This API is used internally and is not directly supported.

#### Alternative endpoint path interface

The `SeamHttpEndpoints` class offers an alternative path-based interface to every API endpoint.
Each endpoint is exposed as simple property that returns the corresponding method from `SeamHttp`.

```ts
import { SeamHttpEndpoints } from '@seamapi/http/connect'

const seam = new SeamHttpEndpoints()
const devices = await seam['/devices/list']()
```

#### Inspecting the Request

All client methods return an instance of `SeamHttpRequest`.
Expand Down
32 changes: 32 additions & 0 deletions codegen/layouts/endpoints.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Automatically generated by codegen/smith.ts.
* Do not edit this file or add other files to this directory.
*/

{{> route-imports }}

{{#each routeImports}}
import {
{{className}},
{{#each typeNames}}
type {{.}},
{{/each}}
} from './{{fileName}}'
{{/each}}

export class SeamHttpEndpoints {
{{> route-class-methods }}

{{#each endpoints}}
get['{{path}}'](): {{className}}['{{methodName}}']
{
const { client, defaults } = this
return function {{functionName}} (...args: Parameters<{{className}}['{{methodName}}']>): ReturnType<{{className}}['{{methodName}}']>
{
const seam = {{className}}.fromClient(client, defaults)
return seam.{{methodName}}(...args)
}
}

{{/each}}
}
29 changes: 18 additions & 11 deletions codegen/lib/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ import type { Blueprint } from '@seamapi/blueprint'
import { kebabCase } from 'change-case'
import type Metalsmith from 'metalsmith'

import {
type EndpointsLayoutContext,
setEndpointsLayoutContext,
} from './layouts/endpoints.js'
import {
type RouteIndexLayoutContext,
type RouteLayoutContext,
setRouteLayoutContext,
toFilePath,
} from './layouts/route.js'

interface Metadata {
blueprint: Blueprint
}

type File = RouteLayoutContext & RouteIndexLayoutContext & { layout: string }
type File = RouteLayoutContext &
RouteIndexLayoutContext &
EndpointsLayoutContext & { layout: string }

const rootPath = 'src/lib/seam/connect/routes'

Expand All @@ -34,15 +41,22 @@ export const connect = (

const routeIndexes: Record<string, Set<string>> = {}

const rootRouteKey = `${rootPath}/seam-http.ts`
files[rootRouteKey] = { contents: Buffer.from('\n') }
const file = files[rootRouteKey] as unknown as File
const k = `${rootPath}/seam-http.ts`
files[k] = { contents: Buffer.from('\n') }
const file = files[k] as unknown as File
file.layout = 'route.hbs'
setRouteLayoutContext(file, null, nodes)

routeIndexes[''] ??= new Set()
routeIndexes['']?.add('seam-http.js')

const endpointsKey = `${rootPath}/seam-http-endpoints.ts`
files[endpointsKey] = { contents: Buffer.from('\n') }
const endpointFile = files[endpointsKey] as unknown as File
endpointFile.layout = 'endpoints.hbs'
setEndpointsLayoutContext(endpointFile, routes)
routeIndexes['']?.add('seam-http-endpoints.js')

for (const node of nodes) {
const path = toFilePath(node.path)
const name = kebabCase(node.name)
Expand Down Expand Up @@ -75,10 +89,3 @@ export const connect = (
file.routes = [...routes]
}
}

const toFilePath = (path: string): string =>
path
.slice(1)
.split('/')
.map((p) => kebabCase(p))
.join('/')
35 changes: 35 additions & 0 deletions codegen/lib/layouts/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Route } from '@seamapi/blueprint'

import {
type EndpointLayoutContext,
getClassName,
getEndpointLayoutContext,
type SubrouteLayoutContext,
toFilePath,
} from './route.js'

export interface EndpointsLayoutContext {
className: string
endpoints: EndpointLayoutContext[]
routeImports: Array<Pick<SubrouteLayoutContext, 'className' | 'fileName'>>
skipClientSessionImport: boolean
}

export const setEndpointsLayoutContext = (
file: Partial<EndpointsLayoutContext>,
routes: Route[],
): void => {
file.className = getClassName('Endpoints')
file.skipClientSessionImport = true
file.endpoints = routes.flatMap((route) =>
route.endpoints
.filter(({ isUndocumented }) => !isUndocumented)
.map((endpoint) => getEndpointLayoutContext(endpoint, route)),
)
file.routeImports = routes.map((route) => {
return {
className: getClassName(route.path),
fileName: `${toFilePath(route.path)}/index.js`,
}
})
}
25 changes: 19 additions & 6 deletions codegen/lib/layouts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export interface RouteIndexLayoutContext {
routes: string[]
}

interface EndpointLayoutContext {
export interface EndpointLayoutContext {
path: string
methodName: string
functionName: string
className: string
method: Method
hasOptions: boolean
responseKey: string
Expand All @@ -30,7 +32,7 @@ interface EndpointLayoutContext {
isOptionalParamsOk: boolean
}

interface SubrouteLayoutContext {
export interface SubrouteLayoutContext {
methodName: string
className: string
fileName: string
Expand Down Expand Up @@ -71,7 +73,7 @@ const getSubrouteLayoutContext = (
}
}

const getEndpointLayoutContext = (
export const getEndpointLayoutContext = (
endpoint: Endpoint,
route: Pick<Route, 'path' | 'name'>,
): EndpointLayoutContext => {
Expand All @@ -95,11 +97,15 @@ const getEndpointLayoutContext = (
endpoint.response.responseType === 'resource' &&
endpoint.response.resourceType === 'action_attempt'

const methodName = camelCase(endpoint.name)

return {
path: endpoint.path,
methodName: camelCase(endpoint.name),
methodName,
functionName: camelCase(prefix),
method: endpoint.request.preferredMethod,
hasOptions: returnsActionAttempt,
className: getClassName(route.path),
methodParamName,
requestFormat,
requestFormatSuffix,
Expand Down Expand Up @@ -129,5 +135,12 @@ const getResponseContext = (
}
}

const getClassName = (name: string | null): string =>
`SeamHttp${pascalCase(name ?? '')}`
export const getClassName = (path: string | null): string =>
`SeamHttp${pascalCase(path ?? '')}`

export const toFilePath = (path: string): string =>
path
.slice(1)
.split('/')
.map((p) => kebabCase(p))
.join('/')
1 change: 1 addition & 0 deletions src/lib/seam/connect/routes/index.ts

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

Loading