xshell 1.3.3 → 1.3.5

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/file.d.ts CHANGED
@@ -115,7 +115,7 @@ export interface FListOptions {
115
115
  stats?: boolean;
116
116
  best_effort?: boolean;
117
117
  }
118
- /**
118
+ /** 列出文件夹,默认返回文件名列表,也可以传入 stats 选项返回元数据
119
119
  - fpd: 文件夹完整路径 absolute path of directory
120
120
  - options?:
121
121
  - deep?: `false` 递归遍历 recursively
package/git.js CHANGED
@@ -98,7 +98,7 @@ export class Git {
98
98
  '--prune'
99
99
  ], print_stdout);
100
100
  if (print)
101
- console.log('更新远程分支成功');
101
+ console.log(`${this.cwd.fname.slice(0, -1)} 更新远程分支成功`);
102
102
  }
103
103
  async checkout(branch = 'main') {
104
104
  let create = false;
package/net.d.ts CHANGED
@@ -1,7 +1,8 @@
1
+ import net from 'node:net';
1
2
  import { type Readable } from 'node:stream';
2
3
  import type { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie';
3
4
  import type { Encoding } from './file.ts';
4
- import { inspect } from './utils.ts';
5
+ import { inspect, type Deferred2 } from './utils.ts';
5
6
  import { type BasicAuth, type BearerAuth } from './net.common.ts';
6
7
  export * from './net.common.ts';
7
8
  export declare enum MyProxy {
@@ -48,7 +49,6 @@ export interface RequestOptions {
48
49
  retry: boolean;
49
50
  timeout: boolean;
50
51
  };
51
- long_connection?: true;
52
52
  }
53
53
  export interface RequestFullOptions extends RequestOptions {
54
54
  full: true;
@@ -97,8 +97,7 @@ export declare class StatusCodeError extends Error {
97
97
  - decode?: `true` 根据 content-encoding: br 等解码压缩后的 response.body
98
98
  - print?:
99
99
  - retry: `true` 是否打印 "等待 1 秒后重试 request" 提示信息
100
- - timeout: `true` 是否打印最后一次超时重试失败时的错误
101
- - long_connection?: 用于保持长 tcp 连接,保留 nat 端口映射记录 */
100
+ - timeout: `true` 是否打印最后一次超时重试失败时的错误 */
102
101
  export declare function request(url: string | URL): Promise<string>;
103
102
  export declare function request(url: string | URL, options: RequestRawOptions): Promise<RawResponse>;
104
103
  export declare function request(url: string | URL, options: RequestFullOptions & {
@@ -111,3 +110,21 @@ export declare function request(url: string | URL, options: RequestOptions & {
111
110
  export declare function request(url: string | URL, options: RequestOptions): Promise<string>;
112
111
  /** 发起 http 请求并将响应体作为 json 解析 */
113
112
  export declare function request_json<T = any>(url: string | URL, options?: RequestOptions): Promise<T>;
113
+ export interface ConnectOptions {
114
+ timeout?: number;
115
+ print?: {
116
+ error: boolean;
117
+ connect: boolean;
118
+ };
119
+ }
120
+ /** 建立 tcp 连接,超时时间为 2 秒 (timeout 选项),返回已连接的 socket
121
+ 连接中遇到的错误会 reject,之后取消监听 error 事件,由调用者 attach 处理
122
+ - hostname: 要连接到的 ip 或域名
123
+ - port: 要连接的端口
124
+ - options?:
125
+ - timeout?: `2000` 超时时间
126
+ - print?: 打印连接日志,错误日志
127
+ - error?: `true`
128
+ - connect: `false` */
129
+ export declare function connect(hostname: string, port: number, { timeout: _timeout, print }?: ConnectOptions): Deferred2<net.Socket>;
130
+ export declare function get_socket_ip(socket: net.Socket): string;
package/net.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import zlib from 'node:zlib';
2
+ import net from 'node:net';
2
3
  import { buffer as stream_to_buffer, text as stream_to_text } from 'node:stream/consumers';
3
- import { isReadable } from 'node:stream';
4
- import { inspect, assert, delay, pipe_with_error, map_values, unique, timeout, check, colored, encode } from "./utils.js";
4
+ import { pipeline, isReadable } from 'node:stream';
5
+ import { noop } from "./prototype.js";
6
+ import { inspect, assert, delay, map_values, unique, timeout, check, colored, encode, TimeoutError, set_error_message, defer2 } from "./utils.js";
5
7
  import { drop_request_headers } from "./net.common.js";
6
8
  export * from "./net.common.js";
7
9
  export var MyProxy;
@@ -32,27 +34,25 @@ async function request_retry(url, options, _timeout, retries = 0, count = 0, pri
32
34
  // 设置给 undici 设置 timeout, signal 不一定管用,还是得自己兜底
33
35
  options.signal = AbortSignal.timeout(_timeout);
34
36
  return await timeout(_timeout + 300, // 为 undici 兜底
35
- undici.request(url, options), undefined, print.timeout && count >= retries // 只打印最后一次超时的错误,避免太多冗余输出
36
- );
37
+ undici.request(url, options), undefined, print.timeout && count >= retries); // 只打印最后一次超时的错误,避免太多冗余输出
37
38
  }
38
39
  else
39
40
  return await undici.request(url, options);
40
41
  }
41
42
  catch (error) {
42
- if (error.name === 'TimeoutError')
43
- if (count < retries) {
44
- const duration = 2 ** count;
45
- if (print.retry)
46
- console.log(`${`等待 ${duration} 秒后重试请求 (已尝试 ${count + 1} 次) ··`.yellow} ${url.toString().blue.underline}`);
47
- await delay(1000 * duration);
48
- return request_retry(url, options, _timeout, retries, count + 1, print);
49
- }
50
- else {
51
- const seconds = _timeout / 1000;
52
- throw Object.assign(new Error(`请求超过 ${seconds.toFixed(seconds < 1 ? 1 : 0)} 秒等待时间: ${url.toString()}`), { name: 'TimeoutError' });
53
- }
54
- else
43
+ if (error.name !== 'TimeoutError')
55
44
  throw error;
45
+ // 重试次数内
46
+ if (count < retries) {
47
+ const duration = 2 ** count;
48
+ if (print.retry)
49
+ console.log(`等待 ${duration} 秒后重试请求 (已尝试 ${count + 1} 次) ··`.yellow + ' ' +
50
+ url.toString().blue.underline);
51
+ await delay(1000 * duration);
52
+ return request_retry(url, options, _timeout, retries, count + 1, print);
53
+ }
54
+ const seconds = _timeout / 1000;
55
+ throw new TimeoutError(`请求超过 ${seconds.toFixed(seconds < 1 ? 1 : 0)} 秒等待时间: ${url.toString()}`);
56
56
  }
57
57
  }
58
58
  export class StatusCodeError extends Error {
@@ -71,7 +71,7 @@ export async function request(url, options = {}) {
71
71
  const { queries, headers: _headers, body, type = 'application/json', timeout = 5 * 1000, auth, cookies: _cookies, raw = false, full = false, redirect = 'follow', decode = true, print = {
72
72
  timeout: true,
73
73
  retry: true
74
- }, long_connection } = options;
74
+ } } = options;
75
75
  let { method, retries, encoding, proxy, } = options;
76
76
  url = new URL(url);
77
77
  if (queries)
@@ -128,7 +128,7 @@ export async function request(url, options = {}) {
128
128
  proxy = MyProxy.socks5;
129
129
  let undici_options = {
130
130
  ...method ? { method } : {},
131
- dispatcher: get_dispatcher(proxy, redirect, long_connection),
131
+ dispatcher: get_dispatcher(proxy, redirect),
132
132
  // 下面这些 timeout 都不是总的时间
133
133
  headersTimeout: timeout,
134
134
  // 从收完 headers 开始算
@@ -155,30 +155,26 @@ export async function request(url, options = {}) {
155
155
  switch (content_encoding) {
156
156
  case 'gzip':
157
157
  case 'x-gzip':
158
- body = pipe_with_error(_body, zlib.createGunzip({
158
+ body = pipeline(_body, zlib.createGunzip({
159
159
  // Be less strict when decoding compressed responses, since sometimes
160
160
  // servers send slightly invalid responses that are still accepted
161
161
  // by common browsers.
162
162
  // Always using Z_SYNC_FLUSH is what cURL does.
163
163
  flush: zlib.constants.Z_SYNC_FLUSH,
164
164
  finishFlush: zlib.constants.Z_SYNC_FLUSH
165
- }));
165
+ }), noop);
166
166
  break;
167
167
  case 'deflate':
168
- body = pipe_with_error(_body, zlib.createInflate());
168
+ body = pipeline(_body, zlib.createInflate(), noop);
169
169
  break;
170
170
  case 'br':
171
- body = pipe_with_error(_body, zlib.createBrotliDecompress());
171
+ body = pipeline(_body, zlib.createBrotliDecompress(), noop);
172
172
  break;
173
173
  default:
174
174
  throw new Error(`不支持 content-encoding: ${content_encoding.quote()} 的 http 请求`);
175
175
  }
176
176
  }
177
- response = {
178
- status,
179
- headers,
180
- body
181
- };
177
+ response = { status, headers, body };
182
178
  if (!((200 <= status && status <= 299) || status === 304 || (redirect === 'manual' && 300 <= status && status < 400)))
183
179
  throw new StatusCodeError(status, url.toString());
184
180
  }
@@ -234,8 +230,8 @@ export async function request(url, options = {}) {
234
230
  :
235
231
  body_;
236
232
  }
237
- function get_dispatcher(proxy, redirect, long_connection) {
238
- const key = `${proxy || (long_connection ? 'direct.long' : 'direct')}.${redirect}`;
233
+ function get_dispatcher(proxy, redirect) {
234
+ const key = `${proxy || 'direct'}.${redirect}`;
239
235
  let dispatcher = dispatchers[key];
240
236
  if (dispatcher)
241
237
  return dispatcher;
@@ -243,7 +239,7 @@ function get_dispatcher(proxy, redirect, long_connection) {
243
239
  // @ts-ignore
244
240
  new undici.ProxyAgent({ uri: proxy })
245
241
  :
246
- new undici.Agent({ keepAliveTimeout: long_connection ? 30_000 : undefined });
242
+ new undici.Agent({ allowH2: true });
247
243
  if (redirect === 'follow')
248
244
  dispatcher = dispatcher.compose(
249
245
  // todo: 强制手动处理重定向,来正确处理 cookie ?
@@ -297,4 +293,63 @@ export async function request_json(url, options) {
297
293
  throw error;
298
294
  }
299
295
  }
296
+ /** 建立 tcp 连接,超时时间为 2 秒 (timeout 选项),返回已连接的 socket
297
+ 连接中遇到的错误会 reject,之后取消监听 error 事件,由调用者 attach 处理
298
+ - hostname: 要连接到的 ip 或域名
299
+ - port: 要连接的端口
300
+ - options?:
301
+ - timeout?: `2000` 超时时间
302
+ - print?: 打印连接日志,错误日志
303
+ - error?: `true`
304
+ - connect: `false` */
305
+ export function connect(hostname, port, { timeout: _timeout = 2000, print = { error: true, connect: false } } = {}) {
306
+ let pconnected = defer2();
307
+ let timeouted = false;
308
+ function get_message(message) {
309
+ return `tcp://${hostname}:${port} ${message}`;
310
+ }
311
+ let socket = net.connect({
312
+ host: hostname,
313
+ port,
314
+ noDelay: true
315
+ }, () => {
316
+ if (pconnected.settled) {
317
+ if (print)
318
+ console.warn(get_message('连接建立了,但是已超时'.yellow));
319
+ socket.resetAndDestroy();
320
+ }
321
+ else {
322
+ clearTimeout(timeout);
323
+ socket.off('error', on_error);
324
+ if (print.connect)
325
+ console.log(get_message('已连接'));
326
+ pconnected.resolve(socket);
327
+ }
328
+ });
329
+ const on_error = (error) => {
330
+ if (pconnected.settled) {
331
+ // 超时后可能会走到这里
332
+ if (timeouted && error.code === 'ERR_SOCKET_CLOSED')
333
+ return;
334
+ // 待观察:不太可能走到这里
335
+ if (print.error)
336
+ console.error(get_message(error.message.red));
337
+ }
338
+ else {
339
+ clearTimeout(timeout);
340
+ pconnected.reject(set_error_message(error, get_message(error.message)));
341
+ }
342
+ };
343
+ socket.once('error', on_error);
344
+ let timeout = setTimeout(() => {
345
+ // 执行到这里一定没有 settled
346
+ timeouted = true;
347
+ socket.resetAndDestroy();
348
+ pconnected.reject(new TimeoutError(get_message('超时了')));
349
+ }, _timeout);
350
+ return pconnected;
351
+ }
352
+ export function get_socket_ip(socket) {
353
+ return socket.remoteAddress.strip_if_start('::ffff:');
354
+ }
300
355
  //# sourceMappingURL=net.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -56,19 +56,19 @@
56
56
  "@stylistic/eslint-plugin": "^5.5.0",
57
57
  "@svgr/webpack": "^8.1.0",
58
58
  "@types/sass-loader": "^8.0.10",
59
- "@typescript-eslint/eslint-plugin": "^8.46.2",
60
- "@typescript-eslint/parser": "^8.46.2",
61
- "@typescript-eslint/utils": "^8.46.2",
59
+ "@typescript-eslint/eslint-plugin": "^8.46.3",
60
+ "@typescript-eslint/parser": "^8.46.3",
61
+ "@typescript-eslint/utils": "^8.46.3",
62
62
  "archiver": "^7.0.1",
63
63
  "chalk": "^5.6.2",
64
64
  "commander": "^14.0.2",
65
65
  "css-loader": "^7.1.2",
66
66
  "emoji-regex": "^10.6.0",
67
- "eslint": "^9.39.0",
67
+ "eslint": "^9.39.1",
68
68
  "eslint-plugin-import": "^2.32.0",
69
69
  "eslint-plugin-react": "^7.37.5",
70
70
  "https-proxy-agent": "^7.0.6",
71
- "i18next": "^25.6.0",
71
+ "i18next": "^25.6.1",
72
72
  "i18next-scanner": "^4.6.0",
73
73
  "koa": "^3.1.1",
74
74
  "koa-compress": "^5.1.1",
@@ -76,7 +76,7 @@
76
76
  "mime-types": "^3.0.1",
77
77
  "p-map": "^7.0.3",
78
78
  "react": "^19.2.0",
79
- "react-i18next": "^16.2.3",
79
+ "react-i18next": "^16.2.4",
80
80
  "resolve-path": "^1.4.0",
81
81
  "sass": "^1.93.3",
82
82
  "sass-loader": "^16.0.6",
@@ -101,7 +101,7 @@
101
101
  "@types/koa": "^3.0.1",
102
102
  "@types/koa-compress": "^4.0.7",
103
103
  "@types/mime-types": "^3.0.1",
104
- "@types/node": "^24.9.2",
104
+ "@types/node": "^24.10.0",
105
105
  "@types/react": "^19.2.2",
106
106
  "@types/tough-cookie": "^4.0.5",
107
107
  "@types/vscode": "^1.105.0",
package/process.d.ts CHANGED
@@ -31,10 +31,10 @@ interface BaseOptions {
31
31
  envs?: Record<string, string>;
32
32
  /** 创建子进程时添加 http_proxy, https_proxy, no_proxy 环境变量以启用代理 */
33
33
  proxy?: MyProxy | true;
34
- /** 控制子进程 stdin,默认为 `false` (读空设备),除非传入了 {@link CallOptions.input} 属性
34
+ /** 控制子进程 stdin,默认为 `Boolean(input)` (false 时读空设备),除非传入了 {@link CallOptions.input} 属性
35
35
  - 默认值:
36
36
  - start(): false ('ignore', 空设备)
37
- - (call|launch)(): true (pipe, 作为 child.stdout 可读流)
37
+ - (call|launch)(): false (pipe, 作为 child.stdin)
38
38
  - `true` 设置子进程 stdin 为 'pipe'
39
39
  - `false` 设置子进程 stdin 为 'ignore' (空设备)
40
40
  - `string` 设置子进程 stdin 为某个文件路径 (打开文件,将句柄设置为子进程 stdin) */
@@ -42,8 +42,8 @@ interface BaseOptions {
42
42
  /** 控制子进程 stdout
43
43
  - 默认值:
44
44
  - start(): false ('ignore', 空设备)
45
- - (call|launch)(): true (pipe, 作为 child.stdout 可读流)
46
- - `true` 设置子进程 stdout 为 'pipe' (作为 child.stdout 可读流)
45
+ - (call|launch)(): true (pipe, 作为 child.stdout)
46
+ - `true` 设置子进程 stdout 为 'pipe' (作为 child.stdout)
47
47
  - `false` 设置子进程 stdout 为 'ignore' (空设备)
48
48
  - `string` 设置子进程 stdout 某个文件路径 (打开文件,将句柄设置为子进程 stdout) */
49
49
  stdout?: boolean | string;
package/server.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createServer as http_create_server, } from 'node:http';
2
- import { createSecureServer as http2_create_server } from 'node:http2';
1
+ import { default as http, } from 'node:http';
2
+ import { default as http2 } from 'node:http2';
3
3
  import { createSecureContext } from 'node:tls';
4
4
  import zlib from 'node:zlib';
5
5
  import fs from 'node:fs';
@@ -12,8 +12,8 @@ import resolve_safely from 'resolve-path';
12
12
  import { contentType as get_content_type } from 'mime-types';
13
13
  // --- my libs
14
14
  import { t } from "./i18n/instance.js";
15
- import { request as _request, Remote } from "./net.js";
16
- import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored, url_width } from "./utils.js";
15
+ import { request as _request, Remote, get_socket_ip } from "./net.js";
16
+ import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored, url_width, defer2 } from "./utils.js";
17
17
  import { flist, fread, fstat } from "./file.js";
18
18
  import { exe_nodejs, sea } from "./process.js";
19
19
  // ------------ my server
@@ -138,9 +138,8 @@ export class Server {
138
138
  app.use(this._router.bind(this));
139
139
  this.app = app;
140
140
  this.handler = this.app.callback();
141
- this.http_server = http_create_server({ optimizeEmptyRequests: true }, this.handler);
142
- const { http, http2 } = this;
143
- if (http2) {
141
+ this.http_server = http.createServer({ optimizeEmptyRequests: true, noDelay: true }, this.handler);
142
+ if (this.http2) {
144
143
  const { fpd_certs } = this;
145
144
  let lazy_secure_ctxs = Object.fromEntries(await Promise.all(
146
145
  // fpd_certs 文件夹下面的每个 .key 对应一个证书及域名
@@ -157,7 +156,7 @@ export class Server {
157
156
  })));
158
157
  const default_ctx = lazy_secure_ctxs[this.default_hostnames.find(hostname => hostname in lazy_secure_ctxs)];
159
158
  check(default_ctx);
160
- this.http2_server = http2_create_server({
159
+ this.http2_server = http2.createSecureServer({
161
160
  SNICallback(servername, callback) {
162
161
  let lazy_ctx = lazy_secure_ctxs[servername] ||
163
162
  lazy_secure_ctxs[servername.replace(/^.*?\./, '*.')] ||
@@ -166,6 +165,7 @@ export class Server {
166
165
  callback(null, lazy_ctx.ctx ??= createSecureContext(lazy_ctx));
167
166
  },
168
167
  allowHTTP1: true,
168
+ noDelay: true
169
169
  }, this.handler);
170
170
  }
171
171
  // websocket rpc
@@ -229,20 +229,16 @@ export class Server {
229
229
  }
230
230
  await this.init_server();
231
231
  await Promise.all([
232
- http && new Promise(resolve => {
233
- this.http_server.listen(this.http_port, resolve);
234
- }),
235
- http2 && new Promise(resolve => {
236
- this.http2_server.listen(this.http2_port, resolve);
237
- }),
232
+ this.http && listen_http_server(this.http_server, this.http_port),
233
+ this.http2 && listen_http_server(this.http2_server, this.http2_port)
238
234
  ]);
239
235
  if (this.print.info)
240
236
  console.log(t('{{name}} 启动成功,正在监听 {{ports}} 端口', {
241
237
  name: this.name,
242
238
  ports: [
243
- ...http ? [this.http_port] : [],
244
- ...http2 ? [this.http2_port] : []
245
- ].join(', ')
239
+ this.http && this.http_port,
240
+ this.http2 && this.http2_port
241
+ ].filter(Boolean).join(', ')
246
242
  }));
247
243
  }
248
244
  try_write_stdio_subscribers(chunk) {
@@ -258,25 +254,25 @@ export class Server {
258
254
  }
259
255
  stop() {
260
256
  this.http_server.close();
261
- if (this.http2_server)
262
- this.http2_server.close();
257
+ this.http2_server?.close();
263
258
  }
264
259
  /** 可被子类重写定义错误处理逻辑 */
265
260
  on_error(error, ctx) {
266
- if (this.print.errors) {
267
- const code = error?.code;
268
- if (code === 'EPIPE' || code === 'ECONNRESET')
269
- console.log(`${error.code}:`, ctx?.request?.url);
270
- else {
271
- console.error(error);
272
- if (ctx)
273
- console.log('ctx:', ctx);
274
- }
261
+ if (!this.print.errors)
262
+ return;
263
+ const code = error?.code;
264
+ if (code === 'EPIPE' || code === 'ECONNRESET')
265
+ console.log(`${error.code}:`, ctx?.request?.url);
266
+ else {
267
+ console.error(error);
268
+ if (ctx)
269
+ console.log('ctx:', ctx);
275
270
  }
276
271
  }
277
272
  on_upgrade(request, socket, head) {
278
273
  // url 只有路径部分
279
274
  const { url, headers, headers: { host = '' }, } = request;
275
+ const ip = get_socket_ip(request.socket);
280
276
  if (this.print.logs) {
281
277
  let s =
282
278
  // 时间
@@ -289,7 +285,7 @@ export class Server {
289
285
  url;
290
286
  s = s.pad(url_width);
291
287
  // ip
292
- s += ` <- ${request.socket.remoteAddress.strip_if_start('::ffff:')}`;
288
+ s += ` <- ${ip}`;
293
289
  // 客户端信息
294
290
  const client_info = this.get_client_info(headers);
295
291
  if (client_info)
@@ -327,7 +323,7 @@ export class Server {
327
323
  console.error(error);
328
324
  response.status = error.status || 500;
329
325
  response.set('content-type', text_plain);
330
- response.body = inspect(error, { colors: false });
326
+ response.body = inspect_error(error);
331
327
  }
332
328
  }
333
329
  /** 解析 req.body to request.body
@@ -638,7 +634,8 @@ export class Server {
638
634
  if (this.print.errors)
639
635
  console.log(error);
640
636
  response.status = 500;
641
- response.body = inspect(error, { colors: false });
637
+ response.set('content-type', text_plain);
638
+ response.body = inspect_error(error);
642
639
  }
643
640
  }
644
641
  return true;
@@ -800,7 +797,7 @@ export class Server {
800
797
  - reverse?: `false` 在 range 内从后往前尝试 */
801
798
  static async get_available_port(range, reverse = false) {
802
799
  for (const port of range_to_numbers(range, reverse)) {
803
- let server = http_create_server();
800
+ let server = http.createServer();
804
801
  try {
805
802
  await new Promise((resolve, reject) => {
806
803
  server.once('error', reject);
@@ -825,4 +822,17 @@ export class Server {
825
822
  }
826
823
  export const text_plain = 'text/plain; charset=utf-8';
827
824
  const devtools_trash = '/.well-known/appspecific/com.chrome.devtools.json';
825
+ function listen_http_server(server, port) {
826
+ let plisten = defer2();
827
+ server.once('error', plisten.reject);
828
+ server.listen(port, () => {
829
+ server.off('error', plisten.reject);
830
+ plisten.resolve();
831
+ });
832
+ return plisten;
833
+ }
834
+ const no_color = { colors: false };
835
+ function inspect_error(error) {
836
+ return inspect(error, no_color);
837
+ }
828
838
  //# sourceMappingURL=server.js.map
package/utils.common.d.ts CHANGED
@@ -50,7 +50,7 @@ export declare function filter_values<TObj extends Record<string, any>>(obj: TOb
50
50
  /** 简单选择对象中的部分 keys, 返回新对象 */
51
51
  export declare function pick<TObject = any>(obj: TObject, keys: (keyof TObject)[]): Partial<TObject>;
52
52
  /** 忽略对象中的 keys, 返回新对象 */
53
- export declare function omit<TObj>(obj: TObj, omit_keys: string[]): TObj;
53
+ export declare function omit<TObj>(obj: TObj, omit_keys: string[] | Set<string>): TObj;
54
54
  /** 拼接 TypedArrays 生成一个完整的 Uint8Array */
55
55
  export declare function concat(arrays: ArrayBufferView[]): Uint8Array<ArrayBufferLike>;
56
56
  export declare let encoder: TextEncoder;
@@ -66,11 +66,15 @@ export declare function buffer_equals(left: Uint8Array, right: Uint8Array | stri
66
66
  /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
67
67
  - milliseconds: 限时毫秒数
68
68
  - action?: 要等待运行的任务, async function 或 promise
69
- - on_timeout?: 超时后调用的函数
70
- - 若传: 调用 on_timeout,参数为 TimeoutError,然后 timeout 函数正常返回 null
71
- - 若不传: 抛出 TimeoutError
69
+ - on_timeout?: 超时后调用的函数,或者设置错误消息
70
+ - 若传:
71
+ - string: 作为 TimeoutError 的 error message
72
+ - 传 function: 超时时调用会调用 on_timeout,参数为 TimeoutError,然后等待 on_timeout 执行完成
73
+ - on_timeout 函数正常运行: timeout 函数返回 null
74
+ - on_timeout 报错: timeout 函数最终抛出这个错误
75
+ - 若不传: 直接抛出 TimeoutError
72
76
  - print?: `true` 打印已超时任务的错误 */
73
- export declare function timeout<TReturn>(milliseconds: number, action: Promise<TReturn> | (() => Promise<TReturn>), on_timeout?: (error: TimeoutError) => void | Promise<void>, print?: boolean): Promise<TReturn>;
77
+ export declare function timeout<TReturn>(milliseconds: number, action: Promise<TReturn> | (() => Promise<TReturn>), on_timeout?: string | ((error: TimeoutError) => void | Promise<void>), print?: boolean): Deferred2<TReturn>;
74
78
  /** 轮询尝试 action 共 times 次,每次间隔 duration
75
79
  action 返回 trusy 值时认为成功,返回 action 的结果
76
80
  如果次数用尽仍然失败,返回 null */
@@ -118,7 +122,7 @@ export type StrictArrayBuffer = ArrayBuffer & {
118
122
  };
119
123
  export interface Deferred<TValue> extends Promise<TValue> {
120
124
  resolve(value: TValue | PromiseLike<TValue>): void;
121
- reject(reason?: Error): void;
125
+ reject(error?: Error): void;
122
126
  }
123
127
  /** 创建一个 promise,后续可调用 promise.resolve, promise.reject 方法设置其状态和值
124
128
  - initial?: `undefined` 传入非 undefined 值(包括 null)时直接设置为 resolved 状态
@@ -126,7 +130,7 @@ export interface Deferred<TValue> extends Promise<TValue> {
126
130
  export declare function defer<TValue>(initial?: TValue): Deferred<TValue>;
127
131
  export interface Deferred2<TValue> extends Promise<TValue> {
128
132
  resolve(value: TValue | PromiseLike<TValue>): void;
129
- reject(reason?: Error): void;
133
+ reject(error?: Error): void;
130
134
  get settled(): boolean;
131
135
  }
132
136
  /** 有 settled 状态的 defer */
@@ -167,7 +171,7 @@ export declare function lowercase_first_letter(str: string): string;
167
171
  export declare function ceil2(n: number): number;
168
172
  /** 节流,最多只在时间间隔末尾调用一次,可以设置首次调用是否延后 */
169
173
  export declare function throttle(duration: number, func: Function, delay_first?: boolean): (this: any, ...args: any[]) => void;
170
- /** 防抖,间隔一段时间不再触发时调用 */
174
+ /** 防抖,间隔一段时间不再触发时调用,同时大幅优化减少 setTimeout, clearTimeout 的调用次数 */
171
175
  export declare function debounce(duration: number, func: Function): (this: any, ...args: any[]) => void;
172
176
  export declare function tomorrow(date?: Date | string | number): Date;
173
177
  export declare function to_csv_field(str: string): string;
@@ -176,10 +180,3 @@ export declare function set_error_message(error: Error, message: string): Error;
176
180
  /** 比较两个数组中的元素完全相同,数组元素用引用比较 */
177
181
  export declare function array_equals(a: any[], b: any[]): boolean;
178
182
  export declare function nowstr(with_date?: boolean): string;
179
- interface MyTimeout {
180
- clear(): void;
181
- ref: NodeJS.Timeout | number;
182
- }
183
- /** 面向对象的 timeout, callback 在后的 timeout */
184
- export declare function set_timeout(delay: number, callback: VoidFunction): MyTimeout;
185
- export {};
package/utils.common.js CHANGED
@@ -136,8 +136,11 @@ export function pick(obj, keys) {
136
136
  }
137
137
  /** 忽略对象中的 keys, 返回新对象 */
138
138
  export function omit(obj, omit_keys) {
139
- const omit_keys_ = new Set(omit_keys);
140
- return filter_keys(obj, key => !omit_keys_.has(key));
139
+ const set = omit_keys instanceof Set;
140
+ return filter_keys(obj, set ?
141
+ key => !(omit_keys.has(key))
142
+ :
143
+ key => !(omit_keys.includes(key)));
141
144
  }
142
145
  /** 拼接 TypedArrays 生成一个完整的 Uint8Array */
143
146
  export function concat(arrays) {
@@ -183,55 +186,52 @@ export function buffer_equals(left, right) {
183
186
  /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
184
187
  - milliseconds: 限时毫秒数
185
188
  - action?: 要等待运行的任务, async function 或 promise
186
- - on_timeout?: 超时后调用的函数
187
- - 若传: 调用 on_timeout,参数为 TimeoutError,然后 timeout 函数正常返回 null
188
- - 若不传: 抛出 TimeoutError
189
+ - on_timeout?: 超时后调用的函数,或者设置错误消息
190
+ - 若传:
191
+ - string: 作为 TimeoutError 的 error message
192
+ - 传 function: 超时时调用会调用 on_timeout,参数为 TimeoutError,然后等待 on_timeout 执行完成
193
+ - on_timeout 函数正常运行: timeout 函数返回 null
194
+ - on_timeout 报错: timeout 函数最终抛出这个错误
195
+ - 若不传: 直接抛出 TimeoutError
189
196
  - print?: `true` 打印已超时任务的错误 */
190
- export async function timeout(milliseconds, action, on_timeout, print = true) {
191
- const error = new TimeoutError();
192
- return new Promise((resolve, reject) => {
193
- let done = false;
194
- let rejected = false;
195
- (async () => {
196
- await delay(milliseconds);
197
- if (!done)
198
- if (on_timeout)
199
- try {
200
- await on_timeout(error);
201
- resolve(null);
202
- }
203
- catch (error) {
204
- if (rejected)
205
- throw error; // 会成为 unhandled rejection
206
- else {
207
- rejected = true;
208
- reject(error);
209
- }
210
- }
211
- else {
212
- rejected = true;
213
- reject(error);
214
- }
215
- })();
216
- (async () => {
197
+ export function timeout(milliseconds, action, on_timeout, print = true) {
198
+ const error = new TimeoutError(typeof on_timeout === 'string' ? on_timeout : undefined);
199
+ let presult = defer2();
200
+ let waiting_on_timeout = false;
201
+ let timeout = setTimeout(async () => {
202
+ if (typeof on_timeout === 'function')
217
203
  try {
218
- resolve(await (typeof action === 'function' ? action() : action));
204
+ waiting_on_timeout = true;
205
+ await on_timeout(error);
206
+ presult.resolve(null);
219
207
  }
220
208
  catch (error) {
221
- if (rejected) {
222
- if (print)
223
- console.log(`已超时任务的错误: ${error.message}`);
224
- }
225
- else {
226
- rejected = true;
227
- reject(error);
228
- }
229
- }
230
- finally {
231
- done = true;
209
+ presult.reject(error);
232
210
  }
233
- })();
211
+ else
212
+ presult.reject(error);
213
+ }, milliseconds);
214
+ const paction = typeof action === 'function' ? action() : action;
215
+ paction.then(result => {
216
+ if (presult.settled) {
217
+ if (print)
218
+ console.log('已超时任务最终完成了');
219
+ }
220
+ else if (!waiting_on_timeout) {
221
+ presult.resolve(result);
222
+ clearTimeout(timeout);
223
+ }
224
+ }, error => {
225
+ if (presult.settled) {
226
+ if (print)
227
+ console.error(`已超时任务的错误: ${error.message}`);
228
+ }
229
+ else if (!waiting_on_timeout) {
230
+ presult.reject(error);
231
+ clearTimeout(timeout);
232
+ }
234
233
  });
234
+ return presult;
235
235
  }
236
236
  /** 轮询尝试 action 共 times 次,每次间隔 duration
237
237
  action 返回 trusy 值时认为成功,返回 action 的结果
@@ -382,19 +382,18 @@ export class TimeoutError extends Error {
382
382
  // eslint-disable-next-line @typescript-eslint/promise-function-async
383
383
  export function defer(initial) {
384
384
  if (initial === undefined) {
385
- let resolve;
386
- let reject;
387
- let promise = new Promise((_resolve, _reject) => {
388
- resolve = _resolve;
389
- reject = _reject;
390
- });
391
- return Object.assign(promise, { resolve, reject });
385
+ let { promise, resolve, reject } = Promise.withResolvers();
386
+ let p = promise;
387
+ p.resolve = resolve;
388
+ p.reject = reject;
389
+ return p;
390
+ }
391
+ else {
392
+ let p = Promise.resolve(initial);
393
+ p.resolve = noop;
394
+ p.reject = noop;
395
+ return p;
392
396
  }
393
- else
394
- return Object.assign(Promise.resolve(initial), {
395
- resolve: noop,
396
- reject: noop
397
- });
398
397
  }
399
398
  /** 有 settled 状态的 defer */
400
399
  // eslint-disable-next-line @typescript-eslint/promise-function-async
@@ -402,26 +401,29 @@ export function defer2(initial) {
402
401
  if (initial === undefined) {
403
402
  let settled = false;
404
403
  let { promise, resolve, reject } = Promise.withResolvers();
405
- return Object.assign(promise, {
406
- resolve(value) {
407
- settled = true;
408
- resolve(value);
409
- },
410
- reject(error) {
411
- settled = true;
412
- reject(error);
413
- },
414
- get settled() {
415
- return settled;
416
- }
404
+ let p = promise;
405
+ p.resolve = (value) => {
406
+ settled = true;
407
+ resolve(value);
408
+ };
409
+ p.reject = (error) => {
410
+ settled = true;
411
+ reject(error);
412
+ };
413
+ Object.defineProperty(p, 'settled', {
414
+ get() { return settled; },
415
+ enumerable: true,
416
+ configurable: true
417
417
  });
418
+ return p;
419
+ }
420
+ else {
421
+ let p = Promise.resolve(initial);
422
+ p.resolve = noop;
423
+ p.reject = noop;
424
+ p.settled = true;
425
+ return p;
418
426
  }
419
- else
420
- return Object.assign(Promise.resolve(initial), {
421
- resolve: noop,
422
- reject: noop,
423
- settled: true
424
- });
425
427
  }
426
428
  /** @example
427
429
  let lock = new Lock(redis)
@@ -508,8 +510,8 @@ export function ceil2(n) {
508
510
  export function throttle(duration, func, delay_first = false) {
509
511
  let timeout = 0;
510
512
  let last = 0;
511
- let saved_this = null;
512
- let saved_args = null;
513
+ let saved_this;
514
+ let saved_args;
513
515
  return function throttled(...args) {
514
516
  // 当前时间间隔已预定执行,本次调用仅更新调用参数
515
517
  if (timeout) {
@@ -539,14 +541,33 @@ export function throttle(duration, func, delay_first = false) {
539
541
  }
540
542
  };
541
543
  }
542
- /** 防抖,间隔一段时间不再触发时调用 */
544
+ /** 防抖,间隔一段时间不再触发时调用,同时大幅优化减少 setTimeout, clearTimeout 的调用次数 */
543
545
  export function debounce(duration, func) {
544
- let timeout = null;
546
+ const half = Math.floor(duration / 2);
547
+ let timeout;
548
+ let last = 0;
549
+ let saved_args;
550
+ let saved_this;
551
+ function debounce_callback() {
552
+ timeout = null;
553
+ func.apply(saved_this, saved_args);
554
+ saved_this = null;
555
+ saved_args = null;
556
+ }
545
557
  return function debounced(...args) {
558
+ if (!timeout) {
559
+ timeout = setTimeout(debounce_callback, duration);
560
+ last = Date.now();
561
+ return;
562
+ }
563
+ const now = Date.now();
564
+ saved_args = args;
565
+ saved_this = this;
566
+ if (now - last < half)
567
+ return;
546
568
  clearTimeout(timeout);
547
- timeout = setTimeout(() => {
548
- func.apply(this, args);
549
- }, duration);
569
+ setTimeout(debounce_callback, duration);
570
+ last = now;
550
571
  };
551
572
  }
552
573
  export function tomorrow(date) {
@@ -576,17 +597,4 @@ export function nowstr(with_date = false) {
576
597
  const date = new Date();
577
598
  return with_date ? date.to_str() : date.to_time_str();
578
599
  }
579
- /** 面向对象的 timeout, callback 在后的 timeout */
580
- export function set_timeout(delay, callback) {
581
- let ret = {
582
- ref: setTimeout(callback, delay),
583
- clear() {
584
- if (!ret.ref)
585
- return;
586
- clearTimeout(ret.ref);
587
- ret.ref = null;
588
- }
589
- };
590
- return ret;
591
- }
592
600
  //# sourceMappingURL=utils.common.js.map
package/utils.d.ts CHANGED
@@ -45,7 +45,6 @@ export declare function map_stream<Out, In = any>(mapper: (obj: In, cb: Function
45
45
  failures?: boolean;
46
46
  }): Duplex;
47
47
  export declare function stream_to_lines(stream: Readable): AsyncGenerator<string, void, unknown>;
48
- export declare function pipe_with_error<TWritable extends Writable>(readable: Readable, writable: TWritable): TWritable;
49
48
  export declare class WritableMemoryStream extends Writable {
50
49
  chunks: Uint8Array[];
51
50
  pbuffer: import("./utils.common.ts").Deferred<Buffer<ArrayBufferLike>>;
package/utils.js CHANGED
@@ -225,11 +225,6 @@ export async function* stream_to_lines(stream) {
225
225
  buf = chunk.slice(j);
226
226
  }
227
227
  }
228
- export function pipe_with_error(readable, writable) {
229
- // 不知道 transform 作为 AsyncIterable 使用时, emit error 是否会在 for await (...) 循环中触发错误
230
- readable.once('error', error => { writable.emit('error', error); });
231
- return readable.pipe(writable);
232
- }
233
228
  export class WritableMemoryStream extends Writable {
234
229
  chunks = [];
235
230
  pbuffer = defer();
package/xlint.js CHANGED
@@ -787,9 +787,6 @@ export const xlint_config = {
787
787
  '@typescript-eslint/dot-notation': 'error',
788
788
  // 必须 throw Error
789
789
  '@typescript-eslint/only-throw-error': 'error',
790
- // ------------ async
791
- // 返回 Promise 的函数一定要标记为 async 函数
792
- '@typescript-eslint/promise-function-async': 'error',
793
790
  // 不要 return await promise, 直接 return promise, 除非外面有 try catch
794
791
  '@typescript-eslint/return-await': 'error',
795
792
  // ------------ 括号