Skip to content
Open
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
4 changes: 4 additions & 0 deletions codegen/layouts/endpoints.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import {
} from './{{fileName}}'
{{/each}}

/**
* HTTP client for the Seam API{{#if withoutWorkspace}} not scoped to a workspace{{/if}}
* with endpoints keyed by their path, e.g., `seam.get['/devices/list']()`.
*/
export class {{className}} {
{{#if withoutWorkspace}}
{{> route-class-methods-without-workspace }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ constructor(options: SeamHttpWithoutWorkspaceOptions = {}) {
this.defaults = limitToSeamHttpRequestOptions(opts)
}

/**
* Creates a new {{className}} from an existing HTTP client.
*/
static fromClient(
client: SeamHttpWithoutWorkspaceOptionsWithClient['client'],
options: Omit<SeamHttpWithoutWorkspaceOptionsWithClient, 'client'> = {},
Expand All @@ -18,6 +21,10 @@ static fromClient(
return new {{className}}(constructorOptions)
}

/**
* Creates a new {{className}} authenticated with a console session token
* and not scoped to a workspace.
*/
static fromConsoleSessionToken(
consoleSessionToken: SeamHttpWithoutWorkspaceOptionsWithConsoleSessionToken['consoleSessionToken'],
options: Omit<
Expand All @@ -38,6 +45,10 @@ static fromConsoleSessionToken(
return new {{className}}(constructorOptions)
}

/**
* Creates a new {{className}} authenticated with a personal access token
* and not scoped to a workspace.
*/
static fromPersonalAccessToken(
personalAccessToken: SeamHttpWithoutWorkspaceOptionsWithPersonalAccessToken['personalAccessToken'],
options: Omit<
Expand Down
31 changes: 31 additions & 0 deletions codegen/layouts/partials/route-class-methods.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) {
this.defaults = limitToSeamHttpRequestOptions(options)
}

/**
* Creates a new {{className}} from an existing HTTP client.
*/
static fromClient(
client: SeamHttpOptionsWithClient['client'],
options: Omit<SeamHttpOptionsWithClient, 'client'> = {},
Expand All @@ -18,6 +21,9 @@ static fromClient(
return new {{className}}(constructorOptions)
}

/**
* Creates a new {{className}} authenticated with an API key.
*/
static fromApiKey(
apiKey: SeamHttpOptionsWithApiKey['apiKey'],
options: Omit<SeamHttpOptionsWithApiKey, 'apiKey'> = {},
Expand All @@ -29,6 +35,9 @@ static fromApiKey(
return new {{className}}(constructorOptions)
}

/**
* Creates a new {{className}} authenticated with a client session token.
*/
static fromClientSessionToken(
clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'],
options: Omit<
Expand All @@ -43,6 +52,11 @@ static fromClientSessionToken(
return new {{className}}(constructorOptions)
}

/**
* Creates a new {{className}} authenticated with a client session token
* for the user identified by the user identifier key.
* The client session is created with the publishable key if it does not exist.
*/
static async fromPublishableKey(
publishableKey: string,
userIdentifierKey: string,
Expand All @@ -63,6 +77,10 @@ static async fromPublishableKey(
return {{className}}.fromClientSessionToken(token, options)
}

/**
* Creates a new {{className}} authenticated with a console session token
* and scoped to a workspace.
*/
static fromConsoleSessionToken(
consoleSessionToken: SeamHttpOptionsWithConsoleSessionToken['consoleSessionToken'],
workspaceId: SeamHttpOptionsWithConsoleSessionToken['workspaceId'],
Expand All @@ -80,6 +98,10 @@ static fromConsoleSessionToken(
return new {{className}}(constructorOptions)
}

/**
* Creates a new {{className}} authenticated with a personal access token
* and scoped to a workspace.
*/
static fromPersonalAccessToken(
personalAccessToken: SeamHttpOptionsWithPersonalAccessToken['personalAccessToken'],
workspaceId: SeamHttpOptionsWithPersonalAccessToken['workspaceId'],
Expand All @@ -97,12 +119,21 @@ static fromPersonalAccessToken(
return new {{className}}(constructorOptions)
}

/**
* Creates a new SeamPaginator to iterate over the paginated results
* of the request.
*/
createPaginator<const TResponse, const TResponseKey extends keyof TResponse>(
request: SeamHttpRequest<TResponse, TResponseKey>,
): SeamPaginator<TResponse, TResponseKey> {
return new SeamPaginator<TResponse, TResponseKey>(this, request)
}

/**
* Updates the client session token used by this client for authentication.
*
* @throws If this client was not created with a client session token.
*/
async updateClientSessionToken(
clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'],
): Promise<void> {
Expand Down
3 changes: 3 additions & 0 deletions codegen/layouts/partials/route-class-subroute.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Client for the Seam API {{routePath}} routes.
*/
get {{ methodName }}(): {{ className }} {
return {{ className }}.fromClient(this.client, this.defaults)
}
12 changes: 12 additions & 0 deletions codegen/layouts/resource.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,23 @@ interface BatchResourceMap {
{{/each}}
}

{{#if (or (trim resources.[0].description) resources.[0].isDeprecated)}}
{{> doc resources.[0]}}
{{else}}
/**
* Represents a {{typeName}} resource of the Seam API.
*/
{{/if}}
export type {{typeName}}<TKey extends keyof BatchResourceMap = keyof BatchResourceMap> = {
[K in TKey]?: Array<BatchResourceMap[K]> | undefined
}
{{else}}
{{#if (or (trim resources.[0].description) resources.[0].isDeprecated)}}
{{> doc resources.[0]}}
{{else}}
/**
* Represents a {{typeName}} resource of the Seam API.
*/
{{/if}}
export type {{typeName}} = {{#each resources}}{{#unless @first}} | {{/unless}}{{> resource-object properties=properties}}{{/each}}
{{/if}}
14 changes: 14 additions & 0 deletions codegen/layouts/route.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ import { assertValidRequestParameters } from 'lib/request-parameters.js'
import type { RequireAtLeastOne } from 'lib/request-parameters.js'
{{/if}}

{{#if routePath}}
/**
* Client for the Seam API {{routePath}} routes.
*/
{{else}}
/**
* HTTP client for the Seam API.
*
* Create a client with the constructor
* or one of the static factory methods, e.g., `fromApiKey`.
* Each Seam API route is available under its corresponding property,
* e.g., use `seam.devices.list()` to call the `/devices/list` endpoint.
*/
{{/if}}
export class {{className}} {
{{> route-class-methods }}

Expand Down
10 changes: 10 additions & 0 deletions codegen/layouts/without-workspace.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@

import { SeamHttpWorkspaces } from 'lib/routes/workspaces/index.js'

/**
* HTTP client for the Seam API not scoped to a workspace.
*
* Use this client to list or create workspaces,
* e.g., when authenticated with a personal access token
* or console session token.
*/
export class SeamHttpWithoutWorkspace {
{{> route-class-methods-without-workspace }}

/**
* Client for the Seam API /workspaces routes available without a workspace.
*/
get workspaces(): Pick<SeamHttpWorkspaces, 'create' | 'list'> {
return SeamHttpWorkspaces.fromClient(this.client, this.defaults)
}
Expand Down
4 changes: 4 additions & 0 deletions codegen/lib/layouts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getResourceTypeName } from './resources.js'

export interface RouteLayoutContext {
className: string
routePath: string | null
endpoints: EndpointLayoutContext[]
subroutes: SubrouteLayoutContext[]
skipClientSessionImport: boolean
Expand Down Expand Up @@ -48,6 +49,7 @@ export interface SubrouteLayoutContext {
methodName: string
className: string
fileName: string
routePath: string
}

interface ResourceTypeImport {
Expand All @@ -61,6 +63,7 @@ export const setRouteLayoutContext = (
nodes: Array<Route | Namespace>,
): void => {
file.className = getClassName(node?.path ?? null)
file.routePath = node?.path ?? null
file.skipClientSessionImport =
node == null || node?.path === '/client_sessions'
file.needsActionAttemptsImport =
Expand Down Expand Up @@ -102,6 +105,7 @@ const getSubrouteLayoutContext = (
fileName: `${kebabCase(route.name)}/index.js`,
methodName: camelCase(route.name),
className: getClassName(route.path),
routePath: route.path,
}
}

Expand Down
94 changes: 94 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
import { globalIgnores } from 'eslint/config'
import importPlugin from 'eslint-plugin-import'
import jsdoc from 'eslint-plugin-jsdoc'
import simpleImportSort from 'eslint-plugin-simple-import-sort'
import unusedImports from 'eslint-plugin-unused-imports'
import neostandard, { resolveIgnoresFromGitignore } from 'neostandard'

const files = ['**/*.{ts,tsx}']

// The public API is the export closure of src/index.ts.
// Only these files contain exports reachable from the package entrypoint,
// so only they must document their exports.
// Errors in src/lib/resources and src/lib/routes must be fixed
// in the codegen/layouts templates followed by npm run generate.
const publicApiFiles = [
'src/lib/auth.ts',
'src/lib/error-interceptor.ts',
'src/lib/openapi.ts',
'src/lib/options.ts',
'src/lib/request-options.ts',
'src/lib/resolve-action-attempt.ts',
'src/lib/resources/**/*.ts',
'src/lib/routes/**/*.ts',
'src/lib/seam-http-error.ts',
'src/lib/seam-http-request.ts',
'src/lib/seam-paginator.ts',
'src/lib/token.ts',
]

// The generated endpoint Parameters, Response, Request, and Options types
// are self-describing, so exported types in generated route files
// do not require documentation.
const generatedRouteFiles = ['src/lib/routes/**/*.ts']

export default [
globalIgnores(resolveIgnoresFromGitignore()),
...neostandard({ ts: true, noStyle: true }),
Expand Down Expand Up @@ -73,6 +99,74 @@ export default [
],
},
},
{
...jsdoc.configs['flat/recommended-typescript-error'],
files: publicApiFiles,
rules: {
...jsdoc.configs['flat/recommended-typescript-error']?.rules,
'jsdoc/require-jsdoc': [
'error',
{
publicOnly: true,
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionDeclaration: true,
FunctionExpression: true,
MethodDefinition: false,
},
contexts: [
'ExportNamedDeclaration > TSInterfaceDeclaration',
'ExportNamedDeclaration > TSTypeAliasDeclaration',
'ExportNamedDeclaration > TSEnumDeclaration',
],
checkConstructors: false,
checkGetters: false,
checkSetters: false,
enableFixer: false,
},
],
'jsdoc/require-description': [
'error',
{
contexts: ['any'],
exemptedBy: ['deprecated', 'inheritdoc', 'internal', 'see'],
},
],
'jsdoc/require-param': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/require-yields': 'off',
// Types belong in the TypeScript type annotations, not the JSDoc tags.
'jsdoc/require-throws-type': 'off',
'jsdoc/tag-lines': ['error', 'never', { startLines: 1 }],
// Conflicts with how Prettier formats JSDoc inside union types.
'jsdoc/check-alignment': 'off',
},
},
{
files: generatedRouteFiles,
rules: {
'jsdoc/require-jsdoc': [
'error',
{
publicOnly: true,
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionDeclaration: true,
FunctionExpression: true,
MethodDefinition: false,
},
checkConstructors: false,
checkGetters: false,
checkSetters: false,
enableFixer: false,
},
],
},
},
{
files,
plugins: {
Expand Down
Loading
Loading