@maiyunnet/kebab 3.2.28 → 3.2.30

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/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "3.2.28";
8
+ export declare const VER = "3.2.30";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  export declare const LIB_PATH: string;
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '3.2.28';
9
+ export const VER = '3.2.30';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
package/lib/core.d.ts CHANGED
@@ -249,3 +249,10 @@ export declare function display(message?: any, ...optionalParams: any[]): void;
249
249
  * @param headers 头部
250
250
  */
251
251
  export declare function writeHead(res: http2.Http2ServerResponse | http.ServerResponse, statusCode: number, headers?: http.OutgoingHttpHeaders): void;
252
+ export declare function writeEventStreamHead(res: http2.Http2ServerResponse | http.ServerResponse): void;
253
+ /**
254
+ * --- 向 res 发送数据 ---
255
+ * @param res 响应对象
256
+ * @param data 数据
257
+ */
258
+ export declare function write(res: http2.Http2ServerResponse | http.ServerResponse, data: string | Buffer): void;
package/lib/core.js CHANGED
@@ -806,3 +806,22 @@ export function writeHead(res, statusCode, headers) {
806
806
  res.writeHead(statusCode, headers);
807
807
  }
808
808
  }
809
+ export function writeEventStreamHead(res) {
810
+ writeHead(res, 200, {
811
+ 'content-type': 'text/event-stream; charset=utf-8',
812
+ 'cache-control': 'no-store',
813
+ });
814
+ }
815
+ /**
816
+ * --- 向 res 发送数据 ---
817
+ * @param res 响应对象
818
+ * @param data 数据
819
+ */
820
+ export function write(res, data) {
821
+ if (res instanceof http2.Http2ServerResponse) {
822
+ res.write(data);
823
+ }
824
+ else {
825
+ res.write(data);
826
+ }
827
+ }
package/lib/db.d.ts CHANGED
@@ -15,6 +15,8 @@ export interface IData {
15
15
  'errno': number;
16
16
  [key: string]: any;
17
17
  } | null;
18
+ /** --- 1-正常,-500-服务器错误 --- */
19
+ 'result': number;
18
20
  }
19
21
  /** --- exec 返回对象 --- */
20
22
  export interface IPacket {
@@ -49,6 +51,7 @@ export declare class Pool {
49
51
  * --- 执行一条 SQL,无视顺序和相同连接,随用随取 ---
50
52
  * @param sql 执行的 SQL 字符串
51
53
  * @param values 要替换的 data 数据
54
+ * @returns error.errno = -500 表示系统错误
52
55
  */
53
56
  query(sql: string, values?: kebab.DbValue[]): Promise<IData>;
54
57
  /**
package/lib/db.js CHANGED
@@ -79,6 +79,7 @@ export class Pool {
79
79
  * --- 执行一条 SQL,无视顺序和相同连接,随用随取 ---
80
80
  * @param sql 执行的 SQL 字符串
81
81
  * @param values 要替换的 data 数据
82
+ * @returns error.errno = -500 表示系统错误
82
83
  */
83
84
  async query(sql, values) {
84
85
  ++this._queries;
@@ -89,9 +90,10 @@ export class Pool {
89
90
  'rows': null,
90
91
  'fields': [],
91
92
  'error': {
92
- 'message': 'null',
93
- 'errno': 0
94
- }
93
+ 'message': 'false',
94
+ 'errno': 0,
95
+ },
96
+ 'result': -500,
95
97
  };
96
98
  }
97
99
  // --- 执行一次后自动解除 using ---
@@ -237,9 +239,10 @@ export class Transaction {
237
239
  'rows': null,
238
240
  'fields': [],
239
241
  'error': {
240
- 'message': 'null',
242
+ 'message': 'false',
241
243
  'errno': 0
242
- }
244
+ },
245
+ 'result': -500,
243
246
  };
244
247
  }
245
248
  ++this._queries;
@@ -439,7 +442,8 @@ export class Connection {
439
442
  return {
440
443
  'rows': null,
441
444
  'fields': [],
442
- 'error': e
445
+ 'error': e,
446
+ 'result': -500,
443
447
  };
444
448
  }
445
449
  if (!this._transaction) {
@@ -449,7 +453,8 @@ export class Connection {
449
453
  return {
450
454
  'rows': res[0],
451
455
  'fields': res[1],
452
- 'error': null
456
+ 'error': null,
457
+ 'result': 1,
453
458
  };
454
459
  }
455
460
  /**
package/lib/kv.d.ts CHANGED
@@ -62,7 +62,7 @@ export declare class Pool {
62
62
  * --- 获取字符串 ---
63
63
  * @param key
64
64
  */
65
- get(key: string): Promise<string | null>;
65
+ get(key: string): Promise<string | false | null>;
66
66
  /**
67
67
  * --- 获取相应的剩余有效期秒数 ---
68
68
  * @param key
@@ -86,8 +86,9 @@ export declare class Pool {
86
86
  /**
87
87
  * --- 获取 json 对象 ---
88
88
  * @param key
89
+ * @returns false 表示系统错误, null 表示不存在, 其他值为 json 对象
89
90
  */
90
- getJson(key: string): Promise<any | null>;
91
+ getJson(key: string): Promise<any | false | null>;
91
92
  /**
92
93
  * --- 删除已存在的值 ---
93
94
  * @param keys
@@ -301,9 +302,9 @@ export declare class Connection {
301
302
  * --- 获取字符串 ---
302
303
  * @param key
303
304
  * @param etc
304
- * @returns 字符串或 null(即使存入时是 number,这个方法也只会返回字符串)
305
+ * @returns 字符串 / false / null(即使存入时是 number,这个方法也只会返回字符串)
305
306
  */
306
- get(key: string, etc: kebab.IConfigKv): Promise<string | null>;
307
+ get(key: string, etc: kebab.IConfigKv): Promise<string | false | null>;
307
308
  /**
308
309
  * --- 获取相应的剩余有效期秒数 ---
309
310
  * @param key
@@ -334,7 +335,7 @@ export declare class Connection {
334
335
  * @param key
335
336
  * @param etc
336
337
  */
337
- getJson(key: string, etc: kebab.IConfigKv): Promise<any | null>;
338
+ getJson(key: string, etc: kebab.IConfigKv): Promise<any | false | null>;
338
339
  /**
339
340
  * --- 删除已存在的值 ---
340
341
  * @param keys
package/lib/kv.js CHANGED
@@ -236,11 +236,12 @@ export class Pool {
236
236
  /**
237
237
  * --- 获取 json 对象 ---
238
238
  * @param key
239
+ * @returns false 表示系统错误, null 表示不存在, 其他值为 json 对象
239
240
  */
240
241
  async getJson(key) {
241
242
  const conn = await this._getConnection();
242
243
  if (!conn) {
243
- return null;
244
+ return false;
244
245
  }
245
246
  const r = await conn.getJson(key, this._etc);
246
247
  conn.used();
@@ -823,7 +824,7 @@ end`;
823
824
  * --- 获取字符串 ---
824
825
  * @param key
825
826
  * @param etc
826
- * @returns 字符串或 null(即使存入时是 number,这个方法也只会返回字符串)
827
+ * @returns 字符串 / false / null(即使存入时是 number,这个方法也只会返回字符串)
827
828
  */
828
829
  async get(key, etc) {
829
830
  this.refreshLast();
@@ -831,7 +832,7 @@ end`;
831
832
  return await this._link.get(etc.pre + key);
832
833
  }
833
834
  catch {
834
- return null;
835
+ return false;
835
836
  }
836
837
  }
837
838
  /**
@@ -917,11 +918,11 @@ end`;
917
918
  */
918
919
  async getJson(key, etc) {
919
920
  const v = await this.get(key, etc);
920
- if (v === null) {
921
- return null;
921
+ if (v === null || v === false) {
922
+ return v;
922
923
  }
923
924
  const r = lText.parseJson(v);
924
- return r === false ? null : r;
925
+ return r;
925
926
  }
926
927
  /**
927
928
  * --- 删除已存在的值 ---
package/lib/session.d.ts CHANGED
@@ -31,6 +31,7 @@ export declare class Session {
31
31
  * @param link Kv 或 Db 实例
32
32
  * @param auth 设为 true 则优先从头 Authorization 或 post _auth 值读取 token
33
33
  * @param opt 选项
34
+ * @returns false 表示系统错误
34
35
  */
35
36
  init(ctr: ctr.Ctr, link: db.Pool | kv.Pool, auth?: boolean, opt?: IOptions): Promise<boolean>;
36
37
  /**
package/lib/session.js CHANGED
@@ -36,6 +36,7 @@ export class Session {
36
36
  * @param link Kv 或 Db 实例
37
37
  * @param auth 设为 true 则优先从头 Authorization 或 post _auth 值读取 token
38
38
  * @param opt 选项
39
+ * @returns false 表示系统错误
39
40
  */
40
41
  async init(ctr, link, auth = false, opt = {}) {
41
42
  const config = ctr.getPrototype('_config');
@@ -68,10 +69,13 @@ export class Session {
68
69
  // --- 如果启用了内存加速则在内存找 ---
69
70
  if (this._link instanceof kv.Pool) {
70
71
  // --- Kv ---
71
- let data;
72
- if ((data = await this._link.getJson(this._name + '_' + this._token)) === null) {
72
+ const data = await this._link.getJson(this._name + '_' + this._token);
73
+ if (data === null) {
73
74
  needInsert = true;
74
75
  }
76
+ else if (data === false) {
77
+ return false;
78
+ }
75
79
  else {
76
80
  session = data;
77
81
  }
@@ -86,6 +90,9 @@ export class Session {
86
90
  if (data.rows?.[0]) {
87
91
  session = text.parseJson(data.rows[0].data);
88
92
  }
93
+ else if (data.result === -500) {
94
+ return false;
95
+ }
89
96
  else {
90
97
  needInsert = true;
91
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "3.2.28",
3
+ "version": "3.2.30",
4
4
  "description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
5
5
  "type": "module",
6
6
  "keywords": [
package/sys/child.js CHANGED
@@ -164,6 +164,8 @@ async function run() {
164
164
  delete linkCount[key];
165
165
  }
166
166
  });
167
+ }).on('clientError', (err, socket) => {
168
+ socket.destroy();
167
169
  }).on('upgrade', function (req, socket, head) {
168
170
  const host = (req.headers['host'] ?? '');
169
171
  if (!host) {
package/sys/ctr.d.ts CHANGED
@@ -58,7 +58,7 @@ export declare class Ctr {
58
58
  protected _localeData: Record<string, Record<string, string>>;
59
59
  constructor(config: kebab.IConfig, req: http2.Http2ServerRequest | http.IncomingMessage, res?: http2.Http2ServerResponse | http.ServerResponse);
60
60
  /** --- 当前用户连接是否还在连接中 --- */
61
- get isAvail(): boolean;
61
+ protected get _isAvail(): boolean;
62
62
  /** --- timeout 的 timer --- */
63
63
  protected _timer?: {
64
64
  'timer': NodeJS.Timeout;
@@ -206,7 +206,7 @@ export declare class Ctr {
206
206
  * @param auth 设为 true 则从头 Authorization 或 post _auth 值读取 token
207
207
  * @param opt name, ttl, ssl, sqlPre
208
208
  */
209
- protected _startSession(link: lDb.Pool | lKv.Pool, auth?: boolean, opt?: lSession.IOptions): Promise<void>;
209
+ protected _startSession(link: lDb.Pool | lKv.Pool, auth?: boolean, opt?: lSession.IOptions): Promise<boolean>;
210
210
  /**
211
211
  * --- 设定语言并加载语言包 ---
212
212
  * @param loc 要加载的目标语言
package/sys/ctr.js CHANGED
@@ -78,7 +78,7 @@ export class Ctr {
78
78
  this._cacheTTL = config.set.cacheTtl;
79
79
  }
80
80
  /** --- 当前用户连接是否还在连接中 --- */
81
- get isAvail() {
81
+ get _isAvail() {
82
82
  return this._req.socket.writable;
83
83
  }
84
84
  /** --- 获取当前过期时间 --- */
@@ -603,9 +603,9 @@ export class Ctr {
603
603
  * @param auth 设为 true 则从头 Authorization 或 post _auth 值读取 token
604
604
  * @param opt name, ttl, ssl, sqlPre
605
605
  */
606
- async _startSession(link, auth = false, opt = {}) {
606
+ _startSession(link, auth = false, opt = {}) {
607
607
  this._sess = new lSession.Session();
608
- await this._sess.init(this, link, auth, opt);
608
+ return this._sess.init(this, link, auth, opt);
609
609
  }
610
610
  // --- 本地化 ---
611
611
  /**
package/sys/route.js CHANGED
@@ -505,7 +505,7 @@ export async function run(data) {
505
505
  pathRight = pathRight.replace(/-([a-zA-Z0-9])/g, function (t, t1) {
506
506
  return t1.toUpperCase();
507
507
  });
508
- if (cctr[pathRight] === undefined) {
508
+ if ((cctr[pathRight] === undefined) || (typeof cctr[pathRight] !== 'function')) {
509
509
  if (config.route['#404']) {
510
510
  data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
511
511
  lCore.writeHead(data.res, 302);
@@ -101,6 +101,8 @@ export default class extends sCtr.Ctr {
101
101
  lan(): Promise<string>;
102
102
  cron(): Promise<string>;
103
103
  ai(): Promise<string>;
104
+ aiStream(): string;
105
+ aiStream1(): Promise<any>;
104
106
  /**
105
107
  * --- END ---
106
108
  */
@@ -128,6 +128,7 @@ export default class extends sCtr.Ctr {
128
128
  '<br><br><b>Library test:</b>',
129
129
  `<br><br><b>Ai:</b>`,
130
130
  `<br><br><a href="${this._config.const.urlBase}test/ai">View "test/ai"</a>`,
131
+ `<br><a href="${this._config.const.urlBase}test/ai-stream">View "test/ai-stream"</a>`,
131
132
  '<br><br><b>Core:</b>',
132
133
  `<br><br><a href="${this._config.const.urlBase}test/core-random">View "test/core-random"</a>`,
133
134
  `<br><a href="${this._config.const.urlBase}test/core-rand">View "test/core-rand"</a>`,
@@ -2133,8 +2134,8 @@ function confirm() {
2133
2134
  echo.push('link = lKv.get(this);\n');
2134
2135
  }
2135
2136
  if (this._get['auth'] === '') {
2136
- await this._startSession(link, false, { 'ttl': 60 });
2137
- echo.push(`await this._startSession(link, false, {'ttl': 60});
2137
+ const r = await this._startSession(link, false, { 'ttl': 60 });
2138
+ echo.push(`await this._startSession(link, false, {'ttl': 60}); // ${r}
2138
2139
  JSON.stringify(this._session);</pre>` + lText.htmlescape(JSON.stringify(this._session)));
2139
2140
  this._session['value'] = lText.logicalOr(this._get['value'], 'ok');
2140
2141
  echo.push(`<pre>this._session['value'] = '${lText.logicalOr(this._get['value'], 'ok')}';
@@ -2146,7 +2147,7 @@ JSON.stringify(this._session);</pre>` + lText.htmlescape(JSON.stringify(this._se
2146
2147
  }
2147
2148
  else {
2148
2149
  // --- AUTH 模式 ---
2149
- await this._startSession(link, true, { 'ttl': 60 });
2150
+ const r = await this._startSession(link, true, { 'ttl': 60 });
2150
2151
  if (Object.keys(this._post).length > 0) {
2151
2152
  if (this._session['count'] === undefined) {
2152
2153
  this._session['count'] = 1;
@@ -2157,7 +2158,7 @@ JSON.stringify(this._session);</pre>` + lText.htmlescape(JSON.stringify(this._se
2157
2158
  return [1, { 'txt': 'this._session: ' + JSON.stringify(this._session) + '\nToken: ' + this._sess.getToken(), 'token': this._sess?.getToken(), '_auth': this._getBasicAuth('token', this._sess.getToken()) }];
2158
2159
  }
2159
2160
  else {
2160
- echo.push(`await this._startSession(link, true, {'ttl': 60});
2161
+ echo.push(`await this._startSession(link, true, {'ttl': 60}); // ${r}
2161
2162
  JSON.stringify(this._session));</pre>` + lText.htmlescape(JSON.stringify(this._session)));
2162
2163
  this._session['value'] = lTime.format(this, 'H:i:s');
2163
2164
  echo.push(`<pre>this._session['value'] = '${lTime.format(this, 'H:i:s')}';
@@ -3128,18 +3129,130 @@ rtn.push(reader.readBCDString());</pre>${JSON.stringify(rtn)}`);
3128
3129
  'model': 'qwen-plus',
3129
3130
  'messages': [
3130
3131
  { 'role': 'system', 'content': 'You are Kebab, a friendly and knowledgeable assistant based on an open-source Node framework. You do not mention any model names or AI identity. You can chat casually, answer questions, and provide guidance naturally. Respond in a human-like, approachable manner, as if you are a helpful companion rather than a traditional AI assistant.' },
3131
- { 'role': 'user', 'content': '你是谁?' }
3132
+ { 'role': 'user', 'content': '你是谁?' },
3132
3133
  ],
3133
3134
  });
3134
3135
  echo.push(`<pre>await ai.link.chat.completions.create({
3135
3136
  'model': 'qwen-plus',
3136
3137
  'messages': [
3137
3138
  { 'role': 'system', 'content': 'You are Kebab, a friendly and knowledgeable assistant based on an open-source Node framework. You do not mention any model names or AI identity. You can chat casually, answer questions, and provide guidance naturally. Respond in a human-like, approachable manner, as if you are a helpful companion rather than a traditional AI assistant.' },
3138
- { 'role': 'user', 'content': '你是谁?' }
3139
+ { 'role': 'user', 'content': '你是谁?' },
3139
3140
  ],
3140
3141
  });</pre>` + JSON.stringify(completion.choices[0].message.content));
3141
3142
  return echo.join('') + '<br><br>' + this._getEnd();
3142
3143
  }
3144
+ aiStream() {
3145
+ const echo = `<input id="text" type="text"><button id="send">Send</button>
3146
+ <hr>
3147
+ <div id="content"></div>
3148
+ <script>
3149
+ let controller;
3150
+ const text = document.getElementById('text');
3151
+ const send = document.getElementById('send');
3152
+ const content = document.getElementById('content');
3153
+ send.addEventListener('click', async () => {
3154
+ if (send.innerHTML === 'Stop') {
3155
+ controller.abort();
3156
+ return;
3157
+ }
3158
+ if (!text.value) {
3159
+ alert('Please input content');
3160
+ return;
3161
+ }
3162
+ send.innerHTML = 'Stop';
3163
+ send.disabled = true;
3164
+ controller = new AbortController();
3165
+ const res = await fetch('http${this._config.const.https ? 's' : ''}://${this._config.const.host}/test/ai-stream1', {
3166
+ 'method': 'POST',
3167
+ 'headers': { 'content-type': 'application/json' },
3168
+ 'body': JSON.stringify({ 'content': text.value, }),
3169
+ 'signal': controller.signal,
3170
+ });
3171
+ send.disabled = false;
3172
+ text.value = '';
3173
+ content.textContent = '';
3174
+ const reader = res.body.getReader();
3175
+ const decoder = new TextDecoder('utf8');
3176
+ let buf = '';
3177
+ while (true) {
3178
+ try {
3179
+ const { value, done } = await reader.read();
3180
+ if (done) {
3181
+ break;
3182
+ }
3183
+ buf += decoder.decode(value, { 'stream': true, });
3184
+ if (!buf.includes('\\n\\n')) {
3185
+ // --- 还没接收完 ---
3186
+ continue;
3187
+ }
3188
+ const events = buf.split('\\n\\n');
3189
+ buf = events.pop(); // --- 最后一个可能不完整 ---
3190
+ for (const ev of events) {
3191
+ content.textContent += JSON.parse(ev.slice(5).trim());
3192
+ }
3193
+ }
3194
+ catch {
3195
+ break;
3196
+ }
3197
+ }
3198
+ send.innerHTML = 'Send';
3199
+ });
3200
+ </script>`;
3201
+ return echo + '<br>' + this._getEnd();
3202
+ }
3203
+ async aiStream1() {
3204
+ if (!this._cross()) {
3205
+ return '';
3206
+ }
3207
+ if (!this._post['content']) {
3208
+ return '';
3209
+ }
3210
+ const ai = lAi.get(this, {
3211
+ 'service': lAi.ESERVICE.ALICN,
3212
+ });
3213
+ const stream = await ai.link.chat.completions.create({
3214
+ 'model': 'qwen-plus',
3215
+ 'stream': true,
3216
+ 'messages': [
3217
+ { 'role': 'system', 'content': 'You are Kebab, a friendly and knowledgeable assistant based on an open-source Node framework. You do not mention any model names or AI identity. You can chat casually, answer questions, and provide guidance naturally. Respond in a human-like, approachable manner, as if you are a helpful companion rather than a traditional AI assistant.' },
3218
+ { 'role': 'user', 'content': this._post['content'] },
3219
+ ],
3220
+ 'stream_options': {
3221
+ 'include_usage': true,
3222
+ },
3223
+ });
3224
+ lCore.writeEventStreamHead(this._res);
3225
+ for await (const chunk of stream) {
3226
+ if (!this._isAvail) {
3227
+ lCore.debug('Client disconnect');
3228
+ stream.controller.abort();
3229
+ break;
3230
+ }
3231
+ if (chunk.choices.length) {
3232
+ const content = chunk.choices[0].delta.content;
3233
+ if (!content) {
3234
+ continue;
3235
+ }
3236
+ if (!this._isAvail) {
3237
+ // --- 测试上面 abort 后不 break 的话还会执行到这里吗 ---
3238
+ // --- 测试结果:可能会,大概率只有一次,就证明连接确实断开了只不过有延迟 ---
3239
+ // --- 但上面 break 的话就肯定不会执行到这里了 ---
3240
+ lCore.debug('Client has been closed', content);
3241
+ continue;
3242
+ }
3243
+ lCore.write(this._res, 'data: ' + JSON.stringify(content) + '\n\n');
3244
+ continue;
3245
+ }
3246
+ if (!chunk.usage) {
3247
+ continue;
3248
+ }
3249
+ lCore.debug('--- All over ---');
3250
+ lCore.debug(`Input Tokens: ${chunk.usage.prompt_tokens}`);
3251
+ lCore.debug(`Output Tokens: ${chunk.usage.completion_tokens}`);
3252
+ lCore.debug(`Total Tokens: ${chunk.usage.total_tokens}`);
3253
+ }
3254
+ lCore.debug('AI DONE');
3255
+ }
3143
3256
  /**
3144
3257
  * --- END ---
3145
3258
  */