@abtnode/util 1.17.12 → 1.17.13-beta-20260512-004004-69bacba8

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.
@@ -4,22 +4,22 @@ const { BLOCKLET_CACHE_TTL } = require('@abtnode/constant');
4
4
  const BLOCKLET_INFO_SHORT_CACHE_TTL = 1000 * 60 * 30; // 30 minutes
5
5
 
6
6
  /**
7
- * Blocklet Info 缓存, 用于 blocklet.js 请求,快速返回数据
7
+ * Blocklet Info cache for blocklet.js requests, allowing fast responses
8
8
  *
9
- * 注意:
10
- * - Redis Adapter 不使用 LRU cacheRedis 本身已经是内存数据库)
11
- * - SQLite Adapter 会使用 LRU cache 作为 L1 缓存层
9
+ * Notes:
10
+ * - Redis Adapter does not use LRU cache (Redis itself is in-memory)
11
+ * - SQLite Adapter uses LRU cache as the L1 cache layer
12
12
  */
13
13
  const blockletInfoShortCache = new DBCache(() => ({
14
14
  prefix: 'blocklet-info-short-cache',
15
15
  ttl: BLOCKLET_INFO_SHORT_CACHE_TTL, // 30 minutes
16
- enableLruCache: true, // 仅对 SQLite 生效
16
+ enableLruCache: true, // only effective for SQLite
17
17
  ...getAbtNodeRedisAndSQLiteUrl(),
18
18
  }));
19
19
 
20
20
  /**
21
21
  * @param {string} did: blockletDid
22
- * @param {string} componentId: componentDid 的格式为 did/children.did
22
+ * @param {string} componentId: componentDid format is did/children.did
23
23
  * @param {string} type: json or js
24
24
  * @returns {string}
25
25
  */
@@ -28,7 +28,7 @@ const getBlockletInfoCachePrefixKey = (did) => {
28
28
  };
29
29
 
30
30
  /**
31
- * 删除 Blocklet Info 缓存 (包括内存缓存和 DBCache)
31
+ * Delete Blocklet Info cache (including memory cache and DBCache)
32
32
  *
33
33
  * CLUSTER MODE NOTE:
34
34
  * - Memory cache: Cleared via delByPrefix with cluster sync.
@@ -38,10 +38,10 @@ const getBlockletInfoCachePrefixKey = (did) => {
38
38
  */
39
39
  const clearBlockletInfoCache = async (did, logger = console) => {
40
40
  try {
41
- // 由于 cacheKey 包含动态参数(pathPrefix 等),无法枚举所有可能的 key
42
- // 所以使用前缀匹配删除所有与该 DID 相关的缓存
41
+ // cacheKey contains dynamic parameters (pathPrefix, etc.), so all possible keys cannot be enumerated
42
+ // use prefix matching to delete all cache entries related to this DID
43
43
  const prefix = getBlockletInfoCachePrefixKey(did);
44
- // DBCache 也使用前缀删除(del 方法会删除该 key 及其 group 下的所有 key)
44
+ // DBCache also uses prefix deletion (del removes this key and all keys in its group)
45
45
  await blockletInfoShortCache.del(prefix);
46
46
  logger.info('Cleared blocklet info cache', { did, prefix });
47
47
  } catch (error) {
@@ -50,17 +50,17 @@ const clearBlockletInfoCache = async (did, logger = console) => {
50
50
  };
51
51
 
52
52
  /**
53
- * Blocklet state 缓存, 用于内部数据获取,避免重复查询数据库
53
+ * Blocklet state cache for internal data retrieval to avoid repeated database queries
54
54
  */
55
55
  const blockletCache = new DBCache(() => ({
56
56
  prefix: 'blocklet-state',
57
57
  ttl: BLOCKLET_CACHE_TTL,
58
- enableLruCache: false, // 明确禁用 LRU cache
58
+ enableLruCache: false, // explicitly disable LRU cache
59
59
  ...getAbtNodeRedisAndSQLiteUrl(),
60
60
  }));
61
61
 
62
62
  /**
63
- * 删除 Blocklet state 缓存
63
+ * Delete Blocklet state cache
64
64
  * @param {string} did: blockletDid
65
65
  */
66
66
  const deleteBlockletCache = async (did, logger = console) => {
package/lib/deep-clone.js CHANGED
@@ -3,8 +3,7 @@ function deepClone(o) {
3
3
  return undefined;
4
4
  }
5
5
 
6
- // 优先使用 structuredClone(性能更好,支持更多类型)
7
- // structuredClone(Node.js 17+ / 现代浏览器)
6
+ // prefer structuredClone (better perf, more type support; Node.js 17+ / modern browsers)
8
7
  try {
9
8
  return structuredClone(o);
10
9
  } catch {
@@ -216,7 +216,7 @@ const getBlockletServices = ({
216
216
  rr: encodeBase32(slpDid),
217
217
  value: daemonDidDomain,
218
218
  domain: slpDomain,
219
- derivedFrom: serverDid, // 用于验证 slpDid 的所有权是否合法
219
+ derivedFrom: serverDid, // used to verify that the ownership of slpDid is legitimate
220
220
  });
221
221
  }
222
222
 
@@ -119,7 +119,7 @@ const downloadFile = async (
119
119
  let current = 0;
120
120
  response.data.on('data', (chunk) => {
121
121
  current += chunk.length;
122
- // 2 秒检查一次, db-cache 里是否标记了取消, 如果有, 则取消下载
122
+ // check every 2 seconds whether cancellation has been flagged in db-cache; if so, cancel the download
123
123
  if (Date.now() - t > 2000) {
124
124
  t = Date.now();
125
125
  checkCanceled().then((cancelled) => {
@@ -4,14 +4,14 @@ async function ensureDockerEndpointHealthy({ host, port, timeout = 10 * 1000 })
4
4
  const checkWget = await promiseSpawn(`docker exec ${host} sh -c "command -v wget || echo 'no-wget'"`, timeout);
5
5
 
6
6
  if (checkWget.trim() === 'no-wget') {
7
- // 检测容器类型并安装 wget
7
+ // detect the container OS type and install wget
8
8
  await promiseSpawn(
9
9
  `docker exec ${host} sh -c "if [ -f /etc/os-release ]; then . /etc/os-release && (echo $ID | grep -q 'alpine' && apk add --no-cache wget || (echo $ID | grep -q 'debian' && apt-get update && apt-get install -y wget) || (echo $ID | grep -q 'centos' && yum install -y wget)); else echo 'Unsupported OS'; fi"`,
10
10
  timeout
11
11
  );
12
12
  }
13
13
 
14
- // 使用 wget 检查端口健康状态
14
+ // use wget to check port health status
15
15
  const res = await promiseSpawn(`docker exec ${host} wget --spider http://${host}:${port}`, timeout);
16
16
  if (!res.includes('connected') && !res.includes('200 OK')) {
17
17
  throw new Error(`Docker endpoint ${host}:${port} is not healthy`);
@@ -36,7 +36,7 @@ const dialHttp = (host, port, timeout = 3 * 1000) => {
36
36
  const socket = net.connect({ host, port });
37
37
  let dataBuffer = '';
38
38
 
39
- // 设定一个整体超时定时器,确保整个 socket 生命期最多 3
39
+ // Set an overall timeout to keep the socket lifetime under 3 seconds
40
40
  const overallTimeout = setTimeout(() => {
41
41
  socket.destroy();
42
42
  reject(new Error(`Socket existed for more than ${timeout}ms`));
@@ -61,17 +61,17 @@ const dialHttp = (host, port, timeout = 3 * 1000) => {
61
61
  }
62
62
  });
63
63
 
64
- // 处理连接结束时的数据情况:如果首行未完整结束,也视为无效响应
64
+ // Handle data on connection end: if the first line is incomplete, treat it as an invalid response
65
65
  socket.on('end', () => {
66
66
  clearTimeout(overallTimeout);
67
67
  socket.destroy();
68
68
  if (!dataBuffer) {
69
69
  reject(new Error('No data received'));
70
70
  } else if (dataBuffer.indexOf('\r\n') === -1) {
71
- // 没有完整的首行
71
+ // No complete first line
72
72
  reject(new Error(`Response did not complete the first line: "${dataBuffer}"`));
73
73
  } else {
74
- // 如果数据有首行但没走到 data 回调中结束后的逻辑(比如数据中断),则进行最终判断
74
+ // If data has a first line but did not reach the data callback completion logic (for example interrupted data), do a final check
75
75
  const firstLine = dataBuffer.substring(0, dataBuffer.indexOf('\r\n'));
76
76
  if (firstLine.startsWith('HTTP')) {
77
77
  resolve(firstLine);
@@ -104,7 +104,7 @@ const ensureStarted = async ({
104
104
  await dialHttp(host, port, minConsecutiveTime);
105
105
  } else {
106
106
  await dial(host, port);
107
- // 如果是 TCP 协议, 标注需要等待 TCP 的,则需要等待 3 秒,确保服务已经启动,例如 MySQL 这种
107
+ // For TCP protocols marked as requiring TCP wait, wait 3 seconds to ensure the service has started, such as MySQL
108
108
  if (waitTCP) {
109
109
  await sleep(WAIT_TCP_TIME);
110
110
  }
@@ -34,7 +34,7 @@ module.exports = (ctx = {}) => {
34
34
  timezone: safeGet('x-timezone'),
35
35
  };
36
36
 
37
- // 警告,尽量不要在这里传递整个 headers 对象,因为可能会影响后续流程
37
+ // WARNING: avoid passing the entire headers object here as it may interfere with downstream processing
38
38
 
39
39
  return result;
40
40
  };
package/lib/fs.js CHANGED
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const canReadAndWriteDir = (dir) => {
5
5
  const root = '/';
6
6
 
7
- // 向父级找到一个存在且有读写权限的目录,否则返回false,即该目录不可读写
7
+ // walk up to the nearest ancestor directory that exists and has read/write permission; if none found, return false
8
8
  const tmpArray = dir.split(path.sep).filter(Boolean);
9
9
  do {
10
10
  const tmpDir = path.join(root, tmpArray.join(path.sep));
package/lib/gcp.js CHANGED
@@ -61,7 +61,7 @@ const getExternalIpv4 = async () => {
61
61
  };
62
62
 
63
63
  const getInternalIpv6 = async () => {
64
- // TODO: 没有支持 ipv6 的机器,这个接口没有在实际的机器上测试
64
+ // TODO: no IPv6-capable machine is available; this interface has not been tested on a real machine
65
65
  const internalIp = await getMeta('instance/network-interfaces/0/ipv6s');
66
66
 
67
67
  debug('getLocalIpv6', { internalIp });
@@ -69,7 +69,7 @@ const getInternalIpv6 = async () => {
69
69
  };
70
70
 
71
71
  const getExternalIpv6 = async () => {
72
- // TODO: 没有支持 ipv6 的机器,这个接口没有在实际的机器上测试
72
+ // TODO: no IPv6-capable machine is available; this interface has not been tested on a real machine
73
73
  const externalIp = await getMeta('instance/network-interfaces/0/access-configs/0/external-ipv6');
74
74
  debug('getPublicIpv6', { externalIp });
75
75
 
@@ -4,7 +4,7 @@ const pm2 = require('./pm2/async-pm2');
4
4
 
5
5
  const noop = () => {};
6
6
 
7
- // 新增 10_000 ms 的超时时间,避免 pm2 遇到什么问题一直不返回
7
+ // add 10,000 ms timeout to avoid pm2 hanging indefinitely on errors
8
8
  const getPm2ProcessInfo = (processId, { printError = noop, throwOnNotExist = true, timeout = 10_000 } = {}) => {
9
9
  return new Promise((resolve, reject) => {
10
10
  let settled = false;
@@ -47,8 +47,7 @@ function getTokenFromReq(req, opts) {
47
47
  if (tokenList.length > 1) {
48
48
  _duplicate = true;
49
49
  } else if (tokenList.length === 1) {
50
- // HACK: 在统一登录的切换账户场景中,会同时携带 cookie header bearerToken,但这两个值是不一样的
51
- // 如果出现了这种情况,则应该判断为不重复,并最终以 headerToken 的值为准
50
+ // HACK: in federated-login account-switch flow, both cookie and header bearerToken may be present with different values; treat as non-duplicate and prefer headerToken
52
51
  if (headerToken === (cookieToken || bodyToken || queryToken)) {
53
52
  _duplicate = true;
54
53
  }
@@ -157,7 +157,7 @@ const ensureCustomOgImage = (req, res, next) => {
157
157
  const ensureBundleLogo = (req, res, next) => {
158
158
  /**
159
159
  * @type {{
160
- * blocklet: import('@blocklet/server-js').BlockletState, // 目前肯定是不准确的,但是有些属性是通用的,无伤大雅
160
+ * blocklet: import('@blocklet/server-js').BlockletState, // currently not fully accurate, but some properties are generic and this is acceptable
161
161
  * sendOptions: any
162
162
  * }}
163
163
  * */
@@ -201,7 +201,7 @@ const cacheError = (err, req, res, next) => {
201
201
  const ensureDefaultLogo = (req, res, next) => {
202
202
  /**
203
203
  * @type {{
204
- * blocklet: import('@blocklet/server-js').BlockletState, // 目前肯定是不准确的,但是有些属性是通用的,无伤大雅
204
+ * blocklet: import('@blocklet/server-js').BlockletState, // currently not fully accurate, but some properties are generic and this is acceptable
205
205
  * sendOptions: any
206
206
  * }}
207
207
  * */
@@ -233,7 +233,7 @@ const ensureDefaultLogo = (req, res, next) => {
233
233
  const ensureCustomFavicon = (req, res, next) => {
234
234
  /**
235
235
  * @type {{
236
- * blocklet: import('@blocklet/server-js').BlockletState, // 目前肯定是不准确的,但是有些属性是通用的,无伤大雅
236
+ * blocklet: import('@blocklet/server-js').BlockletState, // currently not fully accurate, but some properties are generic and this is acceptable
237
237
  * sendOptions: any
238
238
  * }}
239
239
  * */
@@ -19,7 +19,7 @@ function ensureLocale({ methods = ['body', 'query', 'cookies'], force = false }
19
19
  }
20
20
  }
21
21
 
22
- // cookie 中的语言设置转换为标准语言
22
+ // normalize the language setting from the cookie into a standard locale code
23
23
  const localeMap = {
24
24
  'zh-CN': 'zh',
25
25
  'en-US': 'en',
@@ -14,11 +14,11 @@ const toTextList = (str) => {
14
14
  const arr = [];
15
15
  try {
16
16
  let startPoint = 0;
17
- const pattern = /<(.+?)\(?[tx|nft|token|stake|did|dapp|link]+[:](.+?)\)?>/gi; // 匹配 <asdad(did:abt:xxx)>
17
+ const pattern = /<(.+?)\(?[tx|nft|token|stake|did|dapp|link]+[:](.+?)\)?>/gi; // matches <asdad(did:abt:xxx)>
18
18
  const matches = str.matchAll(pattern);
19
19
 
20
20
  for (const match of matches) {
21
- const didPattern = /\([tx|nft|token|stake|did|dapp|link]+[:](.+?)\)/gi; // 匹配 (did:abt:xxx)
21
+ const didPattern = /\([tx|nft|token|stake|did|dapp|link]+[:](.+?)\)/gi; // matches (did:abt:xxx)
22
22
 
23
23
  const oriHit = match[0];
24
24
  const matchIndex = match.index || startPoint;
@@ -98,7 +98,7 @@ const toClickableSpan = (str, isHighLight = true) => {
98
98
  const url = getLink(item);
99
99
  const { type, chainId, did } = item;
100
100
 
101
- // HACK: 邮件中无法支持 dapp 的展示,缺少 dapp 链接,只能作为加粗展示
101
+ // HACK: dapp display is not supported in email (no dapp link available), so render as bold only
102
102
  if (isSameAddr(type, 'dapp')) {
103
103
  return `<em style="font-weight:bold;" data-type="${type}" data-chain-id="${chainId}" data-did="${did}">${item.text}</em>`;
104
104
  }
@@ -106,7 +106,7 @@ const toClickableSpan = (str, isHighLight = true) => {
106
106
  return `<a target="_blank" rel="noopener noreferrer" style="color:#4598fa;font-weight:bold;" href="${url}">${item.text}</a>`;
107
107
  }
108
108
 
109
- // 默认展示为加粗
109
+ // default: render as bold
110
110
  return `<em style="font-weight:bold;" data-type="${type}" data-chain-id="${chainId}" data-did="${did}">${item.text}</em>`;
111
111
  }
112
112
  return item.text;
@@ -118,7 +118,7 @@ const toClickableSpan = (str, isHighLight = true) => {
118
118
  };
119
119
 
120
120
  /**
121
- * 用于 slack 渲染带有连接的消息内容
121
+ * Render message content with links for Slack
122
122
  * @param {*} str
123
123
  * @returns
124
124
  */
@@ -129,7 +129,7 @@ const toSlackLink = (str) => {
129
129
  if (item instanceof Link) {
130
130
  return `<${getLink(item)}|${item.text}>`;
131
131
  }
132
- // HACK: 移除字符串中 :\n 的换行符
132
+ // HACK: remove :\n newlines from string content
133
133
  return item.replace(/:\n/g, ': ');
134
134
  })
135
135
  .join('');
@@ -20,7 +20,7 @@ const getExplorerUrl = (chainId) => {
20
20
  };
21
21
 
22
22
  /**
23
- * notifications 持久化列表
23
+ * persistent notifications list key
24
24
  */
25
25
  const generateKey = () => {
26
26
  const did = window?.env?.appId;
@@ -63,23 +63,23 @@ const getImagePath = (url) => {
63
63
  };
64
64
 
65
65
  /**
66
- * 判断通知是否包含activity
67
- * @param {Object} notification - 通知对象
68
- * @returns {boolean} - 是否包含activity
66
+ * Check whether the notification includes an activity
67
+ * @param {Object} notification - the notification object
68
+ * @returns {boolean} - whether the notification includes an activity
69
69
  */
70
70
  const isActivityIncluded = (notification) => {
71
71
  return !isEmpty(notification.activity) && !!notification.activity.type && !!notification.activity.actor;
72
72
  };
73
73
 
74
74
  /**
75
- * 判断 actor 是否是用户的 DID
76
- * actor 的类型有两种 1) 用户 2) 组件
75
+ * Check whether the actor is a user DID
76
+ * actor can be one of two types: 1) user 2) component
77
77
  * @param {*} notification
78
78
  * @returns
79
79
  */
80
80
  const isUserActor = (notification) => {
81
- // 1. 如果是 isActivityIncluded 并且 notification 中有 actorInfo 则认为是用户
82
- // 2. 如果是 isActivityIncluded 并且 actorInfo 没有查询到用户,则认为是 component
81
+ // 1. if isActivityIncluded and the notification has actorInfo, treat as a user
82
+ // 2. if isActivityIncluded but actorInfo does not resolve to a user, treat as a component
83
83
  return isActivityIncluded(notification) && !!notification.actorInfo && !!notification.actorInfo.did;
84
84
  };
85
85
 
@@ -124,7 +124,6 @@ const ACTIVITY_DESCRIPTIONS = {
124
124
  [ACTIVITY_TYPES.UN_ASSIGN]: () => 'has revoked your task assignment: ',
125
125
  };
126
126
 
127
- // 导出所有函数
128
127
  module.exports = {
129
128
  isEVMChain,
130
129
  getExplorerUrl,
@@ -38,7 +38,7 @@ function loadReloadEnv() {
38
38
  }
39
39
  }
40
40
 
41
- // 等单个 pm_id online
41
+ // wait for a single pm_id to come online
42
42
  function waitProcOnlineById(id, timeout = 20_000) {
43
43
  const start = Date.now();
44
44
  return new Promise((resolve, reject) => {
@@ -61,16 +61,16 @@ async function rollingRestartWithEnv(config) {
61
61
  pm2.list((err, list) => (err ? reject(err) : resolve(list)));
62
62
  });
63
63
 
64
- // 固定顺序
64
+ // ensure a fixed order
65
65
  const targets = procs.filter((p) => p.name === name).sort((a, b) => a.pm_id - b.pm_id);
66
66
 
67
67
  for (const p of targets) {
68
68
  await new Promise((resolve, reject) => {
69
69
  pm2.reload(p.pm_id, {}, (err) => (err ? reject(err) : resolve()));
70
70
  });
71
- // 等这个 pm_id 回到 online(而不是 name 级别)
71
+ // wait for this specific pm_id to come back online (not at the name level)
72
72
  await waitProcOnlineById(p.pm_id, listen_timeout);
73
- // 给切换一点冗余,避免瞬时影响
73
+ // add a small buffer after the switch to avoid transient disruption
74
74
  await new Promise((r) => setTimeout(r, 500));
75
75
  }
76
76
  }
@@ -7,21 +7,21 @@ function setupGracefulShutdown(server, opts = {}) {
7
7
  server[INSTALLED] = true;
8
8
 
9
9
  const {
10
- killTimeout = 10000, // 总体兜底退出时间(ms
11
- socketEndTimeout = 5000, // 等待所有 TCP 连接优雅结束的最大时间(ms
12
- wsServers = [], // 可传入 ws socket.io 实例,用于优雅关闭
13
- logger = console, // 日志对象
14
- setConnectionCloseOnShutdown = true, // h1 关停时设置 Connection: close / shouldKeepAlive=false
15
- tuneKeepAlive = true, // 关停时温和缩短 keepAliveTimeout
16
- http503OnShutdown = false, // 关停窗口是否对新请求/新流立即 503(默认 false:保持 200
17
- http503DelayMs = 2000, // 503 生效延迟(ms),给新进程接管留时间
18
- quietEndDelay = 250, // 关停后延迟一小段再 end 空闲 socket(降低竞态)
19
- // ---- HTTP/2 相关可选项 ----
20
- enableHttp2Support = false, // 若服务包含 HTTP/2,建议保持 true
21
- http2RefuseNewStreamsOnShutdown = true, // 关停窗口拒绝“新建流”(用 503 响应)
22
- // ---- KA 调优参数(温和缩短)----
23
- kaGentleMs = 600, // 关停期将 keepAliveTimeout 降到该值(建议 300–800ms
24
- kaPhaseDelayMs = 500, // 进入关停后,延迟一小段时间再缩短 KA
10
+ killTimeout = 10000, // overall fallback exit timeout (ms)
11
+ socketEndTimeout = 5000, // max time to wait for all TCP connections to close gracefully (ms)
12
+ wsServers = [], // ws or socket.io instances to close gracefully
13
+ logger = console, // logger instance
14
+ setConnectionCloseOnShutdown = true, // set Connection: close / shouldKeepAlive=false during h1 shutdown
15
+ tuneKeepAlive = true, // gently shorten keepAliveTimeout during shutdown
16
+ http503OnShutdown = false, // whether to immediately 503 new requests/streams during shutdown window (default false: keep 200)
17
+ http503DelayMs = 2000, // delay (ms) before 503 takes effect, giving the new process time to take over
18
+ quietEndDelay = 250, // delay slightly after shutdown before ending idle sockets (reduces race conditions)
19
+ // ---- HTTP/2 optional settings ----
20
+ enableHttp2Support = false, // set to true if the server uses HTTP/2
21
+ http2RefuseNewStreamsOnShutdown = true, // refuse new streams during the shutdown window (respond with 503)
22
+ // ---- Keep-Alive tuning parameters (gentle reduction) ----
23
+ kaGentleMs = 600, // reduce keepAliveTimeout to this value during shutdown (recommended 300–800ms)
24
+ kaPhaseDelayMs = 500, // delay slightly after entering shutdown before shortening KA
25
25
  } = opts;
26
26
 
27
27
  let isShuttingDown = false;
@@ -42,7 +42,7 @@ function setupGracefulShutdown(server, opts = {}) {
42
42
  server.on('listening', sendReadyOnce);
43
43
  }
44
44
 
45
- // --------- HTTP/1.1 连接与请求跟踪 ----------
45
+ // --------- HTTP/1.1 connection and request tracking ----------
46
46
  const sockets = new Set();
47
47
  const metaMap = new WeakMap(); // socket -> { active: number, _ended?: boolean }
48
48
 
@@ -60,11 +60,11 @@ function setupGracefulShutdown(server, opts = {}) {
60
60
  const isH2 = req.httpVersionMajor >= 2;
61
61
 
62
62
  if (isShuttingDown) {
63
- // HTTP/1.x:仅设置 shouldKeepAlive=falseNode 会自动输出 Connection: close
63
+ // HTTP/1.x: only set shouldKeepAlive=false (Node will automatically emit Connection: close)
64
64
  if (!isH2 && setConnectionCloseOnShutdown && !res.headersSent) {
65
65
  try {
66
66
  res.shouldKeepAlive = false;
67
- // 下面这句可选;shouldKeepAlive=false 已足够,保留以兼容旧栈
67
+ // the line below is optional; shouldKeepAlive=false is sufficient, kept for compatibility with older stacks
68
68
  res.setHeader?.('Connection', 'close');
69
69
  } catch {
70
70
  //
@@ -88,7 +88,7 @@ function setupGracefulShutdown(server, opts = {}) {
88
88
  if (!m) return;
89
89
  m.active = Math.max(0, m.active - 1);
90
90
 
91
- // 关停期且该 socket 已无在途请求 优雅收尾
91
+ // during shutdown, if this socket has no in-flight requests -> graceful teardown
92
92
  if (isShuttingDown && m.active === 0 && !m._ended) {
93
93
  m._ended = true;
94
94
  try {
@@ -99,11 +99,11 @@ function setupGracefulShutdown(server, opts = {}) {
99
99
  }
100
100
  };
101
101
 
102
- res.on('finish', done); // 正常完成
103
- res.on('close', done); // 客户端提前断开
102
+ res.on('finish', done); // normal completion
103
+ res.on('close', done); // client disconnected early
104
104
  });
105
105
 
106
- // --------- WebSocket / Socket.IO 关闭 ----------
106
+ // --------- WebSocket / Socket.IO shutdown ----------
107
107
  function closeWebSockets() {
108
108
  for (const s of wsServers) {
109
109
  if (s && s.clients && typeof s.clients.forEach === 'function') {
@@ -146,7 +146,7 @@ function setupGracefulShutdown(server, opts = {}) {
146
146
  }
147
147
  }
148
148
  }
149
- s.close(); // 停止接受新连接并关闭现有
149
+ s.close(); // stop accepting new connections and close existing ones
150
150
  } catch (e) {
151
151
  logger.error('[shutdown] close socket.io failed:', e);
152
152
  }
@@ -162,7 +162,7 @@ function setupGracefulShutdown(server, opts = {}) {
162
162
  }
163
163
  }
164
164
 
165
- // --------- HTTP/2 会话与流管理 ----------
165
+ // --------- HTTP/2 session and stream management ----------
166
166
  const h2Sessions = new Set();
167
167
  const h2Meta = new WeakMap(); // session -> { active: number, closed?: boolean }
168
168
 
@@ -173,27 +173,27 @@ function setupGracefulShutdown(server, opts = {}) {
173
173
  const http2 = require('http2');
174
174
  NGHTTP2_NO_ERROR = http2.constants.NGHTTP2_NO_ERROR;
175
175
 
176
- // http2 服务器会触发
176
+ // only triggered on http2 servers
177
177
  server.on?.('session', (session) => {
178
178
  h2Sessions.add(session);
179
179
  h2Meta.set(session, { active: 0 });
180
180
 
181
181
  session.on('close', () => h2Sessions.delete(session));
182
182
 
183
- // 统计活跃流, Http2Stream
183
+ // count active streams (Http2Stream)
184
184
  session.on('stream', (stream) => {
185
185
  const meta = h2Meta.get(session);
186
186
  if (meta) meta.active++;
187
187
 
188
- // 关停窗口内:可选择拒绝“新建流”
188
+ // within the shutdown window: optionally refuse new stream creation
189
189
  if (isShuttingDown && http2RefuseNewStreamsOnShutdown) {
190
190
  const should503 =
191
191
  http503OnShutdown && (http503DelayMs <= 0 || Date.now() - shutdownStartedAt >= http503DelayMs);
192
192
  try {
193
- // 503 语义清晰,Node 没有直接暴露 REFUSED_STREAM 常量
193
+ // 503 is semantically clear; Node does not directly expose the REFUSED_STREAM constant
194
194
  stream.respond({ ':status': should503 ? 503 : 200 });
195
195
  if (should503) stream.end('server reloading');
196
- else stream.end(); // 若不打 503,可立即结束,以促使客户端重建到新实例
196
+ else stream.end(); // if not sending 503, end immediately to prompt the client to reconnect to the new instance
197
197
  return;
198
198
  } catch {
199
199
  //
@@ -221,11 +221,11 @@ function setupGracefulShutdown(server, opts = {}) {
221
221
  });
222
222
  }
223
223
  } catch (e) {
224
- // 环境不支持 http2,忽略
224
+ // environment does not support http2, ignore
225
225
  NGHTTP2_NO_ERROR = undefined;
226
226
  }
227
227
 
228
- // --------- h1 TCP socket 收尾 ----------
228
+ // --------- h1 TCP socket teardown ----------
229
229
  function drainTcpSockets() {
230
230
  sockets.forEach((socket) => {
231
231
  const meta = metaMap.get(socket) || { active: 0 };
@@ -239,7 +239,7 @@ function setupGracefulShutdown(server, opts = {}) {
239
239
  }
240
240
  });
241
241
 
242
- // 兜底:到点仍未关闭的强制 destroy(避免泄露)
242
+ // fallback: force-destroy any sockets still open at deadline (prevents leaks)
243
243
  const hardKillAfter = Math.min(socketEndTimeout, Math.max(0, killTimeout - 200));
244
244
  setTimeout(() => {
245
245
  sockets.forEach((socket) => {
@@ -258,15 +258,15 @@ function setupGracefulShutdown(server, opts = {}) {
258
258
  shutdownStartedAt = Date.now();
259
259
  logger.info('[shutdown] starting graceful shutdown');
260
260
 
261
- // (A) HTTP/1.x:温和缩短 keep-alive(可选)
261
+ // (A) HTTP/1.x: gently reduce keep-alive timeout (optional)
262
262
  if (tuneKeepAlive) {
263
263
  setTimeout(
264
264
  () => {
265
265
  try {
266
- const ka = Math.max(50, Number(kaGentleMs) || 600); // 安全下限 50ms
266
+ const ka = Math.max(50, Number(kaGentleMs) || 600); // safety lower bound: 50ms
267
267
  if ('keepAliveTimeout' in server) server.keepAliveTimeout = ka;
268
268
  if ('headersTimeout' in server) server.headersTimeout = Math.max(server.headersTimeout || 0, ka + 1000);
269
- // 不设置 requestTimeout(保持 0 / 无限),避免掐在途
269
+ // do not set requestTimeout (keep 0 / unlimited) to avoid cutting off in-flight requests
270
270
  } catch {
271
271
  //
272
272
  }
@@ -275,7 +275,7 @@ function setupGracefulShutdown(server, opts = {}) {
275
275
  ).unref?.();
276
276
  }
277
277
 
278
- // (B) HTTP/2:向现有会话发 GOAWAY,阻止新流(让已有流跑完)
278
+ // (B) HTTP/2: send GOAWAY to existing sessions to block new streams (let existing streams finish)
279
279
  if (enableHttp2Support && NGHTTP2_NO_ERROR != null) {
280
280
  for (const session of h2Sessions) {
281
281
  try {
@@ -286,10 +286,10 @@ function setupGracefulShutdown(server, opts = {}) {
286
286
  }
287
287
  }
288
288
 
289
- // 1) 先优雅关闭 WS/Socket.IO
289
+ // 1) gracefully close WS/Socket.IO first
290
290
  closeWebSockets();
291
291
 
292
- // 2) 停止接收新的 TCP 连接/HTTP 请求/HTTP2 会话
292
+ // 2) stop accepting new TCP connections / HTTP requests / HTTP2 sessions
293
293
  if (typeof server.close === 'function') {
294
294
  server.close(() => {
295
295
  logger.log('[shutdown] http server closed, exiting 0');
@@ -297,11 +297,11 @@ function setupGracefulShutdown(server, opts = {}) {
297
297
  });
298
298
  }
299
299
 
300
- // 3) 稍等一会儿再去收尾(减少与在途请求竞争)
300
+ // 3) wait briefly before teardown (reduces race with in-flight requests)
301
301
  setTimeout(() => {
302
302
  drainTcpSockets(); // h1
303
303
 
304
- // h2:对“已无活跃流”的会话关闭
304
+ // h2: close sessions with no active streams
305
305
  if (enableHttp2Support) {
306
306
  for (const session of h2Sessions) {
307
307
  const meta = h2Meta.get(session) || { active: 0 };
@@ -317,7 +317,7 @@ function setupGracefulShutdown(server, opts = {}) {
317
317
  }
318
318
  }, quietEndDelay).unref?.();
319
319
 
320
- // 4) h2 兜底:到点仍未关闭的会话直接 destroy,避免泄露
320
+ // 4) h2 fallback: destroy any sessions still open at deadline to prevent leaks
321
321
  const hardKillAfter = Math.min(socketEndTimeout, Math.max(0, killTimeout - 200));
322
322
  setTimeout(() => {
323
323
  if (enableHttp2Support) {
@@ -331,7 +331,7 @@ function setupGracefulShutdown(server, opts = {}) {
331
331
  }
332
332
  }, hardKillAfter).unref?.();
333
333
 
334
- // 5) 最终兜底:进程仍未退出则强退
334
+ // 5) final fallback: force-exit if the process has not exited yet
335
335
  setTimeout(() => {
336
336
  logger.error('[shutdown] timeout reached, forcing exit 1');
337
337
  process.exit(1);
package/lib/port.js CHANGED
@@ -51,7 +51,7 @@ const isPortTaken = async (port) => {
51
51
  .once('listening', () => {
52
52
  tester.once('close', () => resolve(false)).close();
53
53
  })
54
- .listen(port, '::'); // 这里需要注意: 如果不加 host, 在某些时候端口占用了, 还是会返回 false
54
+ .listen(port, '::'); // Note: without host, this may still return false even when the port is occupied
55
55
  });
56
56
  };
57
57