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
13 changes: 0 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,19 +524,6 @@ const seam = new SeamHttpEndpoints()
const devices = await seam['/devices/list']()
```

#### Enable undocumented API

Pass the `isUndocumentedApiEnabled` option to allow using the undocumented API.
This API is used internally and is not directly supported.
Do not use the undocumented API in production environments.
Seam is not responsible for any issues you may encounter with the undocumented API.

```ts
import { SeamHttp } from '@seamapi/http'

const seam = new SeamHttp({ isUndocumentedApiEnabled: true })
```

#### Inspecting the Request

All client methods return an instance of `SeamHttpRequest`.
Expand Down
5 changes: 0 additions & 5 deletions codegen/layouts/endpoints.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ export class {{className}} {
get['{{path}}'](): {{> endpont-method-signature isFnType=true }}
{
const { client, defaults } = this
{{#if isUndocumented}}
if (!this.defaults.isUndocumentedApiEnabled) {
throw new Error('Cannot use undocumented API without isUndocumentedApiEnabled')
}
{{/if}}
return function {{functionName}} (...args: Parameters<{{className}}['{{methodName}}']>): ReturnType<{{className}}['{{methodName}}']>
{
const seam = {{className}}.fromClient(client, defaults)
Expand Down
9 changes: 2 additions & 7 deletions codegen/layouts/partials/route-class-endpoint-export.hbs
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
export type {{parametersTypeName}} = {{#if isUndocumented}}RouteRequest{{requestFormatSuffix}}<'{{path}}'>{{else}}{{> request-object parameters=parameters}}{{/if}}

/**
* @deprecated Use {{parametersTypeName}} instead.
*/
export type {{legacyRequestTypeName}} = {{parametersTypeName}}
export type {{parametersTypeName}} = {{> request-object parameters=parameters}}

/**
* @deprecated Use {{requestTypeName}} instead.
*/
export type {{responseTypeName}} = {{#if usesLegacyResponseType}}RouteResponse<'{{path}}'>{{else if returnsVoid}}void{{else}}{ {{json responseKey}}: {{#if responseIsList}}Array<{{responseResourceTypeName}}>{{else}}{{responseResourceTypeName}}{{/if}} }{{/if}}
export type {{responseTypeName}} = {{#if returnsVoid}}void{{else}}{ {{json responseKey}}: {{#if responseIsList}}Array<{{responseResourceTypeName}}>{{else}}{{responseResourceTypeName}}{{/if}} }{{/if}}

export type {{requestTypeName}} = SeamHttpRequest<{{#if returnsVoid}}void, undefined{{else}}{{responseTypeName}}, '{{responseKey}}'{{/if}}>

Expand Down
5 changes: 0 additions & 5 deletions codegen/layouts/partials/route-class-endpoint.hbs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
{{methodName}}
{{> endpont-method-signature }}
{
{{#if isUndocumented}}
if (!this.defaults.isUndocumentedApiEnabled) {
throw new Error('Cannot use undocumented API without isUndocumentedApiEnabled')
}
{{/if}}
return new SeamHttpRequest(this, {
pathname: '{{path}}',
method: '{{method}}',
Expand Down
5 changes: 0 additions & 5 deletions codegen/layouts/partials/route-class-methods.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ static ltsVersion = seamApiLtsVersion

constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) {
const options = parseOptions(apiKeyOrOptions)
{{#if isUndocumented}}
if (!options.isUndocumentedApiEnabled) {
throw new Error('Cannot use undocumented API without isUndocumentedApiEnabled')
}
{{/if}}
this.client = 'client' in options ? options.client : createClient(options)
this.defaults = limitToSeamHttpRequestOptions(options)
}
Expand Down
7 changes: 0 additions & 7 deletions codegen/layouts/partials/route-imports.hbs
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import { seamApiLtsVersion } from 'lib/lts-version.js'
{{#if hasLegacyTypes}}
import type {
RouteRequestBody,
RouteRequestParams,
RouteResponse,
} from '@seamapi/types/connect'
{{/if}}
{{#each resourceTypeImports}}
import type { {{typeName}} } from 'lib/seam/connect/resources/{{fileName}}'
{{/each}}
Expand Down
18 changes: 17 additions & 1 deletion codegen/layouts/resource.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,20 @@
* Do not edit this file.
*/

export type {{typeName}} = {{#if isUnknown}}Record<string, unknown>{{else}}{{#each resources}}{{#unless @first}} | {{/unless}}{{> resource-object properties=properties}}{{/each}}{{/if}}
{{#if isBatch}}
{{#each batchResources}}
import type { {{typeName}} } from './{{fileName}}'
{{/each}}

interface BatchResourceMap {
{{#each batchResources}}
{{json batchKey}}: {{typeName}}
{{/each}}
}

export type {{typeName}}<TKey extends keyof BatchResourceMap = keyof BatchResourceMap> = {
[K in TKey]?: Array<BatchResourceMap[K]> | undefined
}
{{else}}
export type {{typeName}} = {{#each resources}}{{#unless @first}} | {{/unless}}{{> resource-object properties=properties}}{{/each}}
{{/if}}
55 changes: 37 additions & 18 deletions codegen/lib/layouts/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ export interface ResourceLayoutContext {
fileName: string
typeName: string
resources: Resource[]
isUnknown: boolean
isBatch: boolean
batchResources: BatchResourceLayoutContext[]
}

export interface ResourceIndexLayoutContext {
resources: Array<Pick<ResourceLayoutContext, 'fileName' | 'typeName'>>
}

interface BatchResourceLayoutContext {
batchKey: string
fileName: string
typeName: string
}

export const getResourceTypeName = (resourceType: string): string =>
`${pascalCase(resourceType)}Resource`
resourceType === 'event' ? 'SeamEvent' : pascalCase(resourceType)

export const getResourceLayoutContexts = (
blueprint: Blueprint,
Expand All @@ -26,21 +33,33 @@ export const getResourceLayoutContexts = (
const resourceTypes = [
...new Set(resources.map(({ resourceType }) => resourceType)),
]
const batchResources = getBatchResourceLayoutContexts(resources)

return [
{
fileName: 'unknown.ts',
typeName: 'UnknownResource',
resources: [],
isUnknown: true,
},
...resourceTypes.map((resourceType) => ({
fileName: `${kebabCase(resourceType)}.ts`,
typeName: getResourceTypeName(resourceType),
resources: resources.filter(
(resource) => resource.resourceType === resourceType,
),
isUnknown: false,
})),
]
return resourceTypes.map((resourceType) => ({
fileName: `${kebabCase(resourceType)}.ts`,
typeName: getResourceTypeName(resourceType),
resources: resources.filter(
(resource) => resource.resourceType === resourceType,
),
isBatch: resourceType === 'batch',
batchResources: resourceType === 'batch' ? batchResources : [],
}))
}

const getBatchResourceLayoutContexts = (
resources: Resource[],
): BatchResourceLayoutContext[] => {
const batch = resources.find(({ resourceType }) => resourceType === 'batch')
if (batch == null) return []

return batch.properties.flatMap((property) => {
if (!('resourceType' in property)) return []
return [
{
batchKey: property.name,
fileName: `${kebabCase(property.resourceType)}.js`,
typeName: getResourceTypeName(property.resourceType),
},
]
})
}
74 changes: 32 additions & 42 deletions codegen/lib/layouts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import { getResourceTypeName } from './resources.js'

export interface RouteLayoutContext {
className: string
isUndocumented: boolean
endpoints: EndpointLayoutContext[]
subroutes: SubrouteLayoutContext[]
skipClientSessionImport: boolean
hasLegacyTypes: boolean
resourceTypeImports: ResourceTypeImport[]
}

Expand All @@ -27,16 +25,12 @@ export interface EndpointLayoutContext {
responseKey: string
requestFormat: 'params' | 'body'
parametersTypeName: string
legacyRequestTypeName: string
responseTypeName: string
requestFormatSuffix: string
optionsTypeName: string
requestTypeName: string
returnsActionAttempt: boolean
returnsVoid: boolean
isOptionalParamsOk: boolean
isUndocumented: boolean
usesLegacyResponseType: boolean
parameters: Parameter[]
responseIsList: boolean
responseResourceTypeName: string
Expand All @@ -59,34 +53,12 @@ export const setRouteLayoutContext = (
nodes: Array<Route | Namespace>,
): void => {
file.className = getClassName(node?.path ?? null)
file.isUndocumented = node?.isUndocumented ?? false
file.skipClientSessionImport =
node == null || node?.path === '/client_sessions'
file.hasLegacyTypes =
node != null && 'endpoints' in node
? node.endpoints.some(
(endpoint) =>
endpoint.isUndocumented ||
(endpoint.response.responseType === 'resource' &&
endpoint.response.resourceType === 'action_attempt'),
)
: false
file.resourceTypeImports =
node != null && 'endpoints' in node
? [
...new Set(
node.endpoints.flatMap((endpoint) => {
if (
endpoint.isUndocumented ||
endpoint.response.responseType === 'void' ||
(endpoint.response.responseType === 'resource' &&
endpoint.response.resourceType === 'action_attempt')
) {
return []
}
return [endpoint.response.resourceType]
}),
),
...new Set(node.endpoints.flatMap(getEndpointResponseResourceTypes)),
].map((resourceType) => ({
fileName: `${kebabCase(resourceType)}.js`,
typeName: getResourceTypeName(resourceType),
Expand All @@ -107,7 +79,7 @@ export const setRouteLayoutContext = (
}

const getSubrouteLayoutContext = (
route: Pick<Route, 'path' | 'name' | 'isUndocumented'>,
route: Pick<Route, 'path' | 'name'>,
): SubrouteLayoutContext => {
return {
fileName: `${kebabCase(route.name)}/index.js`,
Expand All @@ -122,20 +94,24 @@ export const getEndpointLayoutContext = (
): EndpointLayoutContext => {
const prefix = pascalCase([route.path.split('/'), endpoint.name].join('_'))

const legacyMethodParamName = ['GET', 'DELETE'].includes(
endpoint.request.semanticMethod,
)
? 'params'
: 'body'
const batchResourceKeys = getBatchResourceKeys(endpoint)

if (
endpoint.response.responseType !== 'void' &&
endpoint.response.resourceType === 'unknown' &&
batchResourceKeys.length === 0
) {
throw new Error(
`Cannot generate ${endpoint.path}: response resource type is unknown`,
)
}

const requestFormat = ['GET', 'DELETE'].includes(
endpoint.request.preferredMethod,
)
? 'params'
: 'body'

const requestFormatSuffix = pascalCase(requestFormat)

const returnsActionAttempt =
endpoint.response.responseType === 'resource' &&
endpoint.response.resourceType === 'action_attempt'
Expand All @@ -149,28 +125,42 @@ export const getEndpointLayoutContext = (
method: endpoint.request.preferredMethod,
className: getClassName(route.path),
requestFormat,
requestFormatSuffix,
returnsActionAttempt,
parametersTypeName: `${prefix}Parameters`,
legacyRequestTypeName: `${prefix}${pascalCase(legacyMethodParamName)}`,
responseTypeName: `${prefix}Response`,
optionsTypeName: `${prefix}Options`,
requestTypeName: `${prefix}Request`,
isOptionalParamsOk: endpoint.request.parameters.every(
(parameter) => !parameter.isRequired,
),
isUndocumented: endpoint.isUndocumented,
usesLegacyResponseType: endpoint.isUndocumented || returnsActionAttempt,
parameters: endpoint.request.parameters,
responseIsList: endpoint.response.responseType === 'resource_list',
responseResourceTypeName:
endpoint.response.responseType === 'void'
? ''
: getResourceTypeName(endpoint.response.resourceType),
: batchResourceKeys.length > 0
? `Batch<${batchResourceKeys.map((key) => `'${key}'`).join(' | ')}>`
: getResourceTypeName(endpoint.response.resourceType),
...getResponseContext(endpoint),
}
}

const getEndpointResponseResourceTypes = (endpoint: Endpoint): string[] => {
if (endpoint.response.responseType === 'void') return []
if (endpoint.response.responseType === 'resource') {
const { batchResourceTypes } = endpoint.response
if (batchResourceTypes != null) return ['batch']
}
return [endpoint.response.resourceType]
}

const getBatchResourceKeys = (endpoint: Endpoint): string[] => {
if (endpoint.response.responseType !== 'resource') return []
return (
endpoint.response.batchResourceTypes?.map(({ batchKey }) => batchKey) ?? []
)
}

const getResponseContext = (
endpoint: Endpoint,
): Pick<EndpointLayoutContext, 'returnsVoid' | 'responseKey'> => {
Expand Down
2 changes: 1 addition & 1 deletion codegen/smith.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Metalsmith(rootDir)
.source('./content')
.destination('../')
.clean(false)
.use(blueprint({ types }))
.use(blueprint({ types, omitUndocumented: true }))
.use(connect)
.use(
layouts({
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

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

Loading
Loading