@cloudbase/node-sdk
Version:
tencent cloud base server sdk for node.js
389 lines (325 loc) • 12.8 kB
text/typescript
import axios from 'axios'
import wxCloudClient, { IMySqlClient, IMySqlOptions, OrmClient, OrmRawQueryClient } from '@cloudbase/wx-cloud-client-sdk'
import {
ICloudBaseConfig,
ISCFContext,
IContextParam,
ICompleteCloudbaseContext,
ICustomReqOpts,
ICallFunctionOptions,
ICallContainerOptions,
IUploadFileOptions,
IUploadFileResult,
IDownloadFileOptions,
IDownloadFileResult,
ICopyFileOptions,
ICopyFileResult,
IDeleteFileOptions,
IDeleteFileResult,
IGetFileUrlOptions,
IGetFileUrlResult,
IGetFileInfoOptions,
IGetFileInfoResult,
IGetUploadMetadataOptions,
IGetUploadMetadataResult,
IGetFileAuthorityOptions,
IGetFileAuthorityResult,
ICallWxOpenApiOptions,
ICallWxOpenApiResult,
IReportData,
Extension,
ICallApisOptions,
ICloudBaseDBConfig
} from '../types'
import { auth } from './auth'
import { callApis, callFunction } from './functions'
import { callContainer } from './cloudrun'
import { newDb } from './database'
import { uploadFile, deleteFile, getTempFileURL, getFileInfo, downloadFile, getUploadMetadata, getFileAuthority, copyFile } from './storage'
import { callWxOpenApi, callCompatibleWxOpenApi, callWxPayApi, wxCallContainerApi } from './wx'
import { analytics } from './analytics'
import { createAI } from './ai'
import { AI } from '@cloudbase/ai'
import { Logger, logger } from './logger'
import { ERROR } from './const/code'
import * as utils from './utils/utils'
import { preflightRuntimeCloudPlatform } from './utils/cloudplatform'
import { parseContext, getCloudbaseContext } from './utils/tcbcontext'
import { sendNotification, ITemplateNotifyReq } from './notification'
import * as openapicommonrequester from './utils/tcbopenapicommonrequester'
import { IFetchOptions } from '@cloudbase/adapter-interface'
import { buildCommonOpenApiUrlWithPath } from './utils/tcbopenapiendpoint'
import { SYMBOL_CURRENT_ENV } from './const/symbol'
import { IncomingHttpHeaders } from 'http'
import { normalizeConfig } from './utils/utils'
export class CloudBase {
public static scfContext: ISCFContext
public static parseContext(context: IContextParam): ISCFContext {
const parseResult = parseContext(context)
CloudBase.scfContext = parseResult
return parseResult
}
public static getCloudbaseContext(context?: IContextParam): ICompleteCloudbaseContext {
return getCloudbaseContext(context)
}
public config: ICloudBaseConfig
private clsLogger: Logger
private extensionMap: Map<string, Extension>
private aiInstance: AI
public models: OrmClient & OrmRawQueryClient
public mysql: IMySqlClient
public rdb: IMySqlClient
public constructor(config?: ICloudBaseConfig) {
this.init(config)
}
public init(config: ICloudBaseConfig = {}): void {
// 预检运行环境,调用与否并不影响后续逻辑
// 注意:该函数为异步函数,这里并不等待检查结果
/* eslint-disable-next-line */
preflightRuntimeCloudPlatform()
// 所有的鉴权,参数塑形都在 normalizeConfig 中处理
// 后续其他模块获取 config 都通过 CloudBase 实例的 config 获取
// 禁止在业务模块中直接修改 config 配置
this.config = normalizeConfig(config)
this.extensionMap = new Map()
// NOTE:try-catch 为防止 init 报错
try {
// 初始化数据模型等 SDK 方法
const envId = this.config.envName === SYMBOL_CURRENT_ENV
? openapicommonrequester.getEnvIdFromContext()
: (this.config.envName as string)
const httpClient = wxCloudClient.generateHTTPClient(this.callFunction.bind(this), async (options: IFetchOptions) => {
const result = await openapicommonrequester.request({
config: this.config,
data: safeParseJSON(options.body),
method: options.method?.toUpperCase(),
url: options.url,
headers: {
'Content-Type': 'application/json',
...headersInitToRecord(options.headers)
},
/**
* 既然 openapicommonrequester.request 的参数里的 token 获取也是通过 openapicommonrequester.request 方法去获取的
* 为什么不把这里的 token 去掉,全部放在 openapicommonrequester.request 中去统一处理 token 获取的逻辑
*/
token: (await this.auth().getClientCredential()).access_token
})
return result.body
}, buildCommonOpenApiUrlWithPath({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/model', region: this.config.region }), {
sqlBaseUrl: buildCommonOpenApiUrlWithPath({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/sql', region: this.config.region })
})
this.models = httpClient
} catch (e) {
// ignore
}
try {
const getEntity = (options: IMySqlOptions) => {
const envId
= this.config.envName === SYMBOL_CURRENT_ENV
? openapicommonrequester.getEnvIdFromContext()
: (this.config.envName as string)
const { instance = 'default', database = envId } = options || {}
const mysqlClient = wxCloudClient.generateMySQLClient(this, {
mysqlBaseUrl: buildCommonOpenApiUrlWithPath({
serviceUrl: this.config.serviceUrl,
envId,
path: '/v1/rdb/rest',
region: this.config.region
}),
fetch: async (url: RequestInfo | URL, options: RequestInit) => {
let headers = {}
if (options.headers instanceof Headers) {
options.headers.forEach((value, key) => {
headers[key] = value
})
} else {
headers = options.headers || {}
}
const result = await openapicommonrequester.request({
config: this.config,
data: safeParseJSON(options.body),
method: options.method?.toUpperCase(),
url: url instanceof URL ? url.href : String(url),
headers: {
'Content-Type': 'application/json',
...headersInitToRecord({
'X-Db-Instance': instance,
'Accept-Profile': database,
'Content-Profile': database,
...headers
})
},
token: (await this.auth().getClientCredential()).access_token
})
const data = result.body
const res = {
ok: result?.statusCode >= 200 && result?.statusCode < 300,
status: result?.statusCode || 200,
statusText: (result as unknown as { statusMessage: string })?.statusMessage || 'OK',
json: async () => await Promise.resolve(data || {}),
text: async () => await Promise.resolve(typeof data === 'string' ? data : JSON.stringify(data || {})),
headers: new Headers(incomingHttpHeadersToHeadersInit(result?.headers || {}))
}
return res as Response
}
})
return mysqlClient
}
this.mysql = (options: IMySqlOptions) => {
return getEntity(options)(options)
}
this.rdb = (options: IMySqlOptions) => {
return getEntity(options)(options)
}
} catch (e) {
// ignore
}
}
public logger(): Logger {
if (!this.clsLogger) {
this.clsLogger = logger()
}
return this.clsLogger
}
public auth() {
return auth(this)
}
public database(dbConfig: ICloudBaseDBConfig = {}) {
return newDb(this, dbConfig)
}
public ai(): AI {
if (!this.aiInstance) {
this.aiInstance = createAI(this)
}
return this.aiInstance
}
public async callFunction<ParaT, ResultT>(callFunctionOptions: ICallFunctionOptions<ParaT>, opts?: ICustomReqOpts) {
return await callFunction<ParaT, ResultT>(this, callFunctionOptions, opts)
}
public async callContainer<ParaT, ResultT>(callContainerOptions: ICallContainerOptions<ParaT>, opts?: ICustomReqOpts) {
return await callContainer<ParaT, ResultT>(this, callContainerOptions, opts)
}
public async callApis<ParaT>(callApiOptions: ICallApisOptions<ParaT>, opts?: ICustomReqOpts) {
return await callApis<ParaT>(this, callApiOptions, opts)
}
public async callWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<ICallWxOpenApiResult> {
return await callWxOpenApi(this, wxOpenApiOptions, opts)
}
public async callWxPayApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any> {
return await callWxPayApi(this, wxOpenApiOptions, opts)
}
public async wxCallContainerApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any> {
return await wxCallContainerApi(this, wxOpenApiOptions, opts)
}
public async callCompatibleWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any> {
return await callCompatibleWxOpenApi(this, wxOpenApiOptions, opts)
}
public async uploadFile({ cloudPath, fileContent }: IUploadFileOptions, opts?: ICustomReqOpts): Promise<IUploadFileResult> {
return await uploadFile(this, { cloudPath, fileContent }, opts)
}
public async downloadFile({ fileID, urlType, tempFilePath }: IDownloadFileOptions, opts?: ICustomReqOpts): Promise<IDownloadFileResult> {
return await downloadFile(this, { fileID, urlType, tempFilePath }, opts)
}
/**
* 复制文件
*
* @param fileList 复制列表
* @param fileList.srcPath 源文件路径
* @param fileList.dstPath 目标文件路径
* @param fileList.overwrite 当目标文件已经存在时,是否允许覆盖已有文件,默认 true
* @param fileList.removeOriginal 复制文件后是否删除源文件,默认不删除
* @param opts
*/
public async copyFile({ fileList }: ICopyFileOptions, opts?: ICustomReqOpts): Promise<ICopyFileResult> {
return await copyFile(this, { fileList }, opts)
}
public async deleteFile({ fileList }: IDeleteFileOptions, opts?: ICustomReqOpts): Promise<IDeleteFileResult> {
return await deleteFile(this, { fileList }, opts)
}
public async getTempFileURL({ fileList }: IGetFileUrlOptions, opts?: ICustomReqOpts): Promise<IGetFileUrlResult> {
return await getTempFileURL(this, { fileList }, opts)
}
public async getFileInfo({ fileList }: IGetFileInfoOptions, opts?: ICustomReqOpts): Promise<IGetFileInfoResult> {
return await getFileInfo(this, { fileList }, opts)
}
public async getUploadMetadata({ cloudPath }: IGetUploadMetadataOptions, opts?: ICustomReqOpts): Promise<IGetUploadMetadataResult> {
return await getUploadMetadata(this, { cloudPath }, opts)
}
public async getFileAuthority({ fileList }: IGetFileAuthorityOptions, opts?: ICustomReqOpts): Promise<IGetFileAuthorityResult> {
return await getFileAuthority(this, { fileList }, opts)
}
/**
* @deprecated
*/
public async analytics(reportData: IReportData): Promise<void> {
await analytics(this, reportData)
}
public registerExtension(ext: Extension): void {
this.extensionMap.set(ext.name, ext)
}
public async invokeExtension<OptsT>(name: string, opts: OptsT) {
const ext = this.extensionMap.get(name)
if (!ext) {
throw Error(`Please register '${name}' extension before invoke.`)
}
return ext.invoke(opts, this)
}
// SDK推送消息(对外API:sendTemplateNotification)
public async sendTemplateNotification(params: ITemplateNotifyReq, opts?: ICustomReqOpts) {
return await sendNotification(this, params, opts)
}
/**
* shim for tcb extension ci
*/
public get requestClient() {
return {
get: axios,
post: axios,
put: axios,
delete: axios
}
}
}
function headersInitToRecord(headers: HeadersInit): Record<string, string> {
if (!headers) {
return {}
}
const ret: Record<string, string> = {}
if (Array.isArray(headers)) {
headers.forEach(([key, value]) => {
ret[key] = value
})
} else if (typeof headers.forEach === 'function') {
headers.forEach(([key, value]) => {
ret[key] = value
})
} else {
Object.keys(headers).forEach((key) => {
ret[key] = headers[key]
})
}
return ret
}
function safeParseJSON(x: unknown) {
try {
return JSON.parse(x as string)
} catch (e) {
return x
}
}
function incomingHttpHeadersToHeadersInit(headers: IncomingHttpHeaders): HeadersInit {
const result: Array<[string, string]> = []
for (const [key, value] of Object.entries(headers)) {
if (value === undefined) {
continue
}
if (Array.isArray(value)) {
for (const v of value) {
result.push([key, v])
}
} else {
result.push([key, value])
}
}
return result
}