weifuwu 0.56.0 → 0.56.1

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/README.md CHANGED
@@ -679,7 +679,7 @@ app.use(db)
679
679
 
680
680
  ## redis — Redis 客户端(自研)
681
681
 
682
- > **自研 RESP2 协议**(零第三方依赖)——连接/重连(断线 pending 拒绝、指数退避)/离线队列/管道/Pub-Sub(订阅断线自动重放)+ 消除 ioredis 高频痛点(TTL 参数顺序、JSON 手动序列化、缓存样板)。
682
+ > **自研 RESP2 协议**(零第三方依赖)——连接/重连(断线 pending 拒绝、指数退避)/离线队列/管道/Pub-Sub(订阅断线自动重放)+ 消除 ioredis 高频痛点(TTL 参数顺序、JSON 手动序列化、缓存样板)。**二进制安全**:`getBuffer(key)` 原样返回字节(缓存序列化 payload 不损坏)。
683
683
 
684
684
  ```ts
685
685
  import { redis } from 'weifuwu'
@@ -39,9 +39,9 @@ export declare class PgConnection {
39
39
  private onReady;
40
40
  private onAuthFail;
41
41
  private expectingAuth;
42
- private authStage;
43
42
  private authCtx;
44
43
  private prepared;
44
+ private static readonly PREPARED_MAX;
45
45
  private stmtSeq;
46
46
  private currentQuery;
47
47
  private onData;
@@ -49,6 +49,10 @@ export declare class PgConnection {
49
49
  /** SCRAM client-final 消息 */
50
50
  private scramFinal;
51
51
  private scramServerSignature;
52
+ /** LRU 读取:命中移到尾部(最近使用),超限删最旧 */
53
+ private getPrepared;
54
+ /** LRU 写入:刷新位置;超上限淘汰最旧(长运行服务防无限累积) */
55
+ private setPrepared;
52
56
  /** 事务:BEGIN → fn(tx) → COMMIT;fn 抛错 → ROLLBACK(回滚失败吞掉,保留原始错误) */
53
57
  transaction<T>(fn: (tx: {
54
58
  query: (sql: string, params?: (string | number | boolean | object | null)[]) => Promise<Row[]>;
@@ -21,7 +21,10 @@ export declare function startupMessage(params: Record<string, string>): Uint8Arr
21
21
  export declare function queryMessage(sql: string): Uint8Array;
22
22
  /** Parse: P + statementName\0 + query\0 + paramTypeCount(2) + OIDs */
23
23
  export declare function parseMessage(name: string, sql: string, paramTypes?: number[]): Uint8Array;
24
- /** Bind: B + portal\0 + statement\0 + fmtCount + formats + paramCount + params + resultFmtCount */
24
+ /**
25
+ * Bind: B + portal\0 + statement\0 + fmtCount + formats + paramCount + params + resultFmtCount
26
+ * 两遍法:先算 payload 总长 → 预分配一次写入(buffer + offset 指针,避免 number[] 累积 O(n²))。
27
+ */
25
28
  export declare function bindMessage(statement: string, params: (string | Uint8Array | null)[], paramFormats?: number[]): Uint8Array;
26
29
  /** Execute: E + portal\0 + maxRows(4) */
27
30
  export declare function executeMessage(portal?: string, maxRows?: number): Uint8Array;
@@ -15,9 +15,14 @@ export declare class RedisClient {
15
15
  private constructor();
16
16
  /** 建立连接并返回就绪的客户端 */
17
17
  static connect(options?: RedisClientOptions): Promise<RedisClient>;
18
- /** 底层命令透传(RESP 值) */
19
- command(name: string, ...args: (string | number)[]): Promise<RespValue>;
18
+ /** 底层命令透传(RESP 值);Buffer 参数字节原样发送 */
19
+ command(name: string, ...args: (string | number | Buffer)[]): Promise<RespValue>;
20
20
  get(key: string): Promise<string | null>;
21
+ /**
22
+ * 二进制安全读取:返回原始字节(Uint8Array),不经过字符串解码。
23
+ * 用于缓存二进制 payload(序列化字节、图片等)。key 不存在返回 null。
24
+ */
25
+ getBuffer(key: string): Promise<Uint8Array | null>;
21
26
  /**
22
27
  * SET。ttl 秒可省略;传入即安全生效(内部转 SET key val EX ttl)。
23
28
  */
@@ -18,6 +18,8 @@ export interface RedisConnectionOptions {
18
18
  maxRetries?: number;
19
19
  /** 未连接时命令是否入队等待(ioredis enableOfflineQueue 语义)。默认 true。 */
20
20
  enableOfflineQueue?: boolean;
21
+ /** 离线队列上限。默认 5000。超限命令立即 reject(防断线期间无限累积)。 */
22
+ maxOfflineQueue?: number;
21
23
  }
22
24
  export declare class RedisConnection {
23
25
  readonly ready = false;
@@ -25,6 +27,8 @@ export declare class RedisConnection {
25
27
  private socket;
26
28
  private parser;
27
29
  private pending;
30
+ /** pending 头指针(避免 shift() O(n)——消费后定期 compact) */
31
+ private pendingHead;
28
32
  private offlineQueue;
29
33
  private subs;
30
34
  private psubs;
@@ -42,8 +46,13 @@ export declare class RedisConnection {
42
46
  private openSocket;
43
47
  private handleDisconnect;
44
48
  private onData;
45
- /** 发送命令并等待响应(单连接严格有序)。未 ready 时入离线队列(enableOfflineQueue)或拒绝。 */
46
- command(name: string, ...args: (string | number)[]): Promise<RespValue>;
49
+ /**
50
+ * 发送命令并等待响应(单连接严格有序)。未 ready 时入离线队列(enableOfflineQueue)或拒绝。
51
+ * opts.asBuffer=true:响应保留原始字节(Uint8Array)——getBuffer 等二进制场景。
52
+ */
53
+ command(name: string, ...args: (string | number | Buffer | {
54
+ asBuffer?: boolean;
55
+ })[]): Promise<RespValue>;
47
56
  private sendNow;
48
57
  private flushOffline;
49
58
  /** 批量执行:一次 write 发送所有命令字节,响应按序路由(管道) */
@@ -34,6 +34,8 @@ export declare class RedisPool {
34
34
  private next;
35
35
  command(name: string, ...args: (string | number)[]): Promise<RespValue>;
36
36
  get(key: string): Promise<string | null>;
37
+ /** 二进制安全读取(原始字节,不解码) */
38
+ getBuffer(key: string): Promise<Uint8Array | null>;
37
39
  set(key: string, value: string | number, ttl?: number): Promise<'OK'>;
38
40
  del(...keys: string[]): Promise<number>;
39
41
  incr(key: string): Promise<number>;
@@ -7,7 +7,7 @@
7
7
  * 解码为增量式:连接层可喂入任意分片,累积到完整消息后取回。
8
8
  */
9
9
  import { DbError } from '../errors.ts';
10
- export type RespValue = string | number | null | RespError | RespValue[];
10
+ export type RespValue = string | number | null | RespError | RespValue[] | Uint8Array;
11
11
  /** 服务器错误响应(-ERR ...) */
12
12
  export declare class RespError extends DbError {
13
13
  constructor(message: string);
@@ -16,9 +16,11 @@ export declare class RespError extends DbError {
16
16
  export declare class IncompleteError extends Error {
17
17
  constructor();
18
18
  }
19
- /** 编码命令为 RESP 数组字节 */
19
+ /** 递归解码字节值(bulk string)。decodeBytes=false 时字节原样保留(二进制安全)。 */
20
+ export declare function decodeValue(v: RespValue, decodeBytes: boolean): RespValue;
21
+ /** 编码命令为 RESP 数组字节。Buffer 参数字节原样写入(二进制安全,零损坏)。 */
20
22
  export declare function encodeCommand(args: (string | number | Buffer)[]): Uint8Array;
21
- /** 从完整 buffer 解析单个 RESP 值(非增量——单消息场景) */
23
+ /** 从完整 buffer 解析单个 RESP 值(非增量——单消息场景,解码为 string 语义) */
22
24
  export declare function parseReply(data: Uint8Array): RespValue;
23
25
  export declare class RespParser {
24
26
  private buf;
@@ -35,8 +37,13 @@ export declare class RespParser {
35
37
  /** 全部消费后压缩(释放底层) */
36
38
  private compact;
37
39
  private parseValue;
40
+ /**
41
+ * 读整数(: 或 $ 长度):扫描数字字符边算值,直到 \r\n(手动解析,免 parseInt + 字符串)。
42
+ * 支持负号(-1)。未读到终止符抛 IncompleteError(push 回滚)。
43
+ */
44
+ private readInt;
38
45
  /** 读一行(到 \r\n),返回行内容(不含 type 字节与 \r\n),并推进 off */
39
46
  private readLine;
40
- /** 读 len 字节的 bulk 内容 + \r\n */
41
- private readBulk;
47
+ /** 读 len 字节的 bulk 内容 + \r\n(字节中立——不 decode,由调用方决定 string/Buffer) */
48
+ private readBulkBytes;
42
49
  }
package/dist/index.js CHANGED
@@ -1567,23 +1567,61 @@ function parseMessage(name, sql, paramTypes = []) {
1567
1567
  return encodeMessage("P", concat(body, types));
1568
1568
  }
1569
1569
  function bindMessage(statement, params, paramFormats = []) {
1570
- const body = [...utf8(""), 0, ...utf8(statement), 0];
1571
- body.push(0, paramFormats.length);
1570
+ const stmtBytes = utf8(statement);
1571
+ const lens = new Array(params.length);
1572
+ let total = 1 + stmtBytes.length + 1;
1573
+ total += 2 + paramFormats.length * 2;
1574
+ total += 2;
1575
+ for (let i = 0; i < params.length; i++) {
1576
+ const p = params[i];
1577
+ total += 4;
1578
+ if (p !== null) {
1579
+ const len = typeof p === "string" ? utf8Len(p) : p.length;
1580
+ lens[i] = len;
1581
+ total += len;
1582
+ }
1583
+ }
1584
+ total += 2;
1585
+ const body = new Uint8Array(total);
1586
+ let off = 0;
1587
+ body[off++] = 0;
1588
+ body.set(stmtBytes, off);
1589
+ off += stmtBytes.length;
1590
+ body[off++] = 0;
1591
+ body[off] = paramFormats.length >> 8 & 255;
1592
+ body[off + 1] = paramFormats.length & 255;
1593
+ off += 2;
1572
1594
  for (const f of paramFormats) {
1573
- body.push(f >> 8 & 255, f & 255);
1574
- }
1575
- body.push(0, params.length);
1576
- for (const p of params) {
1595
+ body[off] = f >> 8 & 255;
1596
+ body[off + 1] = f & 255;
1597
+ off += 2;
1598
+ }
1599
+ body[off] = params.length >> 8 & 255;
1600
+ body[off + 1] = params.length & 255;
1601
+ off += 2;
1602
+ for (let i = 0; i < params.length; i++) {
1603
+ const p = params[i];
1577
1604
  if (p === null) {
1578
- body.push(255, 255, 255, 255);
1605
+ body[off] = body[off + 1] = body[off + 2] = body[off + 3] = 255;
1606
+ off += 4;
1579
1607
  } else {
1580
- const bytes = typeof p === "string" ? utf8(p) : p;
1581
- body.push(bytes.length >> 24 & 255, bytes.length >> 16 & 255, bytes.length >> 8 & 255, bytes.length & 255);
1582
- for (const b of bytes) body.push(b);
1608
+ const len = lens[i];
1609
+ body[off] = len >> 24 & 255;
1610
+ body[off + 1] = len >> 16 & 255;
1611
+ body[off + 2] = len >> 8 & 255;
1612
+ body[off + 3] = len & 255;
1613
+ off += 4;
1614
+ if (typeof p === "string") body.set(utf8(p), off);
1615
+ else body.set(p, off);
1616
+ off += len;
1583
1617
  }
1584
1618
  }
1585
- body.push(0, 0);
1586
- return encodeMessage("B", new Uint8Array(body));
1619
+ body[off] = 0;
1620
+ body[off + 1] = 0;
1621
+ return encodeMessage("B", body);
1622
+ }
1623
+ function utf8Len(s) {
1624
+ return Buffer.byteLength(s);
1587
1625
  }
1588
1626
  function executeMessage(portal = "", maxRows = 0) {
1589
1627
  const body = new Uint8Array(portal.length + 1 + 4);
@@ -1675,7 +1713,7 @@ function parseRowDescription(payload) {
1675
1713
  for (let c = 0; c < count; c++) {
1676
1714
  let j = i;
1677
1715
  while (payload[j] !== 0) j++;
1678
- const name = new TextDecoder().decode(payload.subarray(i, j));
1716
+ const name = _decoder.decode(payload.subarray(i, j));
1679
1717
  i = j + 1;
1680
1718
  i += 6;
1681
1719
  const typeOid = payload[i] << 24 | payload[i + 1] << 16 | payload[i + 2] << 8 | payload[i + 3];
@@ -1697,7 +1735,7 @@ function parseDataRow(payload) {
1697
1735
  if (len === -1) {
1698
1736
  values.push(null);
1699
1737
  } else {
1700
- values.push(new TextDecoder().decode(payload.subarray(i, i + len)));
1738
+ values.push(_decoder.decode(payload.subarray(i, i + len)));
1701
1739
  i += len;
1702
1740
  }
1703
1741
  }
@@ -1710,7 +1748,7 @@ function parseErrorFields(payload) {
1710
1748
  const type = String.fromCharCode(payload[i]);
1711
1749
  let j = i + 1;
1712
1750
  while (j < payload.length && payload[j] !== 0) j++;
1713
- const value = new TextDecoder().decode(payload.subarray(i + 1, j));
1751
+ const value = _decoder.decode(payload.subarray(i + 1, j));
1714
1752
  switch (type) {
1715
1753
  case "S":
1716
1754
  out.severity = value;
@@ -1735,8 +1773,10 @@ function parseErrorFields(payload) {
1735
1773
  }
1736
1774
  return out;
1737
1775
  }
1776
+ var _decoder = new TextDecoder();
1777
+ var _encoder = new TextEncoder();
1738
1778
  function utf8(s) {
1739
- return new TextEncoder().encode(s);
1779
+ return _encoder.encode(s);
1740
1780
  }
1741
1781
  function concat(...parts) {
1742
1782
  let total = 0;
@@ -1789,7 +1829,7 @@ var ValidationError = class extends DbError {
1789
1829
  };
1790
1830
 
1791
1831
  // src/db/postgres/connection.ts
1792
- var PgConnection = class {
1832
+ var PgConnection = class _PgConnection {
1793
1833
  opts;
1794
1834
  timeoutSet = false;
1795
1835
  awaitingReady = false;
@@ -1823,11 +1863,7 @@ var PgConnection = class {
1823
1863
  }, this.opts.connectTimeoutMs);
1824
1864
  sock.once("connect", () => {
1825
1865
  sock.setNoDelay(true);
1826
- sock.write(
1827
- Buffer.from(
1828
- startupMessage({ user: this.opts.user, database: this.opts.database })
1829
- )
1830
- );
1866
+ sock.write(startupMessage({ user: this.opts.user, database: this.opts.database }));
1831
1867
  });
1832
1868
  sock.on("data", (chunk) => this.onData(chunk));
1833
1869
  sock.on("error", (err) => {
@@ -1860,10 +1896,10 @@ var PgConnection = class {
1860
1896
  onReady = null;
1861
1897
  onAuthFail = null;
1862
1898
  expectingAuth = true;
1863
- authStage = "start";
1864
1899
  authCtx = null;
1865
1900
  prepared = /* @__PURE__ */ new Map();
1866
- // sig → stmt + 列缓存
1901
+ // sig → stmt + 列缓存(LRU)
1902
+ static PREPARED_MAX = 128;
1867
1903
  stmtSeq = 0;
1868
1904
  currentQuery = null;
1869
1905
  onData(chunk) {
@@ -1885,7 +1921,6 @@ var PgConnection = class {
1885
1921
  const outer = crypto2.createHash("md5").update(inner + Buffer.from(salt).toString("latin1")).digest("hex");
1886
1922
  this.send(passwordMessage(`md5${outer}`));
1887
1923
  } else if (code === 10) {
1888
- this.authStage = "sasl-initial";
1889
1924
  const nonce = crypto2.randomBytes(18).toString("base64url");
1890
1925
  const clientFirstBare = `n=,r=${nonce}`;
1891
1926
  this.authCtx = { clientNonce: nonce, clientFirstBare, serverFirst: "" };
@@ -1907,7 +1942,6 @@ var PgConnection = class {
1907
1942
  }
1908
1943
  this.authCtx.serverFirst = serverFirst;
1909
1944
  const clientFinal = this.scramFinal(this.authCtx);
1910
- this.authStage = "sasl-final";
1911
1945
  this.send(encodeP(utf82(clientFinal)));
1912
1946
  } else if (code === 12) {
1913
1947
  const serverFinal = new TextDecoder().decode(msg.payload.subarray(4));
@@ -1934,9 +1968,7 @@ var PgConnection = class {
1934
1968
  if (this.opts.statementTimeoutMs > 0 && !this.timeoutSet) {
1935
1969
  this.timeoutSet = true;
1936
1970
  this.awaitingReady = true;
1937
- this.socket?.write(
1938
- Buffer.from(queryMessage(`SET statement_timeout = ${this.opts.statementTimeoutMs}`))
1939
- );
1971
+ this.socket?.write(queryMessage(`SET statement_timeout = ${this.opts.statementTimeoutMs}`));
1940
1972
  return;
1941
1973
  }
1942
1974
  this.onReady?.();
@@ -1948,9 +1980,8 @@ var PgConnection = class {
1948
1980
  case "T": {
1949
1981
  if (this.currentQuery) {
1950
1982
  this.currentQuery.columns = parseRowDescription(msg.payload);
1951
- const entry = this.prepared.get(
1952
- `${this.currentQuery.sql}|${this.currentQuery.params?.length ?? 0}`
1953
- );
1983
+ const sig = `${this.currentQuery.sql}|${this.currentQuery.params?.length ?? 0}`;
1984
+ const entry = this.getPrepared(sig);
1954
1985
  if (entry) entry.columns = this.currentQuery.columns;
1955
1986
  }
1956
1987
  break;
@@ -1973,7 +2004,7 @@ var PgConnection = class {
1973
2004
  if (this.currentQuery?.awaitingDescribe && this.currentQuery.sql !== void 0) {
1974
2005
  this.currentQuery.awaitingDescribe = false;
1975
2006
  if (this.currentQuery.prepKey && this.currentQuery.prepName) {
1976
- this.prepared.set(this.currentQuery.prepKey, {
2007
+ this.setPrepared(this.currentQuery.prepKey, {
1977
2008
  name: this.currentQuery.prepName,
1978
2009
  columns: this.currentQuery.columns
1979
2010
  });
@@ -2057,6 +2088,24 @@ var PgConnection = class {
2057
2088
  const authMessage = `${ctx.clientFirstBare},${ctx.serverFirst},${clientFinalWithoutProof}`;
2058
2089
  return Buffer.from(hmac(serverKey, authMessage)).toString("base64");
2059
2090
  }
2091
+ /** LRU 读取:命中移到尾部(最近使用),超限删最旧 */
2092
+ getPrepared(sig) {
2093
+ const entry = this.prepared.get(sig);
2094
+ if (entry) {
2095
+ this.prepared.delete(sig);
2096
+ this.prepared.set(sig, entry);
2097
+ }
2098
+ return entry;
2099
+ }
2100
+ /** LRU 写入:刷新位置;超上限淘汰最旧(长运行服务防无限累积) */
2101
+ setPrepared(sig, entry) {
2102
+ this.prepared.delete(sig);
2103
+ this.prepared.set(sig, entry);
2104
+ if (this.prepared.size > _PgConnection.PREPARED_MAX) {
2105
+ const oldest = this.prepared.keys().next().value;
2106
+ if (oldest !== void 0) this.prepared.delete(oldest);
2107
+ }
2108
+ }
2060
2109
  /** 事务:BEGIN → fn(tx) → COMMIT;fn 抛错 → ROLLBACK(回滚失败吞掉,保留原始错误) */
2061
2110
  async transaction(fn) {
2062
2111
  await this.query("BEGIN");
@@ -2088,10 +2137,10 @@ var PgConnection = class {
2088
2137
  resolve: resolve3,
2089
2138
  reject
2090
2139
  };
2091
- this.socket.write(Buffer.from(queryMessage(sql)));
2140
+ this.socket.write(queryMessage(sql));
2092
2141
  } else {
2093
2142
  const sig = `${sql}|${params.length}`;
2094
- let stmtEntry = this.prepared.get(sig);
2143
+ let stmtEntry = this.getPrepared(sig);
2095
2144
  const encoded = encodeParams(params);
2096
2145
  if (!stmtEntry) {
2097
2146
  const name = `wf_s${++this.stmtSeq}`;
@@ -2106,9 +2155,9 @@ var PgConnection = class {
2106
2155
  prepKey: sig,
2107
2156
  prepName: name
2108
2157
  };
2109
- this.socket.write(Buffer.from(parseMessage(name, sql, params.map(() => 0))));
2110
- this.socket.write(Buffer.from(describeMessage("S", name)));
2111
- this.socket.write(Buffer.from(flushMessage()));
2158
+ this.socket.write(parseMessage(name, sql, params.map(() => 0)));
2159
+ this.socket.write(describeMessage("S", name));
2160
+ this.socket.write(flushMessage());
2112
2161
  } else {
2113
2162
  this.currentQuery = {
2114
2163
  columns: [...stmtEntry.columns],
@@ -2119,15 +2168,15 @@ var PgConnection = class {
2119
2168
  params: encoded,
2120
2169
  awaitingDescribe: false
2121
2170
  };
2122
- this.socket.write(Buffer.from(bindMessage(stmtEntry.name, encoded)));
2123
- this.socket.write(Buffer.from(executeMessage()));
2124
- this.socket.write(Buffer.from(syncMessage()));
2171
+ this.socket.write(bindMessage(stmtEntry.name, encoded));
2172
+ this.socket.write(executeMessage());
2173
+ this.socket.write(syncMessage());
2125
2174
  }
2126
2175
  }
2127
2176
  });
2128
2177
  }
2129
2178
  send(data) {
2130
- this.socket?.write(Buffer.from(data));
2179
+ this.socket?.write(data);
2131
2180
  }
2132
2181
  notifyIdle() {
2133
2182
  const ws = this.waiters;
@@ -2143,7 +2192,7 @@ var PgConnection = class {
2143
2192
  async close() {
2144
2193
  const sock = this.socket;
2145
2194
  if (this.status === "ready" && sock) {
2146
- sock.write(Buffer.from(terminateMessage()));
2195
+ sock.write(terminateMessage());
2147
2196
  }
2148
2197
  this.status = "closed";
2149
2198
  if (sock) sock.destroy();
@@ -2615,19 +2664,56 @@ var IncompleteError = class extends Error {
2615
2664
  this.name = "IncompleteError";
2616
2665
  }
2617
2666
  };
2667
+ function decodeValue(v, decodeBytes) {
2668
+ if (v instanceof Uint8Array) return decodeBytes ? _decoder2.decode(v) : v;
2669
+ if (Array.isArray(v)) return v.map((x) => decodeValue(x, decodeBytes));
2670
+ return v;
2671
+ }
2618
2672
  function encodeCommand(args) {
2619
- const parts = [`*${args.length}\r
2620
- `];
2621
- for (const arg of args) {
2622
- const s = typeof arg === "string" ? arg : arg instanceof Buffer ? arg.toString() : String(arg);
2623
- parts.push(`$${Buffer.byteLength(s)}\r
2624
- ${s}\r
2625
- `);
2673
+ const lens = new Array(args.length);
2674
+ let total = headerLen(args.length);
2675
+ for (let i = 0; i < args.length; i++) {
2676
+ const arg = args[i];
2677
+ const len = typeof arg === "string" ? Buffer.byteLength(arg) : arg instanceof Buffer ? arg.length : String(arg).length;
2678
+ lens[i] = len;
2679
+ total += headerLen(len) + len + 2;
2680
+ }
2681
+ const out = new Uint8Array(total);
2682
+ let off = 0;
2683
+ const head = `*${args.length}\r
2684
+ `;
2685
+ out.set(_encoder2.encode(head), 0);
2686
+ off = head.length;
2687
+ for (let i = 0; i < args.length; i++) {
2688
+ const arg = args[i];
2689
+ const len = lens[i];
2690
+ const lh = `$${len}\r
2691
+ `;
2692
+ out.set(_encoder2.encode(lh), off);
2693
+ off += lh.length;
2694
+ if (typeof arg === "string") {
2695
+ out.set(_encoder2.encode(arg), off);
2696
+ } else if (arg instanceof Buffer) {
2697
+ out.set(arg, off);
2698
+ } else {
2699
+ out.set(_encoder2.encode(String(arg)), off);
2700
+ }
2701
+ off += len;
2702
+ out[off] = 13;
2703
+ out[off + 1] = 10;
2704
+ off += 2;
2626
2705
  }
2627
- return _encoder.encode(parts.join(""));
2706
+ return out;
2628
2707
  }
2629
- var _decoder = new TextDecoder();
2630
- var _encoder = new TextEncoder();
2708
+ function headerLen(n) {
2709
+ if (n < 10) return 4;
2710
+ if (n < 100) return 5;
2711
+ if (n < 1e3) return 6;
2712
+ if (n < 1e4) return 7;
2713
+ return 8;
2714
+ }
2715
+ var _decoder2 = new TextDecoder();
2716
+ var _encoder2 = new TextEncoder();
2631
2717
  var RespParser = class {
2632
2718
  buf = new Uint8Array(0);
2633
2719
  off = 0;
@@ -2701,16 +2787,15 @@ var RespParser = class {
2701
2787
  return new RespError(line);
2702
2788
  }
2703
2789
  case ":": {
2704
- const line = this.readLine();
2705
- return parseInt(line, 10);
2790
+ return this.readInt();
2706
2791
  }
2707
2792
  case "$": {
2708
- const len = parseInt(this.readLine(), 10);
2793
+ const len = this.readInt();
2709
2794
  if (len === -1) return null;
2710
- return this.readBulk(len);
2795
+ return this.readBulkBytes(len);
2711
2796
  }
2712
2797
  case "*": {
2713
- const count = parseInt(this.readLine(), 10);
2798
+ const count = this.readInt();
2714
2799
  if (count === -1) return null;
2715
2800
  const items = [];
2716
2801
  for (let i = 0; i < count; i++) items.push(this.parseValue());
@@ -2720,25 +2805,50 @@ var RespParser = class {
2720
2805
  throw new DbError("protocol", `unknown RESP type byte: ${type}`, { code: "RESP" });
2721
2806
  }
2722
2807
  }
2808
+ /**
2809
+ * 读整数(: 或 $ 长度):扫描数字字符边算值,直到 \r\n(手动解析,免 parseInt + 字符串)。
2810
+ * 支持负号(-1)。未读到终止符抛 IncompleteError(push 回滚)。
2811
+ */
2812
+ readInt() {
2813
+ const buf = this.buf;
2814
+ let i = this.off;
2815
+ let neg = false;
2816
+ if (i < buf.length && buf[i] === 45) {
2817
+ neg = true;
2818
+ i++;
2819
+ }
2820
+ let n = 0;
2821
+ while (i < buf.length && buf[i] >= 48 && buf[i] <= 57) {
2822
+ n = n * 10 + (buf[i] - 48);
2823
+ i++;
2824
+ }
2825
+ if (i + 1 < buf.length && buf[i] === 13 && buf[i + 1] === 10) {
2826
+ this.off = i + 2;
2827
+ return neg ? -n : n;
2828
+ }
2829
+ throw new IncompleteError();
2830
+ }
2723
2831
  /** 读一行(到 \r\n),返回行内容(不含 type 字节与 \r\n),并推进 off */
2724
2832
  readLine() {
2725
2833
  const idx = indexOfCRLF(this.buf, this.off);
2726
2834
  if (idx === -1) throw new IncompleteError();
2727
- const line = _decoder.decode(this.buf.subarray(this.off, idx));
2835
+ const line = _decoder2.decode(this.buf.subarray(this.off, idx));
2728
2836
  this.off = idx + 2;
2729
2837
  return line;
2730
2838
  }
2731
- /** 读 len 字节的 bulk 内容 + \r\n */
2732
- readBulk(len) {
2839
+ /** 读 len 字节的 bulk 内容 + \r\n(字节中立——不 decode,由调用方决定 string/Buffer) */
2840
+ readBulkBytes(len) {
2733
2841
  if (this.buf.length - this.off < len + 2) throw new IncompleteError();
2734
- const value = _decoder.decode(this.buf.subarray(this.off, this.off + len));
2842
+ const value = this.buf.subarray(this.off, this.off + len);
2735
2843
  this.off += len + 2;
2736
2844
  return value;
2737
2845
  }
2738
2846
  };
2739
2847
  function indexOfCRLF(buf, from = 0) {
2740
- for (let i = from; i < buf.length - 1; i++) {
2741
- if (buf[i] === 13 && buf[i + 1] === 10) return i;
2848
+ let i = buf.indexOf(13, from);
2849
+ while (i !== -1) {
2850
+ if (i + 1 < buf.length && buf[i + 1] === 10) return i;
2851
+ i = buf.indexOf(13, i + 1);
2742
2852
  }
2743
2853
  return -1;
2744
2854
  }
@@ -2751,6 +2861,8 @@ var RedisConnection = class {
2751
2861
  socket = null;
2752
2862
  parser = new RespParser();
2753
2863
  pending = [];
2864
+ /** pending 头指针(避免 shift() O(n)——消费后定期 compact) */
2865
+ pendingHead = 0;
2754
2866
  offlineQueue = [];
2755
2867
  subs = /* @__PURE__ */ new Map();
2756
2868
  psubs = /* @__PURE__ */ new Map();
@@ -2766,7 +2878,8 @@ var RedisConnection = class {
2766
2878
  port: options.port ?? 6379,
2767
2879
  retryDelayMs: options.retryDelayMs ?? 100,
2768
2880
  maxRetries: options.maxRetries ?? 10,
2769
- enableOfflineQueue: options.enableOfflineQueue ?? true
2881
+ enableOfflineQueue: options.enableOfflineQueue ?? true,
2882
+ maxOfflineQueue: options.maxOfflineQueue ?? 5e3
2770
2883
  };
2771
2884
  }
2772
2885
  /** 建立连接并等待 ready。重连失败(超过 maxRetries)抛 ConnectionError。 */
@@ -2816,10 +2929,10 @@ var RedisConnection = class {
2816
2929
  });
2817
2930
  }
2818
2931
  handleDisconnect(err) {
2819
- const queue = this.pending;
2932
+ const queue = this.pending.slice(this.pendingHead);
2820
2933
  this.pending = [];
2821
- const retryErr = err instanceof ConnectionError ? err : err;
2822
- for (const p of queue) p.reject(retryErr);
2934
+ this.pendingHead = 0;
2935
+ for (const p of queue) p.reject(err);
2823
2936
  if (this.closed || this.status === "closed") return;
2824
2937
  this.retries++;
2825
2938
  if (this.opts.maxRetries > 0 && this.retries > this.opts.maxRetries) {
@@ -2837,38 +2950,52 @@ var RedisConnection = class {
2837
2950
  onData(chunk) {
2838
2951
  try {
2839
2952
  const values = this.parser.pushAll(chunk);
2840
- for (const value of values) {
2841
- if (Array.isArray(value) && typeof value[0] === "string" && PUSH_TYPES.has(value[0])) {
2842
- this.dispatchSubscribe(value);
2953
+ for (const raw of values) {
2954
+ const first = Array.isArray(raw) && raw.length > 0 ? decodeValue(raw[0], true) : void 0;
2955
+ if (typeof first === "string" && PUSH_TYPES.has(first)) {
2956
+ this.dispatchSubscribe(decodeValue(raw, true));
2843
2957
  continue;
2844
2958
  }
2845
- const p = this.pending.shift();
2959
+ const p = this.pending[this.pendingHead++];
2846
2960
  if (!p) break;
2847
- if (value instanceof RespError) p.reject(value);
2848
- else p.resolve(value);
2961
+ if (raw instanceof RespError) p.reject(raw);
2962
+ else p.resolve(decodeValue(raw, !p.asBuffer));
2963
+ }
2964
+ if (this.pendingHead > 64 && this.pendingHead * 2 > this.pending.length) {
2965
+ this.pending = this.pending.slice(this.pendingHead);
2966
+ this.pendingHead = 0;
2849
2967
  }
2850
2968
  } catch (e) {
2851
- const queue = this.pending;
2969
+ const queue = this.pending.slice(this.pendingHead);
2852
2970
  this.pending = [];
2971
+ this.pendingHead = 0;
2853
2972
  for (const q of queue) q.reject(e);
2854
2973
  this.socket?.destroy();
2855
2974
  }
2856
2975
  }
2857
- /** 发送命令并等待响应(单连接严格有序)。未 ready 时入离线队列(enableOfflineQueue)或拒绝。 */
2976
+ /**
2977
+ * 发送命令并等待响应(单连接严格有序)。未 ready 时入离线队列(enableOfflineQueue)或拒绝。
2978
+ * opts.asBuffer=true:响应保留原始字节(Uint8Array)——getBuffer 等二进制场景。
2979
+ */
2858
2980
  command(name, ...args) {
2981
+ const last = args[args.length - 1];
2982
+ const opts = typeof last === "object" && last !== null && !(last instanceof Uint8Array) ? args.pop() : void 0;
2859
2983
  if (this.status === "ready" && this.socket) {
2860
- return this.sendNow(name, args);
2984
+ return this.sendNow(name, args, opts?.asBuffer);
2861
2985
  }
2862
2986
  if (this.closed || this.status === "closed" || !this.opts.enableOfflineQueue) {
2863
2987
  return Promise.reject(new ConnectionError("redis: not connected"));
2864
2988
  }
2989
+ if (this.offlineQueue.length >= this.opts.maxOfflineQueue) {
2990
+ return Promise.reject(new ConnectionError(`redis: offline queue full (${this.opts.maxOfflineQueue})`));
2991
+ }
2865
2992
  return new Promise((resolve3, reject) => {
2866
- this.offlineQueue.push({ name, args, resolve: resolve3, reject });
2993
+ this.offlineQueue.push({ name, args, resolve: resolve3, reject, asBuffer: opts?.asBuffer });
2867
2994
  });
2868
2995
  }
2869
- sendNow(name, args) {
2996
+ sendNow(name, args, asBuffer) {
2870
2997
  return new Promise((resolve3, reject) => {
2871
- this.pending.push({ resolve: resolve3, reject });
2998
+ this.pending.push({ resolve: resolve3, reject, asBuffer });
2872
2999
  this.socket.write(encodeCommand([name, ...args]));
2873
3000
  });
2874
3001
  }
@@ -2876,7 +3003,7 @@ var RedisConnection = class {
2876
3003
  const queue = this.offlineQueue;
2877
3004
  this.offlineQueue = [];
2878
3005
  for (const q of queue) {
2879
- this.sendNow(q.name, q.args).then(q.resolve, q.reject);
3006
+ this.sendNow(q.name, q.args, q.asBuffer).then(q.resolve, q.reject);
2880
3007
  }
2881
3008
  }
2882
3009
  /** 批量执行:一次 write 发送所有命令字节,响应按序路由(管道) */
@@ -2934,15 +3061,16 @@ var RedisConnection = class {
2934
3061
  clearTimeout(this.reconnectTimer);
2935
3062
  this.reconnectTimer = null;
2936
3063
  }
2937
- const queue = this.pending;
3064
+ const queue = this.pending.slice(this.pendingHead);
2938
3065
  this.pending = [];
3066
+ this.pendingHead = 0;
2939
3067
  for (const p of queue) p.reject(new ConnectionError("redis: connection closed"));
3068
+ const oq = this.offlineQueue;
3069
+ this.offlineQueue = [];
3070
+ for (const q of oq) q.reject(new ConnectionError("redis: connection closed"));
2940
3071
  const sock = this.socket;
2941
3072
  this.socket = null;
2942
- if (sock) {
2943
- sock.destroy();
2944
- await new Promise((r) => setTimeout(r, 0));
2945
- }
3073
+ sock?.destroy();
2946
3074
  }
2947
3075
  get connected() {
2948
3076
  return this.status === "ready";
@@ -2961,7 +3089,7 @@ var RedisClient = class _RedisClient {
2961
3089
  await conn.connect();
2962
3090
  return new _RedisClient(conn);
2963
3091
  }
2964
- /** 底层命令透传(RESP 值) */
3092
+ /** 底层命令透传(RESP 值);Buffer 参数字节原样发送 */
2965
3093
  command(name, ...args) {
2966
3094
  return this.conn.command(name, ...args);
2967
3095
  }
@@ -2970,6 +3098,15 @@ var RedisClient = class _RedisClient {
2970
3098
  const v = await this.conn.command("GET", key);
2971
3099
  return v === null ? null : String(v);
2972
3100
  }
3101
+ /**
3102
+ * 二进制安全读取:返回原始字节(Uint8Array),不经过字符串解码。
3103
+ * 用于缓存二进制 payload(序列化字节、图片等)。key 不存在返回 null。
3104
+ */
3105
+ async getBuffer(key) {
3106
+ const v = await this.conn.command("GET", key, { asBuffer: true });
3107
+ if (v === null) return null;
3108
+ return v instanceof Uint8Array ? v : new TextEncoder().encode(String(v));
3109
+ }
2973
3110
  /**
2974
3111
  * SET。ttl 秒可省略;传入即安全生效(内部转 SET key val EX ttl)。
2975
3112
  */
@@ -3106,6 +3243,11 @@ var RedisPool = class _RedisPool {
3106
3243
  await this.ensure();
3107
3244
  return this.next().get(this.k(key));
3108
3245
  }
3246
+ /** 二进制安全读取(原始字节,不解码) */
3247
+ async getBuffer(key) {
3248
+ await this.ensure();
3249
+ return this.next().getBuffer(this.k(key));
3250
+ }
3109
3251
  async set(key, value, ttl) {
3110
3252
  await this.ensure();
3111
3253
  return this.next().set(this.k(key), value, ttl);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.56.0",
4
+ "version": "0.56.1",
5
5
  "description": "AI SaaS framework — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": {