Skip to content

Commit

Permalink
feat(lark): support pagination api
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jan 7, 2025
1 parent ab96af1 commit 7ee766e
Show file tree
Hide file tree
Showing 36 changed files with 596 additions and 1,726 deletions.
41 changes: 16 additions & 25 deletions adapters/lark/scripts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ async function start() {
const apiType = capitalize(method)
const args: string[] = []
const extras: string[] = []
let returnType = `${apiType}Response`

let returnType: string
let paginationRequest: { queryType?: string } | undefined
for (const property of detail.request.path?.properties || []) {
args.push(`${property.name}: ${formatType(property, project.imports)}`)
Expand All @@ -256,9 +256,9 @@ async function start() {
if (keys.includes('page_token') && keys.includes('page_size')) {
const properties = detail.request.query.properties.filter(s => s.name !== 'page_token' && s.name !== 'page_size')
if (properties.length) {
project.requests.push(`export interface ${queryType} {\n${generateParams(properties, project.imports)}}`)
project.internalImports.add('Pagination')
project.requests.push(`export interface ${queryType} extends Pagination {\n${generateParams(properties, project.imports)}}`)
paginationRequest = { queryType }
queryType = `${queryType} & Pagination`
} else {
queryType = 'Pagination'
paginationRequest = {}
Expand All @@ -271,20 +271,21 @@ async function start() {

let paginationResponse: { innerType: string; tokenKey: string; itemsKey: string } | undefined
if (detail.supportFileDownload) {
returnType = 'ArrayBuffer'
returnType = 'Promise<ArrayBuffer>'
extras.push(`type: 'binary'`)
} else {
const keys = (detail.response.body?.properties || []).map(v => v.name)
if (!keys.includes('code') || !keys.includes('msg')) {
console.log(`unsupported response body: ${keys}, see https://open.feishu.cn${summary.fullPath}}`)
return
} else if (keys.length === 2) {
returnType = 'void'
returnType = 'Promise<void>'
} else if (keys.length === 3 && keys.includes('data')) {
const data = detail.response.body.properties!.find(v => v.name === 'data')!
if (!data.properties?.length) {
returnType = 'void'
returnType = 'Promise<void>'
} else {
const responseType = `${apiType}Response`
const keys = (data.properties || []).map(v => v.name)
let pagination: [string, string, Schema] | undefined
if (keys.includes('has_more') && (keys.includes('page_token') || keys.includes('next_page_token')) && keys.length === 3) {
Expand All @@ -298,25 +299,26 @@ async function start() {
const [itemsKey, tokenKey, schema] = pagination
let innerType = formatType(schema, project.imports)
if (schema.type === 'object' && schema.properties && !schema.ref) {
const name = `${apiType}Item`
project.responses.push(`export interface ${name} ${innerType}`)
innerType = name
project.responses.push(`export interface ${apiType}Item ${innerType}`)
innerType = `${apiType}Item`
}
returnType = itemsKey === 'items' ? `Paginated<${innerType}>` : `Paginated<${innerType}, '${itemsKey}'>`
project.internalImports.add('Paginated')
paginationResponse = { innerType, tokenKey, itemsKey }
} else {
if (detail.pagination) {
console.log(`unsupported pagination (${keys.join(', ')}), see https://open.feishu.cn${summary.fullPath}}`)
}
project.responses.push(`export interface ${returnType} {\n${generateParams(data.properties, project.imports)}}`)
project.responses.push(`export interface ${responseType} {\n${generateParams(data.properties, project.imports)}}`)
returnType = `Promise<${responseType}>`
}
}
} else {
const responseType = `${apiType}Response`
const properties = detail.response.body.properties!.filter(v => !['code', 'msg'].includes(v.name))
project.responses.push(`export interface ${returnType} extends BaseResponse {\n${generateParams(properties, project.imports)}}`)
project.responses.push(`export interface ${responseType} extends BaseResponse {\n${generateParams(properties, project.imports)}}`)
extras.push(`type: 'raw-json'`)
project.internalImports.add('BaseResponse')
returnType = `Promise<${responseType}>`
}
}

Expand All @@ -325,22 +327,11 @@ async function start() {
* ${summary.name}
* @see https://open.feishu.cn${summary.fullPath}
*/
${method}(${args.join(', ')}): Promise<${returnType}>
${method}(${args.join(', ')}): ${returnType}
`)

if (paginationRequest && paginationResponse) {
const paginationArgs = args.slice(0, -1)
const argIndex = paginationArgs.length
if (paginationRequest.queryType) {
paginationArgs.push(`query?: ${paginationRequest.queryType}`)
}
project.methods.push(dedent`
/**
* ${summary.name}
* @see https://open.feishu.cn${summary.fullPath}
*/
${method}Iter(${paginationArgs.join(', ')}): AsyncIterator<${paginationResponse.innerType}>
`)
const argIndex = args.length - 1
const props: string[] = [`argIndex: ${argIndex}`]
if (paginationResponse.itemsKey !== 'items') {
props.push(`itemsKey: '${paginationResponse.itemsKey}'`)
Expand Down
67 changes: 42 additions & 25 deletions adapters/lark/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ export interface BaseResponse {
msg: string
}

export type Paginated<T = any, ItemsKey extends string = 'items', TokenKey extends string = 'page_token'> =
& Promise<
& { [K in ItemsKey]: T[] }
& { [K in TokenKey]?: string }
& { has_more: boolean }
>
& AsyncIterableIterator<T>

export interface Pagination {
page_size?: number
page_token?: string
}

export type Paginated<T, K extends string = 'items'> = {
[P in K]: T[];
} & {
has_more: boolean
page_token: string
}

export interface InternalRoute {
name: string
pagination?: {
Expand Down Expand Up @@ -68,7 +69,8 @@ export class Internal {
if (typeof route === 'string') {
route = { name: route }
}
Internal.prototype[route.name] = async function (this: Internal, ...args: any[]) {

const impl = async function (this: Internal, ...args: any[]) {
const raw = args.join(', ')
const url = path.replace(/\{([^}]+)\}/g, () => {
if (!args.length) throw new Error(`too few arguments for ${path}, received ${raw}`)
Expand Down Expand Up @@ -99,26 +101,41 @@ export class Internal {
}
}

if (route.pagination) {
const { argIndex, itemsKey = 'items', tokenKey = 'page_token' } = route.pagination
Internal.prototype[route.name + 'Iter'] = async function (this: Internal, ...args: any[]) {
let list: Paginated<any>
const getList = async () => {
args[argIndex] = { ...args[argIndex], page_token: list?.[tokenKey] }
list = await this[route.name](...args)
Internal.prototype[route.name] = function (this: Internal, ...args: any[]) {
let promise: Promise<any> | undefined
const result = {} as Paginated
for (const key of ['then', 'catch', 'finally']) {
result[key] = (...args2: any[]) => {
return (promise ??= impl.apply(this, args))[key](...args2)
}
return {
async next() {
if (list?.[itemsKey].length) return { done: false, value: list[itemsKey].shift() }
if (!list.has_more) return { done: true, value: undefined }
await getList()
return this.next()
},
[Symbol.asyncIterator]() {
return this
},
}

if (route.pagination) {
const { argIndex, itemsKey = 'items', tokenKey = 'page_token' } = route.pagination
const iterArgs = [...args]
iterArgs[argIndex] = { ...args[argIndex] }
let pagination: { data: any[]; next?: any } | undefined
result.next = async function () {
pagination ??= await this[Symbol.for('satori.pagination')]()
if (pagination.data.length) return { done: false, value: pagination.data.shift() }
if (!pagination.next) return { done: true, value: undefined }
pagination = await this[Symbol.for('satori.pagination')]()
return this.next()
}
result[Symbol.asyncIterator] = function () {
return this
}
result[Symbol.for('satori.pagination')] = async () => {
const data = await impl.apply(this, iterArgs)
iterArgs[argIndex].page_token = data[tokenKey]
return {
data: data[itemsKey],
next: data.has_more ? iterArgs : undefined,
}
}
}

return result
}
}
}
Expand Down
20 changes: 5 additions & 15 deletions adapters/lark/src/types/acs.ts

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

47 changes: 11 additions & 36 deletions adapters/lark/src/types/admin.ts

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

Loading

0 comments on commit 7ee766e

Please sign in to comment.