@cloudbase/node-sdk 3.15.0 → 3.17.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/index.js +58 -0
- package/dist/ai/request-adapter.js +259 -0
- package/dist/cloudbase.js +84 -5
- package/dist/utils/request-core.js +4 -0
- package/dist/utils/tcbdbapirequester.js +14 -2
- package/dist/utils/tcbopenapicommonrequester.js +11 -4
- package/dist/utils/tcbopenapiendpoint.js +11 -2
- package/dist/utils/tcbopenapirequester.js +8 -2
- package/package.json +6 -2
- package/src/ai/index.ts +38 -0
- package/src/ai/request-adapter.ts +235 -0
- package/src/cloudbase.ts +40 -23
- package/src/functions/index.ts +1 -1
- package/src/utils/request-core.ts +15 -4
- package/src/utils/request.ts +2 -2
- package/src/utils/tcbdbapirequester.ts +6 -5
- package/src/utils/tcbopenapicommonrequester.ts +9 -4
- package/types/index.d.ts +20 -1
- package/types/internal.d.ts +1 -1
package/dist/ai/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.createAI = void 0;
|
|
27
|
+
const ai_1 = require("@cloudbase/ai");
|
|
28
|
+
const request_adapter_1 = require("./request-adapter");
|
|
29
|
+
const tcbopenapiendpoint_1 = require("../utils/tcbopenapiendpoint");
|
|
30
|
+
const symbol_1 = require("../const/symbol");
|
|
31
|
+
const openapicommonrequester = __importStar(require("../utils/tcbopenapicommonrequester"));
|
|
32
|
+
const app_1 = require("@cloudbase/app");
|
|
33
|
+
/**
|
|
34
|
+
* 创建 AI 实例
|
|
35
|
+
* @param cloudbase CloudBase 实例
|
|
36
|
+
* @returns AI 实例
|
|
37
|
+
*/
|
|
38
|
+
function createAI(cloudbase) {
|
|
39
|
+
const config = cloudbase.config;
|
|
40
|
+
// 获取环境 ID
|
|
41
|
+
const envId = config.envName === symbol_1.SYMBOL_CURRENT_ENV ? openapicommonrequester.getEnvIdFromContext() : config.envName;
|
|
42
|
+
// 构建 AI 基础 URL
|
|
43
|
+
const baseUrl = (0, tcbopenapiendpoint_1.buildCommonOpenApiUrlWithPath)({
|
|
44
|
+
serviceUrl: config.serviceUrl,
|
|
45
|
+
envId,
|
|
46
|
+
path: '/v1',
|
|
47
|
+
region: config.region
|
|
48
|
+
});
|
|
49
|
+
// 创建请求适配器
|
|
50
|
+
const requestAdapter = new request_adapter_1.AIRequestAdapter(config, async () => {
|
|
51
|
+
const credential = await cloudbase.auth().getClientCredential();
|
|
52
|
+
return credential.access_token;
|
|
53
|
+
});
|
|
54
|
+
// 创建 AI 实例
|
|
55
|
+
const ai = new ai_1.AI(requestAdapter, baseUrl, { t: (s) => s, lang: app_1.LANGS.ZH, LANG_HEADER_KEY: 'Accept-Language' });
|
|
56
|
+
return ai;
|
|
57
|
+
}
|
|
58
|
+
exports.createAI = createAI;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.AIRequestAdapter = void 0;
|
|
27
|
+
const openapicommonrequester = __importStar(require("../utils/tcbopenapicommonrequester"));
|
|
28
|
+
const web_streams_polyfill_1 = require("web-streams-polyfill");
|
|
29
|
+
const utils_1 = require("../utils/utils");
|
|
30
|
+
class AIRequestAdapter {
|
|
31
|
+
constructor(config, getAccessToken) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.getAccessToken = getAccessToken;
|
|
34
|
+
}
|
|
35
|
+
async fetch(options) {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
const { url, stream = false, timeout, method = 'POST', headers, body } = options;
|
|
38
|
+
const headersObj = {};
|
|
39
|
+
if (isHeaders(headers)) {
|
|
40
|
+
headers.forEach((value, key) => {
|
|
41
|
+
headersObj[key] = value;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else if (Array.isArray(headers)) {
|
|
45
|
+
headers.forEach(([k, v]) => (headersObj[k] = v));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
Object.assign(headersObj, headers);
|
|
49
|
+
}
|
|
50
|
+
let parsedBody;
|
|
51
|
+
if (typeof body === 'string') {
|
|
52
|
+
try {
|
|
53
|
+
parsedBody = JSON.parse(body);
|
|
54
|
+
}
|
|
55
|
+
catch (_c) {
|
|
56
|
+
parsedBody = body;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
parsedBody = body;
|
|
61
|
+
}
|
|
62
|
+
const token = await this.getAccessToken();
|
|
63
|
+
const result = await openapicommonrequester.request({
|
|
64
|
+
config: this.config,
|
|
65
|
+
data: parsedBody,
|
|
66
|
+
method: (method === null || method === void 0 ? void 0 : method.toUpperCase()) || 'POST',
|
|
67
|
+
url,
|
|
68
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, headersObj),
|
|
69
|
+
token,
|
|
70
|
+
opts: {
|
|
71
|
+
timeout: timeout || this.config.timeout,
|
|
72
|
+
stream
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const { body: bodyData, headers: responseHeaders, statusCode } = result;
|
|
76
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
77
|
+
let errorMessage = `Request failed with status code ${statusCode}`;
|
|
78
|
+
let errorCode = `${statusCode}`;
|
|
79
|
+
let requestId = '';
|
|
80
|
+
let errorBody = null;
|
|
81
|
+
if (typeof bodyData === 'string') {
|
|
82
|
+
errorBody = bodyData;
|
|
83
|
+
}
|
|
84
|
+
else if (Buffer.isBuffer(bodyData)) {
|
|
85
|
+
errorBody = bodyData.toString('utf-8');
|
|
86
|
+
}
|
|
87
|
+
else if (bodyData && typeof bodyData === 'object' && typeof bodyData.on === 'function') {
|
|
88
|
+
errorBody = await readStreamToString(bodyData);
|
|
89
|
+
}
|
|
90
|
+
if (errorBody) {
|
|
91
|
+
try {
|
|
92
|
+
const errorData = JSON.parse(errorBody);
|
|
93
|
+
if ((_a = errorData.error) === null || _a === void 0 ? void 0 : _a.message) {
|
|
94
|
+
errorMessage = errorData.error.message;
|
|
95
|
+
}
|
|
96
|
+
else if (errorData.message) {
|
|
97
|
+
errorMessage = errorData.message;
|
|
98
|
+
}
|
|
99
|
+
if ((_b = errorData.error) === null || _b === void 0 ? void 0 : _b.code) {
|
|
100
|
+
errorCode = errorData.error.code;
|
|
101
|
+
}
|
|
102
|
+
else if (errorData.code) {
|
|
103
|
+
errorCode = errorData.code;
|
|
104
|
+
}
|
|
105
|
+
if (errorData.requestId) {
|
|
106
|
+
requestId = errorData.requestId;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (_d) {
|
|
110
|
+
errorMessage = errorBody || errorMessage;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 从响应头中获取 requestId
|
|
114
|
+
if (!requestId && responseHeaders) {
|
|
115
|
+
const headerRequestId = responseHeaders['x-cloudbase-request-id'] || responseHeaders['x-request-id'] || '';
|
|
116
|
+
requestId = Array.isArray(headerRequestId) ? headerRequestId[0] : headerRequestId;
|
|
117
|
+
}
|
|
118
|
+
throw (0, utils_1.E)({
|
|
119
|
+
code: errorCode,
|
|
120
|
+
message: errorMessage,
|
|
121
|
+
requestId
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (stream) {
|
|
125
|
+
// 对于流式响应,将 Node.js 原生流转换为 Web ReadableStream
|
|
126
|
+
let readableStream;
|
|
127
|
+
if (bodyData && typeof bodyData === 'object' && 'on' in bodyData && typeof bodyData.on === 'function') {
|
|
128
|
+
const nodeStream = bodyData;
|
|
129
|
+
// Node 12 兼容: 使用标志位追踪 stream 状态,避免重复 close 导致异常
|
|
130
|
+
let streamClosed = false;
|
|
131
|
+
readableStream = new web_streams_polyfill_1.ReadableStream({
|
|
132
|
+
start(controller) {
|
|
133
|
+
nodeStream.on('data', (chunk) => {
|
|
134
|
+
if (streamClosed)
|
|
135
|
+
return;
|
|
136
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
137
|
+
});
|
|
138
|
+
nodeStream.on('end', () => {
|
|
139
|
+
if (streamClosed)
|
|
140
|
+
return;
|
|
141
|
+
streamClosed = true;
|
|
142
|
+
controller.close();
|
|
143
|
+
});
|
|
144
|
+
nodeStream.on('error', (err) => {
|
|
145
|
+
if (streamClosed)
|
|
146
|
+
return;
|
|
147
|
+
streamClosed = true;
|
|
148
|
+
controller.error(err);
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
cancel() {
|
|
152
|
+
streamClosed = true;
|
|
153
|
+
nodeStream.destroy();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else if (bodyData instanceof Buffer) {
|
|
158
|
+
readableStream = new web_streams_polyfill_1.ReadableStream({
|
|
159
|
+
start(controller) {
|
|
160
|
+
controller.enqueue(new Uint8Array(bodyData));
|
|
161
|
+
controller.close();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else if (typeof bodyData === 'string') {
|
|
166
|
+
const encoder = new TextEncoder();
|
|
167
|
+
readableStream = new web_streams_polyfill_1.ReadableStream({
|
|
168
|
+
start(controller) {
|
|
169
|
+
controller.enqueue(encoder.encode(bodyData));
|
|
170
|
+
controller.close();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
readableStream = new web_streams_polyfill_1.ReadableStream({
|
|
176
|
+
start(controller) {
|
|
177
|
+
controller.close();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
data: readableStream,
|
|
183
|
+
statusCode,
|
|
184
|
+
header: responseHeaders
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
let responseData;
|
|
188
|
+
if (typeof bodyData === 'string') {
|
|
189
|
+
try {
|
|
190
|
+
responseData = JSON.parse(bodyData);
|
|
191
|
+
}
|
|
192
|
+
catch (_e) {
|
|
193
|
+
responseData = bodyData;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (bodyData instanceof Buffer) {
|
|
197
|
+
const bodyString = bodyData.toString('utf-8');
|
|
198
|
+
try {
|
|
199
|
+
responseData = JSON.parse(bodyString);
|
|
200
|
+
}
|
|
201
|
+
catch (_f) {
|
|
202
|
+
responseData = bodyString;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
responseData = bodyData;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
data: Promise.resolve(responseData),
|
|
210
|
+
statusCode,
|
|
211
|
+
header: responseHeaders
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* post 方法 - AI 模块可能不使用,但需要实现接口
|
|
216
|
+
*/
|
|
217
|
+
async post() {
|
|
218
|
+
throw new Error('post method is not supported in AI module');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* upload 方法 - AI 模块可能不使用,但需要实现接口
|
|
222
|
+
*/
|
|
223
|
+
async upload() {
|
|
224
|
+
throw new Error('upload method is not supported in AI module');
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* download 方法 - AI 模块可能不使用,但需要实现接口
|
|
228
|
+
*/
|
|
229
|
+
async download() {
|
|
230
|
+
throw new Error('download method is not supported in AI module');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
exports.AIRequestAdapter = AIRequestAdapter;
|
|
234
|
+
function isHeaders(h) {
|
|
235
|
+
try {
|
|
236
|
+
// Node.js 低版本可能没有 Headers
|
|
237
|
+
return h instanceof Headers;
|
|
238
|
+
}
|
|
239
|
+
catch (_) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 从 Node.js 流中读取完整内容为字符串
|
|
245
|
+
*/
|
|
246
|
+
async function readStreamToString(stream) {
|
|
247
|
+
return await new Promise((resolve, reject) => {
|
|
248
|
+
const chunks = [];
|
|
249
|
+
stream.on('data', (chunk) => {
|
|
250
|
+
chunks.push(chunk);
|
|
251
|
+
});
|
|
252
|
+
stream.on('end', () => {
|
|
253
|
+
resolve(Buffer.concat(chunks).toString('utf-8'));
|
|
254
|
+
});
|
|
255
|
+
stream.on('error', (err) => {
|
|
256
|
+
reject(err);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
package/dist/cloudbase.js
CHANGED
|
@@ -47,6 +47,7 @@ const database_1 = require("./database");
|
|
|
47
47
|
const storage_1 = require("./storage");
|
|
48
48
|
const wx_1 = require("./wx");
|
|
49
49
|
const analytics_1 = require("./analytics");
|
|
50
|
+
const ai_1 = require("./ai");
|
|
50
51
|
const logger_1 = require("./logger");
|
|
51
52
|
const code_1 = require("./const/code");
|
|
52
53
|
const utils = __importStar(require("./utils/utils"));
|
|
@@ -75,8 +76,7 @@ class CloudBase {
|
|
|
75
76
|
/* eslint-disable-next-line */
|
|
76
77
|
(0, cloudplatform_1.preflightRuntimeCloudPlatform)();
|
|
77
78
|
const { debug, secretId, secretKey, sessionToken, env, timeout, headers = {} } = config, restConfig = __rest(config, ["debug", "secretId", "secretKey", "sessionToken", "env", "timeout", "headers"]);
|
|
78
|
-
if (('secretId' in config && !('secretKey' in config))
|
|
79
|
-
|| (!('secretId' in config) && 'secretKey' in config)) {
|
|
79
|
+
if (('secretId' in config && !('secretKey' in config)) || (!('secretId' in config) && 'secretKey' in config)) {
|
|
80
80
|
throw utils.E(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'secretId and secretKey must be a pair' }));
|
|
81
81
|
}
|
|
82
82
|
const newConfig = Object.assign(Object.assign({}, restConfig), { debug: !!debug, secretId,
|
|
@@ -115,14 +115,70 @@ class CloudBase {
|
|
|
115
115
|
token: (await this.auth().getClientCredential()).access_token
|
|
116
116
|
});
|
|
117
117
|
return result.body;
|
|
118
|
-
}, (0, tcbopenapiendpoint_1.buildCommonOpenApiUrlWithPath)({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/model' }), {
|
|
119
|
-
sqlBaseUrl: (0, tcbopenapiendpoint_1.buildCommonOpenApiUrlWithPath)({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/sql' })
|
|
118
|
+
}, (0, tcbopenapiendpoint_1.buildCommonOpenApiUrlWithPath)({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/model', region: this.config.region }), {
|
|
119
|
+
sqlBaseUrl: (0, tcbopenapiendpoint_1.buildCommonOpenApiUrlWithPath)({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/sql', region: this.config.region })
|
|
120
120
|
});
|
|
121
121
|
this.models = httpClient;
|
|
122
122
|
}
|
|
123
123
|
catch (e) {
|
|
124
124
|
// ignore
|
|
125
125
|
}
|
|
126
|
+
try {
|
|
127
|
+
const getEntity = (options) => {
|
|
128
|
+
const envId = this.config.envName === symbol_1.SYMBOL_CURRENT_ENV
|
|
129
|
+
? openapicommonrequester.getEnvIdFromContext()
|
|
130
|
+
: this.config.envName;
|
|
131
|
+
const { instance = 'default', database = envId } = options || {};
|
|
132
|
+
const mysqlClient = wx_cloud_client_sdk_1.default.generateMySQLClient(this, {
|
|
133
|
+
mysqlBaseUrl: (0, tcbopenapiendpoint_1.buildCommonOpenApiUrlWithPath)({
|
|
134
|
+
serviceUrl: this.config.serviceUrl,
|
|
135
|
+
envId,
|
|
136
|
+
path: '/v1/rdb/rest',
|
|
137
|
+
region: this.config.region
|
|
138
|
+
}),
|
|
139
|
+
fetch: async (url, options) => {
|
|
140
|
+
var _a;
|
|
141
|
+
let headers = {};
|
|
142
|
+
if (options.headers instanceof Headers) {
|
|
143
|
+
options.headers.forEach((value, key) => {
|
|
144
|
+
headers[key] = value;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
headers = options.headers || {};
|
|
149
|
+
}
|
|
150
|
+
const result = await openapicommonrequester.request({
|
|
151
|
+
config: this.config,
|
|
152
|
+
data: safeParseJSON(options.body),
|
|
153
|
+
method: (_a = options.method) === null || _a === void 0 ? void 0 : _a.toUpperCase(),
|
|
154
|
+
url: url instanceof URL ? url.href : String(url),
|
|
155
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, headersInitToRecord(Object.assign({ 'X-Db-Instance': instance, 'Accept-Profile': database, 'Content-Profile': database }, headers))),
|
|
156
|
+
token: (await this.auth().getClientCredential()).access_token
|
|
157
|
+
});
|
|
158
|
+
const data = result.body;
|
|
159
|
+
const res = {
|
|
160
|
+
ok: (result === null || result === void 0 ? void 0 : result.statusCode) >= 200 && (result === null || result === void 0 ? void 0 : result.statusCode) < 300,
|
|
161
|
+
status: (result === null || result === void 0 ? void 0 : result.statusCode) || 200,
|
|
162
|
+
statusText: (result === null || result === void 0 ? void 0 : result.statusMessage) || 'OK',
|
|
163
|
+
json: async () => await Promise.resolve(data || {}),
|
|
164
|
+
text: async () => await Promise.resolve(typeof data === 'string' ? data : JSON.stringify(data || {})),
|
|
165
|
+
headers: new Headers(incomingHttpHeadersToHeadersInit((result === null || result === void 0 ? void 0 : result.headers) || {}))
|
|
166
|
+
};
|
|
167
|
+
return res;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return mysqlClient;
|
|
171
|
+
};
|
|
172
|
+
this.mysql = (options) => {
|
|
173
|
+
return getEntity(options)(options);
|
|
174
|
+
};
|
|
175
|
+
this.rdb = (options) => {
|
|
176
|
+
return getEntity(options)(options);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
// ignore
|
|
181
|
+
}
|
|
126
182
|
}
|
|
127
183
|
logger() {
|
|
128
184
|
if (!this.clsLogger) {
|
|
@@ -136,6 +192,12 @@ class CloudBase {
|
|
|
136
192
|
database(dbConfig = {}) {
|
|
137
193
|
return (0, database_1.newDb)(this, dbConfig);
|
|
138
194
|
}
|
|
195
|
+
ai() {
|
|
196
|
+
if (!this.aiInstance) {
|
|
197
|
+
this.aiInstance = (0, ai_1.createAI)(this);
|
|
198
|
+
}
|
|
199
|
+
return this.aiInstance;
|
|
200
|
+
}
|
|
139
201
|
async callFunction(callFunctionOptions, opts) {
|
|
140
202
|
return await (0, functions_1.callFunction)(this, callFunctionOptions, opts);
|
|
141
203
|
}
|
|
@@ -240,7 +302,7 @@ function headersInitToRecord(headers) {
|
|
|
240
302
|
});
|
|
241
303
|
}
|
|
242
304
|
else {
|
|
243
|
-
Object.keys(headers).forEach(key => {
|
|
305
|
+
Object.keys(headers).forEach((key) => {
|
|
244
306
|
ret[key] = headers[key];
|
|
245
307
|
});
|
|
246
308
|
}
|
|
@@ -254,3 +316,20 @@ function safeParseJSON(x) {
|
|
|
254
316
|
return x;
|
|
255
317
|
}
|
|
256
318
|
}
|
|
319
|
+
function incomingHttpHeadersToHeadersInit(headers) {
|
|
320
|
+
const result = [];
|
|
321
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
322
|
+
if (value === undefined) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (Array.isArray(value)) {
|
|
326
|
+
for (const v of value) {
|
|
327
|
+
result.push([key, v]);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
result.push([key, value]);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
@@ -113,6 +113,10 @@ async function onResponse(res, { encoding, type = 'json' }) {
|
|
|
113
113
|
if (type === 'stream') {
|
|
114
114
|
return await Promise.resolve(undefined);
|
|
115
115
|
}
|
|
116
|
+
// rawStream: 返回原始的 Node.js 流,用于真正的流式处理
|
|
117
|
+
if (type === 'rawStream') {
|
|
118
|
+
return await Promise.resolve(res);
|
|
119
|
+
}
|
|
116
120
|
if (encoding) {
|
|
117
121
|
res.setEncoding(encoding);
|
|
118
122
|
}
|
|
@@ -22,6 +22,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
26
|
+
var t = {};
|
|
27
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
28
|
+
t[p] = s[p];
|
|
29
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
30
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
31
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
32
|
+
t[p[i]] = s[p[i]];
|
|
33
|
+
}
|
|
34
|
+
return t;
|
|
35
|
+
};
|
|
25
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
37
|
exports.TcbDBApiHttpRequester = void 0;
|
|
27
38
|
const tcbapicaller = __importStar(require("./tcbapirequester"));
|
|
@@ -36,9 +47,10 @@ class TcbDBApiHttpRequester {
|
|
|
36
47
|
* @param opts - 可选配置项
|
|
37
48
|
*/
|
|
38
49
|
async send(api, data, opts) {
|
|
39
|
-
const
|
|
50
|
+
const _a = this.config, { instance, database } = _a, config = __rest(_a, ["instance", "database"]);
|
|
51
|
+
const params = Object.assign(Object.assign({}, data), { action: api, instance, database });
|
|
40
52
|
return await tcbapicaller.request({
|
|
41
|
-
config
|
|
53
|
+
config,
|
|
42
54
|
params,
|
|
43
55
|
method: 'post',
|
|
44
56
|
opts,
|
|
@@ -80,7 +80,7 @@ class TcbOpenApiHttpCommonRequester {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
makeReqOpts() {
|
|
83
|
-
var _a;
|
|
83
|
+
var _a, _b;
|
|
84
84
|
const config = this.config;
|
|
85
85
|
const args = this.args;
|
|
86
86
|
const envId = args.config.envName === symbol_1.SYMBOL_CURRENT_ENV
|
|
@@ -88,7 +88,8 @@ class TcbOpenApiHttpCommonRequester {
|
|
|
88
88
|
: args.config.envName;
|
|
89
89
|
const url = args.url || (0, tcbopenapiendpoint_1.buildCommonOpenApiUrlWithPath)({
|
|
90
90
|
envId,
|
|
91
|
-
path: args.path
|
|
91
|
+
path: args.path,
|
|
92
|
+
region: config.region
|
|
92
93
|
});
|
|
93
94
|
const timeout = ((_a = this.args.opts) === null || _a === void 0 ? void 0 : _a.timeout) || this.config.timeout || this.defaultTimeout;
|
|
94
95
|
const opts = {
|
|
@@ -107,14 +108,20 @@ class TcbOpenApiHttpCommonRequester {
|
|
|
107
108
|
opts.keepalive = typeof config.keepalive === 'boolean' && config.keepalive;
|
|
108
109
|
}
|
|
109
110
|
if (args.data) {
|
|
110
|
-
if (['post', 'put'].includes(args.method.toLowerCase())) {
|
|
111
|
+
if (['post', 'put', 'patch', 'delete'].includes(args.method.toLowerCase())) {
|
|
111
112
|
if (args.isFormData) {
|
|
112
113
|
opts.formData = args.data;
|
|
113
114
|
opts.encoding = null;
|
|
114
115
|
}
|
|
115
116
|
else {
|
|
116
117
|
opts.body = args.data;
|
|
117
|
-
opts.
|
|
118
|
+
if (((_b = args.opts) === null || _b === void 0 ? void 0 : _b.stream) !== true) {
|
|
119
|
+
opts.json = true;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// 使用 rawStream 类型返回原始的 Node.js 流,实现真正的流式响应
|
|
123
|
+
opts.type = 'rawStream';
|
|
124
|
+
}
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
else {
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildCommonOpenApiUrlWithPath = exports.buildUrl = void 0;
|
|
4
|
+
const ZONE_CHINA = ['ap-shanghai', 'ap-guangzhou', 'ap-shenzhen-fsi', 'ap-shanghai-fsi', 'ap-nanjing', 'ap-beijing', 'ap-chengdu', 'ap-chongqing', 'ap-hongkong'];
|
|
4
5
|
/* eslint-disable complexity */
|
|
5
6
|
function buildUrl(options) {
|
|
6
|
-
const endpoint = `https://${options
|
|
7
|
+
const endpoint = `https://${getGatewayUrl(options)}/v1/cloudrun/${options.name}`;
|
|
7
8
|
const path = options.path.startsWith('/') ? options.path : `/${options.path}`;
|
|
8
9
|
return `${endpoint}${path}`;
|
|
9
10
|
}
|
|
10
11
|
exports.buildUrl = buildUrl;
|
|
11
12
|
function buildCommonOpenApiUrlWithPath(options) {
|
|
12
|
-
return `${options.protocol || 'https'}://${options.serviceUrl ||
|
|
13
|
+
return `${options.protocol || 'https'}://${options.serviceUrl || getGatewayUrl(options)}${options.path}`;
|
|
13
14
|
}
|
|
14
15
|
exports.buildCommonOpenApiUrlWithPath = buildCommonOpenApiUrlWithPath;
|
|
16
|
+
function getGatewayUrl(options) {
|
|
17
|
+
const region = options.region || 'ap-shanghai';
|
|
18
|
+
let baseUrl = `${options.envId}.api.tcloudbasegateway.com`;
|
|
19
|
+
if (!ZONE_CHINA.includes(region)) {
|
|
20
|
+
baseUrl = `${options.envId}.api.intl.tcloudbasegateway.com`;
|
|
21
|
+
}
|
|
22
|
+
return baseUrl;
|
|
23
|
+
}
|
|
@@ -88,7 +88,7 @@ class TcbOpenApiHttpRequester {
|
|
|
88
88
|
: args.config.envName;
|
|
89
89
|
const url = (0, tcbopenapiendpoint_1.buildUrl)({
|
|
90
90
|
envId,
|
|
91
|
-
|
|
91
|
+
region: this.config.region,
|
|
92
92
|
// protocol: this.config.protocol || 'https',
|
|
93
93
|
// serviceUrl: this.config.serviceUrl,
|
|
94
94
|
// seqId: this.tracingInfo.seqId,
|
|
@@ -125,6 +125,7 @@ class TcbOpenApiHttpRequester {
|
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
127
|
/* istanbul ignore next */
|
|
128
|
+
// 这里 qs 参数暂时并没使用
|
|
128
129
|
opts.qs = args.data;
|
|
129
130
|
}
|
|
130
131
|
}
|
|
@@ -168,13 +169,18 @@ class TcbOpenApiHttpRequester {
|
|
|
168
169
|
requiredHeaders['X-TCB-Region'] = region;
|
|
169
170
|
}
|
|
170
171
|
requiredHeaders = Object.assign(Object.assign(Object.assign({}, config.headers), args.headers), requiredHeaders);
|
|
172
|
+
let params = args.data || '';
|
|
173
|
+
// GET 方法不支持传 BODY
|
|
174
|
+
if (method.toLowerCase() === 'get') {
|
|
175
|
+
params = '';
|
|
176
|
+
}
|
|
171
177
|
// TODO: 升级SDK版本,否则没传 args.data 时会签名失败
|
|
172
178
|
const { authorization, timestamp } = (0, signature_nodejs_1.sign)({
|
|
173
179
|
secretId,
|
|
174
180
|
secretKey,
|
|
175
181
|
method,
|
|
176
182
|
url,
|
|
177
|
-
params
|
|
183
|
+
params,
|
|
178
184
|
headers: requiredHeaders,
|
|
179
185
|
timestamp: second() - 1,
|
|
180
186
|
withSignedParams: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/node-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0-alpha.0",
|
|
4
4
|
"description": "tencent cloud base server sdk for node.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "types/index.d.ts",
|
|
@@ -41,8 +41,11 @@
|
|
|
41
41
|
"prepare": "husky install"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@cloudbase/
|
|
44
|
+
"@cloudbase/ai": "^2.23.0",
|
|
45
|
+
"@cloudbase/app": "^2.23.0",
|
|
46
|
+
"@cloudbase/database": "1.4.3-alpha.0",
|
|
45
47
|
"@cloudbase/signature-nodejs": "2.2.0",
|
|
48
|
+
"@cloudbase/types": "^2.23.0",
|
|
46
49
|
"@cloudbase/wx-cloud-client-sdk": "1.7.1",
|
|
47
50
|
"agentkeepalive": "^4.3.0",
|
|
48
51
|
"axios": "0.27.2",
|
|
@@ -51,6 +54,7 @@
|
|
|
51
54
|
"https-proxy-agent": "^5.0.1",
|
|
52
55
|
"jsonwebtoken": "^9.0.2",
|
|
53
56
|
"retry": "^0.13.1",
|
|
57
|
+
"web-streams-polyfill": "^4.2.0",
|
|
54
58
|
"xml2js": "^0.6.2"
|
|
55
59
|
},
|
|
56
60
|
"devDependencies": {
|
package/src/ai/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AI } from '@cloudbase/ai'
|
|
2
|
+
import { CloudBase } from '../cloudbase'
|
|
3
|
+
import { AIRequestAdapter } from './request-adapter'
|
|
4
|
+
import { buildCommonOpenApiUrlWithPath } from '../utils/tcbopenapiendpoint'
|
|
5
|
+
import { SYMBOL_CURRENT_ENV } from '../const/symbol'
|
|
6
|
+
import * as openapicommonrequester from '../utils/tcbopenapicommonrequester'
|
|
7
|
+
import { LANGS } from '@cloudbase/app'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 创建 AI 实例
|
|
11
|
+
* @param cloudbase CloudBase 实例
|
|
12
|
+
* @returns AI 实例
|
|
13
|
+
*/
|
|
14
|
+
export function createAI(cloudbase: CloudBase): AI {
|
|
15
|
+
const config = cloudbase.config
|
|
16
|
+
|
|
17
|
+
// 获取环境 ID
|
|
18
|
+
const envId = config.envName === SYMBOL_CURRENT_ENV ? openapicommonrequester.getEnvIdFromContext() : (config.envName as string)
|
|
19
|
+
|
|
20
|
+
// 构建 AI 基础 URL
|
|
21
|
+
const baseUrl = buildCommonOpenApiUrlWithPath({
|
|
22
|
+
serviceUrl: config.serviceUrl,
|
|
23
|
+
envId,
|
|
24
|
+
path: '/v1',
|
|
25
|
+
region: config.region
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// 创建请求适配器
|
|
29
|
+
const requestAdapter = new AIRequestAdapter(config, async () => {
|
|
30
|
+
const credential = await cloudbase.auth().getClientCredential()
|
|
31
|
+
return credential.access_token
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// 创建 AI 实例
|
|
35
|
+
const ai = new AI(requestAdapter, baseUrl, { t: (s) => s, lang: LANGS.ZH, LANG_HEADER_KEY: 'Accept-Language' })
|
|
36
|
+
|
|
37
|
+
return ai
|
|
38
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { SDKRequestInterface, IFetchOptions, ResponseObject } from '@cloudbase/adapter-interface'
|
|
2
|
+
import { ICloudBaseConfig } from '../../types'
|
|
3
|
+
import * as openapicommonrequester from '../utils/tcbopenapicommonrequester'
|
|
4
|
+
import { ReadableStream } from 'web-streams-polyfill'
|
|
5
|
+
import { E } from '../utils/utils'
|
|
6
|
+
|
|
7
|
+
export class AIRequestAdapter implements SDKRequestInterface {
|
|
8
|
+
constructor(private readonly config: ICloudBaseConfig, private readonly getAccessToken: () => Promise<string>) {}
|
|
9
|
+
|
|
10
|
+
async fetch(options: IFetchOptions): Promise<ResponseObject> {
|
|
11
|
+
const { url, stream = false, timeout, method = 'POST', headers, body } = options
|
|
12
|
+
|
|
13
|
+
const headersObj: Record<string, string> = {}
|
|
14
|
+
if (isHeaders(headers)) {
|
|
15
|
+
headers.forEach((value, key) => {
|
|
16
|
+
headersObj[key] = value
|
|
17
|
+
})
|
|
18
|
+
} else if (Array.isArray(headers)) {
|
|
19
|
+
headers.forEach(([k, v]) => (headersObj[k] = v))
|
|
20
|
+
} else {
|
|
21
|
+
Object.assign(headersObj, headers)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let parsedBody: any
|
|
25
|
+
if (typeof body === 'string') {
|
|
26
|
+
try {
|
|
27
|
+
parsedBody = JSON.parse(body)
|
|
28
|
+
} catch {
|
|
29
|
+
parsedBody = body
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
parsedBody = body
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const token = await this.getAccessToken()
|
|
36
|
+
|
|
37
|
+
const result = await openapicommonrequester.request({
|
|
38
|
+
config: this.config,
|
|
39
|
+
data: parsedBody,
|
|
40
|
+
method: method?.toUpperCase() || 'POST',
|
|
41
|
+
url,
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
...headersObj
|
|
45
|
+
},
|
|
46
|
+
token,
|
|
47
|
+
opts: {
|
|
48
|
+
timeout: timeout || this.config.timeout,
|
|
49
|
+
stream
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const { body: bodyData, headers: responseHeaders, statusCode } = result
|
|
54
|
+
|
|
55
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
56
|
+
let errorMessage = `Request failed with status code ${statusCode}`
|
|
57
|
+
let errorCode = `${statusCode}`
|
|
58
|
+
let requestId = ''
|
|
59
|
+
let errorBody: string | null = null
|
|
60
|
+
|
|
61
|
+
if (typeof bodyData === 'string') {
|
|
62
|
+
errorBody = bodyData
|
|
63
|
+
} else if (Buffer.isBuffer(bodyData)) {
|
|
64
|
+
errorBody = bodyData.toString('utf-8')
|
|
65
|
+
} else if (bodyData && typeof bodyData === 'object' && typeof (bodyData as any).on === 'function') {
|
|
66
|
+
errorBody = await readStreamToString(bodyData as unknown as NodeJS.ReadableStream)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (errorBody) {
|
|
70
|
+
try {
|
|
71
|
+
const errorData = JSON.parse(errorBody)
|
|
72
|
+
if (errorData.error?.message) {
|
|
73
|
+
errorMessage = errorData.error.message
|
|
74
|
+
} else if (errorData.message) {
|
|
75
|
+
errorMessage = errorData.message
|
|
76
|
+
}
|
|
77
|
+
if (errorData.error?.code) {
|
|
78
|
+
errorCode = errorData.error.code
|
|
79
|
+
} else if (errorData.code) {
|
|
80
|
+
errorCode = errorData.code
|
|
81
|
+
}
|
|
82
|
+
if (errorData.requestId) {
|
|
83
|
+
requestId = errorData.requestId
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
errorMessage = errorBody || errorMessage
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 从响应头中获取 requestId
|
|
91
|
+
if (!requestId && responseHeaders) {
|
|
92
|
+
const headerRequestId = responseHeaders['x-cloudbase-request-id'] || responseHeaders['x-request-id'] || ''
|
|
93
|
+
requestId = Array.isArray(headerRequestId) ? headerRequestId[0] : headerRequestId
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
throw E({
|
|
97
|
+
code: errorCode,
|
|
98
|
+
message: errorMessage,
|
|
99
|
+
requestId
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (stream) {
|
|
104
|
+
// 对于流式响应,将 Node.js 原生流转换为 Web ReadableStream
|
|
105
|
+
let readableStream: ReadableStream<Uint8Array>
|
|
106
|
+
|
|
107
|
+
if (bodyData && typeof bodyData === 'object' && 'on' in bodyData && typeof bodyData.on === 'function') {
|
|
108
|
+
const nodeStream = bodyData
|
|
109
|
+
// Node 12 兼容: 使用标志位追踪 stream 状态,避免重复 close 导致异常
|
|
110
|
+
let streamClosed = false
|
|
111
|
+
readableStream = new ReadableStream({
|
|
112
|
+
start(controller) {
|
|
113
|
+
nodeStream.on('data', (chunk: Buffer) => {
|
|
114
|
+
if (streamClosed) return
|
|
115
|
+
controller.enqueue(new Uint8Array(chunk))
|
|
116
|
+
})
|
|
117
|
+
nodeStream.on('end', () => {
|
|
118
|
+
if (streamClosed) return
|
|
119
|
+
streamClosed = true
|
|
120
|
+
controller.close()
|
|
121
|
+
})
|
|
122
|
+
nodeStream.on('error', (err) => {
|
|
123
|
+
if (streamClosed) return
|
|
124
|
+
streamClosed = true
|
|
125
|
+
controller.error(err)
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
cancel() {
|
|
129
|
+
streamClosed = true
|
|
130
|
+
nodeStream.destroy()
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
} else if (bodyData instanceof Buffer) {
|
|
134
|
+
readableStream = new ReadableStream({
|
|
135
|
+
start(controller) {
|
|
136
|
+
controller.enqueue(new Uint8Array(bodyData))
|
|
137
|
+
controller.close()
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
} else if (typeof bodyData === 'string') {
|
|
141
|
+
const encoder = new TextEncoder()
|
|
142
|
+
readableStream = new ReadableStream({
|
|
143
|
+
start(controller) {
|
|
144
|
+
controller.enqueue(encoder.encode(bodyData))
|
|
145
|
+
controller.close()
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
} else {
|
|
149
|
+
readableStream = new ReadableStream({
|
|
150
|
+
start(controller) {
|
|
151
|
+
controller.close()
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
data: readableStream,
|
|
158
|
+
statusCode,
|
|
159
|
+
header: responseHeaders
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let responseData: any
|
|
164
|
+
if (typeof bodyData === 'string') {
|
|
165
|
+
try {
|
|
166
|
+
responseData = JSON.parse(bodyData)
|
|
167
|
+
} catch {
|
|
168
|
+
responseData = bodyData
|
|
169
|
+
}
|
|
170
|
+
} else if (bodyData instanceof Buffer) {
|
|
171
|
+
const bodyString = bodyData.toString('utf-8')
|
|
172
|
+
try {
|
|
173
|
+
responseData = JSON.parse(bodyString)
|
|
174
|
+
} catch {
|
|
175
|
+
responseData = bodyString
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
responseData = bodyData
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
data: Promise.resolve(responseData),
|
|
183
|
+
statusCode,
|
|
184
|
+
header: responseHeaders
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* post 方法 - AI 模块可能不使用,但需要实现接口
|
|
190
|
+
*/
|
|
191
|
+
async post() {
|
|
192
|
+
throw new Error('post method is not supported in AI module')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* upload 方法 - AI 模块可能不使用,但需要实现接口
|
|
197
|
+
*/
|
|
198
|
+
async upload() {
|
|
199
|
+
throw new Error('upload method is not supported in AI module')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* download 方法 - AI 模块可能不使用,但需要实现接口
|
|
204
|
+
*/
|
|
205
|
+
async download() {
|
|
206
|
+
throw new Error('download method is not supported in AI module')
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isHeaders(h: HeadersInit): h is Headers {
|
|
211
|
+
try {
|
|
212
|
+
// Node.js 低版本可能没有 Headers
|
|
213
|
+
return h instanceof Headers
|
|
214
|
+
} catch (_) {
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 从 Node.js 流中读取完整内容为字符串
|
|
221
|
+
*/
|
|
222
|
+
async function readStreamToString(stream: NodeJS.ReadableStream): Promise<string> {
|
|
223
|
+
return await new Promise((resolve, reject) => {
|
|
224
|
+
const chunks: Buffer[] = []
|
|
225
|
+
stream.on('data', (chunk: Buffer) => {
|
|
226
|
+
chunks.push(chunk)
|
|
227
|
+
})
|
|
228
|
+
stream.on('end', () => {
|
|
229
|
+
resolve(Buffer.concat(chunks).toString('utf-8'))
|
|
230
|
+
})
|
|
231
|
+
stream.on('error', (err) => {
|
|
232
|
+
reject(err)
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
}
|
package/src/cloudbase.ts
CHANGED
|
@@ -36,7 +36,8 @@ import {
|
|
|
36
36
|
IReportData,
|
|
37
37
|
|
|
38
38
|
Extension,
|
|
39
|
-
ICallApisOptions
|
|
39
|
+
ICallApisOptions,
|
|
40
|
+
ICloudBaseDBConfig
|
|
40
41
|
} from '../types'
|
|
41
42
|
|
|
42
43
|
import { auth } from './auth'
|
|
@@ -46,6 +47,8 @@ import { newDb } from './database'
|
|
|
46
47
|
import { uploadFile, deleteFile, getTempFileURL, getFileInfo, downloadFile, getUploadMetadata, getFileAuthority, copyFile } from './storage'
|
|
47
48
|
import { callWxOpenApi, callCompatibleWxOpenApi, callWxPayApi, wxCallContainerApi } from './wx'
|
|
48
49
|
import { analytics } from './analytics'
|
|
50
|
+
import { createAI } from './ai'
|
|
51
|
+
import { AI } from '@cloudbase/ai'
|
|
49
52
|
|
|
50
53
|
import { Logger, logger } from './logger'
|
|
51
54
|
|
|
@@ -59,6 +62,7 @@ import * as openapicommonrequester from './utils/tcbopenapicommonrequester'
|
|
|
59
62
|
import { IFetchOptions } from '@cloudbase/adapter-interface'
|
|
60
63
|
import { buildCommonOpenApiUrlWithPath } from './utils/tcbopenapiendpoint'
|
|
61
64
|
import { SYMBOL_CURRENT_ENV } from './const/symbol'
|
|
65
|
+
import { IncomingHttpHeaders } from 'http'
|
|
62
66
|
|
|
63
67
|
export class CloudBase {
|
|
64
68
|
public static scfContext: ISCFContext
|
|
@@ -79,6 +83,8 @@ export class CloudBase {
|
|
|
79
83
|
|
|
80
84
|
private extensionMap: Map<string, Extension>
|
|
81
85
|
|
|
86
|
+
private aiInstance: AI
|
|
87
|
+
|
|
82
88
|
public models: OrmClient & OrmRawQueryClient
|
|
83
89
|
|
|
84
90
|
public mysql: IMySqlClient
|
|
@@ -94,19 +100,9 @@ export class CloudBase {
|
|
|
94
100
|
/* eslint-disable-next-line */
|
|
95
101
|
preflightRuntimeCloudPlatform()
|
|
96
102
|
|
|
97
|
-
const {
|
|
98
|
-
debug,
|
|
99
|
-
secretId,
|
|
100
|
-
secretKey,
|
|
101
|
-
sessionToken,
|
|
102
|
-
env,
|
|
103
|
-
timeout,
|
|
104
|
-
headers = {},
|
|
105
|
-
...restConfig
|
|
106
|
-
} = config
|
|
103
|
+
const { debug, secretId, secretKey, sessionToken, env, timeout, headers = {}, ...restConfig } = config
|
|
107
104
|
|
|
108
|
-
if (('secretId' in config && !('secretKey' in config))
|
|
109
|
-
|| (!('secretId' in config) && 'secretKey' in config)) {
|
|
105
|
+
if (('secretId' in config && !('secretKey' in config)) || (!('secretId' in config) && 'secretKey' in config)) {
|
|
110
106
|
throw utils.E({
|
|
111
107
|
...ERROR.INVALID_PARAM,
|
|
112
108
|
message: 'secretId and secretKey must be a pair'
|
|
@@ -220,13 +216,10 @@ export class CloudBase {
|
|
|
220
216
|
const res = {
|
|
221
217
|
ok: result?.statusCode >= 200 && result?.statusCode < 300,
|
|
222
218
|
status: result?.statusCode || 200,
|
|
223
|
-
statusText: result?.statusMessage || 'OK',
|
|
219
|
+
statusText: (result as unknown as { statusMessage: string })?.statusMessage || 'OK',
|
|
224
220
|
json: async () => await Promise.resolve(data || {}),
|
|
225
|
-
text: async () =>
|
|
226
|
-
|
|
227
|
-
typeof data === 'string' ? data : JSON.stringify(data || {})
|
|
228
|
-
),
|
|
229
|
-
headers: new Headers(result?.headers || {})
|
|
221
|
+
text: async () => await Promise.resolve(typeof data === 'string' ? data : JSON.stringify(data || {})),
|
|
222
|
+
headers: new Headers(incomingHttpHeadersToHeadersInit(result?.headers || {}))
|
|
230
223
|
}
|
|
231
224
|
|
|
232
225
|
return res as Response
|
|
@@ -258,10 +251,17 @@ export class CloudBase {
|
|
|
258
251
|
return auth(this)
|
|
259
252
|
}
|
|
260
253
|
|
|
261
|
-
public database(dbConfig:
|
|
254
|
+
public database(dbConfig: ICloudBaseDBConfig = {}) {
|
|
262
255
|
return newDb(this, dbConfig)
|
|
263
256
|
}
|
|
264
257
|
|
|
258
|
+
public ai(): AI {
|
|
259
|
+
if (!this.aiInstance) {
|
|
260
|
+
this.aiInstance = createAI(this)
|
|
261
|
+
}
|
|
262
|
+
return this.aiInstance
|
|
263
|
+
}
|
|
264
|
+
|
|
265
265
|
public async callFunction<ParaT, ResultT>(callFunctionOptions: ICallFunctionOptions<ParaT>, opts?: ICustomReqOpts) {
|
|
266
266
|
return await callFunction<ParaT, ResultT>(this, callFunctionOptions, opts)
|
|
267
267
|
}
|
|
@@ -270,8 +270,8 @@ export class CloudBase {
|
|
|
270
270
|
return await callContainer<ParaT, ResultT>(this, callContainerOptions, opts)
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
public async callApis<ParaT
|
|
274
|
-
return await callApis<ParaT
|
|
273
|
+
public async callApis<ParaT>(callApiOptions: ICallApisOptions<ParaT>, opts?: ICustomReqOpts) {
|
|
274
|
+
return await callApis<ParaT>(this, callApiOptions, opts)
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
public async callWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<ICallWxOpenApiResult> {
|
|
@@ -384,7 +384,7 @@ function headersInitToRecord(headers: HeadersInit): Record<string, string> {
|
|
|
384
384
|
ret[key] = value
|
|
385
385
|
})
|
|
386
386
|
} else {
|
|
387
|
-
Object.keys(headers).forEach(key => {
|
|
387
|
+
Object.keys(headers).forEach((key) => {
|
|
388
388
|
ret[key] = headers[key]
|
|
389
389
|
})
|
|
390
390
|
}
|
|
@@ -398,3 +398,20 @@ function safeParseJSON(x: unknown) {
|
|
|
398
398
|
return x
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
|
+
|
|
402
|
+
function incomingHttpHeadersToHeadersInit(headers: IncomingHttpHeaders): HeadersInit {
|
|
403
|
+
const result: Array<[string, string]> = []
|
|
404
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
405
|
+
if (value === undefined) {
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
if (Array.isArray(value)) {
|
|
409
|
+
for (const v of value) {
|
|
410
|
+
result.push([key, v])
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
result.push([key, value])
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return result
|
|
417
|
+
}
|
package/src/functions/index.ts
CHANGED
|
@@ -76,7 +76,7 @@ export async function callFunction<ParaT, ResultT>(cloudbase: CloudBase, callFun
|
|
|
76
76
|
* @param opts
|
|
77
77
|
* @returns
|
|
78
78
|
*/
|
|
79
|
-
export async function callApis<ParaT
|
|
79
|
+
export async function callApis<ParaT>(cloudbase: CloudBase, callApiOptions: ICallApisOptions<ParaT>, opts?: ICustomReqOpts) {
|
|
80
80
|
let { name, body, path = '', method = 'POST', header = {}, token = '' } = callApiOptions
|
|
81
81
|
|
|
82
82
|
if (!name) {
|
|
@@ -101,12 +101,17 @@ async function onResponse(
|
|
|
101
101
|
{
|
|
102
102
|
encoding,
|
|
103
103
|
type = 'json'
|
|
104
|
-
}: { encoding?: string, type?: 'stream' | 'raw' | 'json' }
|
|
105
|
-
): Promise<string | Buffer | undefined> {
|
|
104
|
+
}: { encoding?: string, type?: 'stream' | 'raw' | 'json' | 'rawStream' }
|
|
105
|
+
): Promise<string | Buffer | http.IncomingMessage | undefined> {
|
|
106
106
|
if (type === 'stream') {
|
|
107
107
|
return await Promise.resolve(undefined)
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// rawStream: 返回原始的 Node.js 流,用于真正的流式处理
|
|
111
|
+
if (type === 'rawStream') {
|
|
112
|
+
return await Promise.resolve(res)
|
|
113
|
+
}
|
|
114
|
+
|
|
110
115
|
if (encoding) {
|
|
111
116
|
res.setEncoding(encoding)
|
|
112
117
|
}
|
|
@@ -171,7 +176,13 @@ function onTimeout(req: http.ClientRequest, cb: RequestCB) {
|
|
|
171
176
|
|
|
172
177
|
export type RequestCB = (err: Error, res?: http.IncomingMessage, body?: string | Buffer) => void
|
|
173
178
|
|
|
174
|
-
|
|
179
|
+
// 用于 rawStream 类型的回调,body 参数实际上是 IncomingMessage
|
|
180
|
+
export type RequestCBWithStream = (err: Error, res?: http.IncomingMessage, body?: string | Buffer | http.IncomingMessage) => void
|
|
181
|
+
|
|
182
|
+
// 函数重载:支持普通回调和流式回调
|
|
183
|
+
export function request(opts: IReqOpts, cb: RequestCB): http.ClientRequest
|
|
184
|
+
export function request(opts: IReqOpts & { type: 'rawStream' }, cb: RequestCBWithStream): http.ClientRequest
|
|
185
|
+
export function request(opts: IReqOpts, cb: RequestCB | RequestCBWithStream): http.ClientRequest {
|
|
175
186
|
const times = opts.times || 1
|
|
176
187
|
|
|
177
188
|
const options: http.ClientRequestArgs = {
|
|
@@ -200,7 +211,7 @@ export function request(opts: IReqOpts, cb: RequestCB): http.ClientRequest {
|
|
|
200
211
|
type: opts.json ? 'json' : opts.type
|
|
201
212
|
})
|
|
202
213
|
.then((body) => {
|
|
203
|
-
cb(null, res, body)
|
|
214
|
+
(cb as RequestCBWithStream)(null, res, body)
|
|
204
215
|
})
|
|
205
216
|
.catch((err) => {
|
|
206
217
|
cb(err)
|
package/src/utils/request.ts
CHANGED
|
@@ -67,7 +67,7 @@ interface IExtraRequestOptions {
|
|
|
67
67
|
interface IResponse {
|
|
68
68
|
statusCode: number
|
|
69
69
|
headers: http.IncomingHttpHeaders
|
|
70
|
-
body: string | Buffer
|
|
70
|
+
body: string | Buffer | http.IncomingMessage
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/* istanbul ignore next */
|
|
@@ -99,7 +99,7 @@ export async function requestWithTimingsMeasure(opts: IReqOpts, extraOptions?: I
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
; (function r() {
|
|
102
|
-
const cRequest = request(opts, (err: Error, res: http.IncomingMessage, body
|
|
102
|
+
const cRequest = request(opts, (err: Error, res: http.IncomingMessage, body) => {
|
|
103
103
|
if (err) {
|
|
104
104
|
reject(err)
|
|
105
105
|
} else {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as tcbapicaller from './tcbapirequester'
|
|
2
|
-
import {
|
|
2
|
+
import { ICustomReqOpts, ICloudBaseDBConfig } from '../../types'
|
|
3
3
|
|
|
4
4
|
export class TcbDBApiHttpRequester {
|
|
5
|
-
private readonly config:
|
|
5
|
+
private readonly config: ICloudBaseDBConfig
|
|
6
6
|
|
|
7
|
-
public constructor(config:
|
|
7
|
+
public constructor(config: ICloudBaseDBConfig) {
|
|
8
8
|
this.config = config
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -15,10 +15,11 @@ export class TcbDBApiHttpRequester {
|
|
|
15
15
|
* @param opts - 可选配置项
|
|
16
16
|
*/
|
|
17
17
|
public async send(api: string, data: any, opts?: ICustomReqOpts): Promise<any> {
|
|
18
|
-
const
|
|
18
|
+
const { instance, database, ...config } = this.config
|
|
19
|
+
const params = { ...data, action: api, instance, database }
|
|
19
20
|
|
|
20
21
|
return await tcbapicaller.request({
|
|
21
|
-
config
|
|
22
|
+
config,
|
|
22
23
|
params,
|
|
23
24
|
method: 'post',
|
|
24
25
|
opts,
|
|
@@ -53,7 +53,7 @@ export class TcbOpenApiHttpCommonRequester {
|
|
|
53
53
|
this.tracingInfo = generateTracingInfo(args.config?.context?.eventID)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
public async request()
|
|
56
|
+
public async request() {
|
|
57
57
|
await this.prepareCredentials()
|
|
58
58
|
|
|
59
59
|
const opts = this.makeReqOpts()
|
|
@@ -75,7 +75,7 @@ export class TcbOpenApiHttpCommonRequester {
|
|
|
75
75
|
seqId: this.tracingInfo.seqId,
|
|
76
76
|
retryOptions,
|
|
77
77
|
timingsMeasurerOptions: config.timingsMeasurerOptions || {}
|
|
78
|
-
}).then((response
|
|
78
|
+
}).then((response) => {
|
|
79
79
|
this.slowWarnTimer && clearTimeout(this.slowWarnTimer)
|
|
80
80
|
return response
|
|
81
81
|
})
|
|
@@ -120,7 +120,12 @@ export class TcbOpenApiHttpCommonRequester {
|
|
|
120
120
|
opts.encoding = null
|
|
121
121
|
} else {
|
|
122
122
|
opts.body = args.data
|
|
123
|
-
opts
|
|
123
|
+
if (args.opts?.stream !== true) {
|
|
124
|
+
opts.json = true
|
|
125
|
+
} else {
|
|
126
|
+
// 使用 rawStream 类型返回原始的 Node.js 流,实现真正的流式响应
|
|
127
|
+
opts.type = 'rawStream'
|
|
128
|
+
}
|
|
124
129
|
}
|
|
125
130
|
} else {
|
|
126
131
|
/* istanbul ignore next */
|
|
@@ -201,7 +206,7 @@ export class TcbOpenApiHttpCommonRequester {
|
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
|
|
204
|
-
export async function request(args: ITcbOpenApiHttpCommonRequestInfo)
|
|
209
|
+
export async function request(args: ITcbOpenApiHttpCommonRequestInfo) {
|
|
205
210
|
if (typeof args.isInternal === 'undefined') {
|
|
206
211
|
args.isInternal = await checkIsInternalAsync()
|
|
207
212
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs'
|
|
|
2
2
|
|
|
3
3
|
import { Db } from '@cloudbase/database'
|
|
4
4
|
import { OrmClient, OrmRawQueryClient } from '@cloudbase/wx-cloud-client-sdk'
|
|
5
|
+
import { AI } from '@cloudbase/ai'
|
|
5
6
|
|
|
6
7
|
type IKeyValue = Record<string, any>
|
|
7
8
|
|
|
@@ -247,6 +248,19 @@ export interface IBaseResult {
|
|
|
247
248
|
requestId?: string
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
/* ********************************** database ********************************** */
|
|
252
|
+
|
|
253
|
+
export interface ICloudBaseDBConfig extends ICloudBaseConfig {
|
|
254
|
+
/**
|
|
255
|
+
* 数据库实例
|
|
256
|
+
*/
|
|
257
|
+
instance?: string
|
|
258
|
+
/**
|
|
259
|
+
* 数据库名称
|
|
260
|
+
*/
|
|
261
|
+
database?: string
|
|
262
|
+
}
|
|
263
|
+
|
|
250
264
|
/* ********************************** storage ********************************** */
|
|
251
265
|
|
|
252
266
|
export interface IUploadFileOptions {
|
|
@@ -520,7 +534,7 @@ export declare class CloudBase {
|
|
|
520
534
|
/**
|
|
521
535
|
* database - 获取云数据库实例
|
|
522
536
|
*/
|
|
523
|
-
database(dbConfig:
|
|
537
|
+
database(dbConfig: ICloudBaseDBConfig = {}): Db
|
|
524
538
|
|
|
525
539
|
/**
|
|
526
540
|
* callFunction - 调用云函数
|
|
@@ -570,6 +584,11 @@ export declare class CloudBase {
|
|
|
570
584
|
wxCallContainerApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any>
|
|
571
585
|
callCompatibleWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any>
|
|
572
586
|
|
|
587
|
+
/**
|
|
588
|
+
* ai - 获取 AI 实例
|
|
589
|
+
*/
|
|
590
|
+
ai(): AI
|
|
591
|
+
|
|
573
592
|
registerExtension(ext: Extension): void
|
|
574
593
|
invokeExtension<T>(name: string, opts: T)
|
|
575
594
|
}
|
package/types/internal.d.ts
CHANGED