weifuwu 0.55.3 → 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/dist/index.js CHANGED
@@ -1083,23 +1083,219 @@ import { build } from "esbuild";
1083
1083
  import { readFile, stat } from "node:fs/promises";
1084
1084
  import { resolve as resolve2 } from "node:path";
1085
1085
  import { fileURLToPath } from "node:url";
1086
+
1087
+ // src/ui/html-safe.ts
1086
1088
  var HtmlSafe = class {
1089
+ value;
1087
1090
  constructor(value) {
1088
1091
  this.value = value;
1089
1092
  }
1090
- value;
1091
1093
  toString() {
1092
1094
  return this.value;
1093
1095
  }
1094
1096
  };
1097
+
1098
+ // src/client/vnode.ts
1099
+ var ASYNC_MARK = "__wfAsyncComponent";
1100
+ function isAsyncComponent(type) {
1101
+ return typeof type === "function" && type?.[ASYNC_MARK] === true;
1102
+ }
1103
+ var Fragment = /* @__PURE__ */ Symbol("Fragment");
1104
+ var Portal = /* @__PURE__ */ Symbol("Portal");
1105
+
1106
+ // src/client/reactive.ts
1107
+ function createReactiveState(dirty) {
1108
+ const proxyCache = /* @__PURE__ */ new WeakMap();
1109
+ const reactive = (target) => {
1110
+ if (target === null || typeof target !== "object") return target;
1111
+ if (proxyCache.has(target)) return proxyCache.get(target);
1112
+ const proxy = new Proxy(target, {
1113
+ set(target2, key, value) {
1114
+ const old = Reflect.get(target2, key);
1115
+ if (old === value) return true;
1116
+ Reflect.set(target2, key, value);
1117
+ dirty();
1118
+ return true;
1119
+ },
1120
+ get(target2, key) {
1121
+ const value = Reflect.get(target2, key);
1122
+ if (typeof value === "object" && value !== null) return reactive(value);
1123
+ return value;
1124
+ },
1125
+ deleteProperty(target2, key) {
1126
+ if (Reflect.has(target2, key)) {
1127
+ Reflect.deleteProperty(target2, key);
1128
+ dirty();
1129
+ }
1130
+ return true;
1131
+ }
1132
+ });
1133
+ proxyCache.set(target, proxy);
1134
+ return proxy;
1135
+ };
1136
+ return reactive({});
1137
+ }
1138
+
1139
+ // src/ui/ssr.ts
1095
1140
  function escape(s) {
1096
1141
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1097
1142
  }
1143
+ var VOID_TAGS = /* @__PURE__ */ new Set([
1144
+ "area",
1145
+ "base",
1146
+ "br",
1147
+ "col",
1148
+ "embed",
1149
+ "hr",
1150
+ "img",
1151
+ "input",
1152
+ "link",
1153
+ "meta",
1154
+ "param",
1155
+ "source",
1156
+ "track",
1157
+ "wbr"
1158
+ ]);
1159
+ function classToString(v) {
1160
+ if (typeof v === "string") return v;
1161
+ if (Array.isArray(v)) return v.filter(Boolean).join(" ");
1162
+ if (v && typeof v === "object") {
1163
+ return Object.entries(v).filter(([, b]) => b).map(([k]) => k).join(" ");
1164
+ }
1165
+ return "";
1166
+ }
1167
+ function styleToString(v) {
1168
+ if (typeof v === "string") return v;
1169
+ if (v && typeof v === "object") {
1170
+ return Object.entries(v).filter(([, x]) => x != null).map(([k, x]) => `${k}:${typeof x === "number" ? x + "px" : String(x)}`).join(";");
1171
+ }
1172
+ return "";
1173
+ }
1174
+ function createSsrContext(serverCtx, dataStore) {
1175
+ const usedIds = /* @__PURE__ */ new Set();
1176
+ const ui2 = {
1177
+ $: () => createReactiveState(() => {
1178
+ }),
1179
+ dirty: () => {
1180
+ },
1181
+ render: () => {
1182
+ },
1183
+ selfId: (name) => {
1184
+ if (usedIds.has(name)) throw new Error(`[weifuwu] Duplicate component ID: "${name}"`);
1185
+ usedIds.add(name);
1186
+ },
1187
+ useMedia: () => {
1188
+ },
1189
+ useBreakpoint: () => {
1190
+ },
1191
+ usePopupPosition: () => ({ top: 0, left: 0, refresh: () => {
1192
+ } })
1193
+ };
1194
+ const ctx = Object.create(serverCtx ?? {});
1195
+ ctx.ui = ui2;
1196
+ ctx.data = {
1197
+ async get(key, fetcher) {
1198
+ if (dataStore.has(key)) return dataStore.get(key);
1199
+ if (!fetcher) return void 0;
1200
+ const val = await fetcher();
1201
+ dataStore.set(key, val);
1202
+ return val;
1203
+ },
1204
+ set(key, val) {
1205
+ dataStore.set(key, val);
1206
+ },
1207
+ has(key) {
1208
+ return dataStore.has(key);
1209
+ }
1210
+ };
1211
+ return ctx;
1212
+ }
1213
+ async function renderSsr(input, ctx) {
1214
+ if (input == null || typeof input === "boolean") return "";
1215
+ if (typeof input === "string" || typeof input === "number") return escape(String(input));
1216
+ if (Array.isArray(input)) {
1217
+ let s = "";
1218
+ for (const c of input) s += await renderSsr(c, ctx);
1219
+ return s;
1220
+ }
1221
+ const vnode = input;
1222
+ if (vnode.type === Portal) return renderSsr(vnode.props?.children, ctx);
1223
+ if (vnode.type === Fragment) return renderSsr(vnode.props?.children, ctx);
1224
+ if (typeof vnode.type === "function") {
1225
+ let def;
1226
+ if (isAsyncComponent(vnode.type)) {
1227
+ def = await vnode.type(ctx);
1228
+ if (typeof def !== "function") {
1229
+ throw new Error(
1230
+ `asyncComponent factory <${vnode.type.name || "anonymous"}> must return a Component (initProps, ctx) => (props) => VNode.`
1231
+ );
1232
+ }
1233
+ } else {
1234
+ def = vnode.type;
1235
+ }
1236
+ const childCtx = Object.create(ctx);
1237
+ const renderFn = def(vnode.props ?? {}, childCtx);
1238
+ if (typeof renderFn !== "function") {
1239
+ throw new Error(
1240
+ `Component ${vnode.type.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
1241
+ );
1242
+ }
1243
+ return renderSsr(renderFn(vnode.props ?? {}), childCtx);
1244
+ }
1245
+ const tag = vnode.type;
1246
+ const props = vnode.props ?? {};
1247
+ const attrs = [];
1248
+ let innerHTML;
1249
+ for (const [key, value] of Object.entries(props)) {
1250
+ if (key === "children" || key === "key") continue;
1251
+ if (key === "ref") continue;
1252
+ if (key.startsWith("on") && typeof value === "function") continue;
1253
+ if (key === "innerHTML") {
1254
+ innerHTML = String(value ?? "");
1255
+ continue;
1256
+ }
1257
+ if (key === "class" || key === "className") {
1258
+ const cls = classToString(value);
1259
+ if (cls) attrs.push(` class="${escape(cls)}"`);
1260
+ continue;
1261
+ }
1262
+ if (key === "style" && value && typeof value === "object") {
1263
+ attrs.push(` style="${escape(styleToString(value))}"`);
1264
+ continue;
1265
+ }
1266
+ if (value === true) {
1267
+ attrs.push(` ${key}`);
1268
+ continue;
1269
+ }
1270
+ if (value === false || value == null) continue;
1271
+ attrs.push(` ${key}="${escape(String(value))}"`);
1272
+ }
1273
+ const attrStr = attrs.join("");
1274
+ if (VOID_TAGS.has(tag)) return `<${tag}${attrStr}>`;
1275
+ if (innerHTML !== void 0) return `<${tag}${attrStr}>${innerHTML}</${tag}>`;
1276
+ const children = await renderSsr(props.children, ctx);
1277
+ return `<${tag}${attrStr}>${children}</${tag}>`;
1278
+ }
1279
+ async function ssrToString(Comp, props, serverCtx, opts = {}) {
1280
+ const dataStore = opts.data ?? /* @__PURE__ */ new Map();
1281
+ const ctx = createSsrContext(serverCtx, dataStore);
1282
+ const html = await renderSsr({ type: Comp, props: props ?? {}, key: void 0 }, ctx);
1283
+ return new HtmlSafe(html);
1284
+ }
1285
+ function serializeData(data) {
1286
+ const json = JSON.stringify(Object.fromEntries(data)).replace(/</g, "\\u003c");
1287
+ return `<script>window.__DATA__=${json};</script>`;
1288
+ }
1289
+
1290
+ // src/ui/index.ts
1291
+ function escape2(s) {
1292
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1293
+ }
1098
1294
  function stringify(v) {
1099
1295
  if (v == null || v === false || v === true) return "";
1100
1296
  if (Array.isArray(v)) return v.map(stringify).join("");
1101
1297
  if (v instanceof HtmlSafe) return v.value;
1102
- return escape(String(v));
1298
+ return escape2(String(v));
1103
1299
  }
1104
1300
  function unsafe(s) {
1105
1301
  return new HtmlSafe(s);
@@ -1153,6 +1349,15 @@ function ui() {
1153
1349
  }
1154
1350
  ctx.ui = {
1155
1351
  html: Object.assign(htmlTag, { unsafe }),
1352
+ /** SSR 渲染组件 → HTML 片段(HtmlSafe:内联进 ctx.ui.html 不二次转义) */
1353
+ async ssr(Comp, props, opts) {
1354
+ const safe = await ssrToString(Comp, props ?? {}, ctx, opts);
1355
+ return safe;
1356
+ },
1357
+ /** 序列化 SSR 数据存储 → window.__DATA__ 脚本(HtmlSafe) */
1358
+ ssrData(data) {
1359
+ return new HtmlSafe(serializeData(data));
1360
+ },
1156
1361
  async js(entryPath) {
1157
1362
  const absPath = resolveEntry(entryPath);
1158
1363
  const cached = jsCache.get(absPath);
@@ -1217,6 +1422,98 @@ function ui() {
1217
1422
  };
1218
1423
  }
1219
1424
 
1425
+ // src/client/route-match.ts
1426
+ function joinPaths(a, b) {
1427
+ if (!b || b === "/") return a || "/";
1428
+ const left = a.endsWith("/") ? a.slice(0, -1) : a;
1429
+ const right = b.startsWith("/") ? b : "/" + b;
1430
+ return left + right;
1431
+ }
1432
+ function compilePath(path) {
1433
+ const keys = [];
1434
+ const reStr = path.replace(/:(\w+)/g, (_, key) => {
1435
+ keys.push(key);
1436
+ return "([^/]+)";
1437
+ }).replace(/\*/g, ".*");
1438
+ return { re: new RegExp(`^${reStr}$`), keys };
1439
+ }
1440
+ function flattenRoutes(routes, basePath = "", chain = []) {
1441
+ const result = [];
1442
+ for (const route of routes) {
1443
+ const fullPath = joinPaths(basePath, route.path);
1444
+ const { re, keys } = compilePath(fullPath);
1445
+ const fullChain = [...chain, route];
1446
+ result.push({ re, keys, def: route, chain: fullChain });
1447
+ if (route.children) {
1448
+ result.push(...flattenRoutes(route.children, fullPath, fullChain));
1449
+ }
1450
+ }
1451
+ return result;
1452
+ }
1453
+ function matchRoute(path, routes) {
1454
+ let best = null;
1455
+ for (const fr of routes) {
1456
+ if (path.match(fr.re)) {
1457
+ if (!best || fr.chain.length > best.chain.length) best = fr;
1458
+ }
1459
+ }
1460
+ return best;
1461
+ }
1462
+ function extractParams(path, match) {
1463
+ const params = {};
1464
+ const m = path.match(match.re);
1465
+ if (m) {
1466
+ for (let i = 0; i < match.keys.length; i++) {
1467
+ params[match.keys[i]] = decodeURIComponent(m[i + 1]);
1468
+ }
1469
+ }
1470
+ return params;
1471
+ }
1472
+
1473
+ // src/ui/ssr-page.ts
1474
+ var DEFAULT_TEMPLATE = (p) => `<!DOCTYPE html>
1475
+ <html lang="zh-CN">
1476
+ <head>
1477
+ <meta charset="UTF-8">
1478
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1479
+ <title>${escapeHtml(p.title)}</title>
1480
+ ${(p.styles ?? []).map((s) => `<link rel="stylesheet" href="${escapeHtml(s)}">`).join("\n ")}
1481
+ </head>
1482
+ <body>
1483
+ <div id="root">${p.html}</div>
1484
+ ${p.dataScript}
1485
+ ${p.bundle ? `<script src="${escapeHtml(p.bundle)}"></script>` : ""}
1486
+ </body>
1487
+ </html>`;
1488
+ function escapeHtml(s) {
1489
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1490
+ }
1491
+ function uiSsr(opts) {
1492
+ const flatRoutes = flattenRoutes(opts.routes);
1493
+ const template = opts.template ?? DEFAULT_TEMPLATE;
1494
+ return async (req, ctx, next) => {
1495
+ if (req.method !== "GET") return next(req, ctx);
1496
+ const url = new URL(req.url);
1497
+ const match = matchRoute(url.pathname, flatRoutes);
1498
+ if (!match) return next(req, ctx);
1499
+ const params = extractParams(url.pathname, match);
1500
+ const def = match.chain[match.chain.length - 1] ?? match.def;
1501
+ const pageCtx = Object.assign(Object.create(ctx), {
1502
+ params,
1503
+ route: { path: url.pathname, params, query: Object.fromEntries(url.searchParams) }
1504
+ });
1505
+ if (!def.component) return next(req, ctx);
1506
+ const data = /* @__PURE__ */ new Map();
1507
+ const html = await ssrToString(def.component, {}, pageCtx, { data });
1508
+ const title = opts.title ? opts.title({ params, def }) : def.title || "weifuwu";
1509
+ const dataScript = new HtmlSafe(serializeData(data)).toString();
1510
+ const body = template({ html: html.toString(), dataScript, title, bundle: opts.bundle, styles: opts.styles });
1511
+ return new Response(body, {
1512
+ headers: { "Content-Type": "text/html; charset=utf-8" }
1513
+ });
1514
+ };
1515
+ }
1516
+
1220
1517
  // src/db/postgres/connection.ts
1221
1518
  import net from "node:net";
1222
1519
  import crypto2 from "node:crypto";
@@ -1270,23 +1567,61 @@ function parseMessage(name, sql, paramTypes = []) {
1270
1567
  return encodeMessage("P", concat(body, types));
1271
1568
  }
1272
1569
  function bindMessage(statement, params, paramFormats = []) {
1273
- const body = [...utf8(""), 0, ...utf8(statement), 0];
1274
- 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;
1275
1594
  for (const f of paramFormats) {
1276
- body.push(f >> 8 & 255, f & 255);
1277
- }
1278
- body.push(0, params.length);
1279
- 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];
1280
1604
  if (p === null) {
1281
- body.push(255, 255, 255, 255);
1605
+ body[off] = body[off + 1] = body[off + 2] = body[off + 3] = 255;
1606
+ off += 4;
1282
1607
  } else {
1283
- const bytes = typeof p === "string" ? utf8(p) : p;
1284
- body.push(bytes.length >> 24 & 255, bytes.length >> 16 & 255, bytes.length >> 8 & 255, bytes.length & 255);
1285
- 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;
1286
1617
  }
1287
1618
  }
1288
- body.push(0, 0);
1289
- 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);
1290
1625
  }
1291
1626
  function executeMessage(portal = "", maxRows = 0) {
1292
1627
  const body = new Uint8Array(portal.length + 1 + 4);
@@ -1378,7 +1713,7 @@ function parseRowDescription(payload) {
1378
1713
  for (let c = 0; c < count; c++) {
1379
1714
  let j = i;
1380
1715
  while (payload[j] !== 0) j++;
1381
- const name = new TextDecoder().decode(payload.subarray(i, j));
1716
+ const name = _decoder.decode(payload.subarray(i, j));
1382
1717
  i = j + 1;
1383
1718
  i += 6;
1384
1719
  const typeOid = payload[i] << 24 | payload[i + 1] << 16 | payload[i + 2] << 8 | payload[i + 3];
@@ -1400,7 +1735,7 @@ function parseDataRow(payload) {
1400
1735
  if (len === -1) {
1401
1736
  values.push(null);
1402
1737
  } else {
1403
- values.push(new TextDecoder().decode(payload.subarray(i, i + len)));
1738
+ values.push(_decoder.decode(payload.subarray(i, i + len)));
1404
1739
  i += len;
1405
1740
  }
1406
1741
  }
@@ -1413,7 +1748,7 @@ function parseErrorFields(payload) {
1413
1748
  const type = String.fromCharCode(payload[i]);
1414
1749
  let j = i + 1;
1415
1750
  while (j < payload.length && payload[j] !== 0) j++;
1416
- const value = new TextDecoder().decode(payload.subarray(i + 1, j));
1751
+ const value = _decoder.decode(payload.subarray(i + 1, j));
1417
1752
  switch (type) {
1418
1753
  case "S":
1419
1754
  out.severity = value;
@@ -1438,8 +1773,10 @@ function parseErrorFields(payload) {
1438
1773
  }
1439
1774
  return out;
1440
1775
  }
1776
+ var _decoder = new TextDecoder();
1777
+ var _encoder = new TextEncoder();
1441
1778
  function utf8(s) {
1442
- return new TextEncoder().encode(s);
1779
+ return _encoder.encode(s);
1443
1780
  }
1444
1781
  function concat(...parts) {
1445
1782
  let total = 0;
@@ -1492,7 +1829,7 @@ var ValidationError = class extends DbError {
1492
1829
  };
1493
1830
 
1494
1831
  // src/db/postgres/connection.ts
1495
- var PgConnection = class {
1832
+ var PgConnection = class _PgConnection {
1496
1833
  opts;
1497
1834
  timeoutSet = false;
1498
1835
  awaitingReady = false;
@@ -1526,11 +1863,7 @@ var PgConnection = class {
1526
1863
  }, this.opts.connectTimeoutMs);
1527
1864
  sock.once("connect", () => {
1528
1865
  sock.setNoDelay(true);
1529
- sock.write(
1530
- Buffer.from(
1531
- startupMessage({ user: this.opts.user, database: this.opts.database })
1532
- )
1533
- );
1866
+ sock.write(startupMessage({ user: this.opts.user, database: this.opts.database }));
1534
1867
  });
1535
1868
  sock.on("data", (chunk) => this.onData(chunk));
1536
1869
  sock.on("error", (err) => {
@@ -1563,10 +1896,10 @@ var PgConnection = class {
1563
1896
  onReady = null;
1564
1897
  onAuthFail = null;
1565
1898
  expectingAuth = true;
1566
- authStage = "start";
1567
1899
  authCtx = null;
1568
1900
  prepared = /* @__PURE__ */ new Map();
1569
- // sig → stmt + 列缓存
1901
+ // sig → stmt + 列缓存(LRU)
1902
+ static PREPARED_MAX = 128;
1570
1903
  stmtSeq = 0;
1571
1904
  currentQuery = null;
1572
1905
  onData(chunk) {
@@ -1588,7 +1921,6 @@ var PgConnection = class {
1588
1921
  const outer = crypto2.createHash("md5").update(inner + Buffer.from(salt).toString("latin1")).digest("hex");
1589
1922
  this.send(passwordMessage(`md5${outer}`));
1590
1923
  } else if (code === 10) {
1591
- this.authStage = "sasl-initial";
1592
1924
  const nonce = crypto2.randomBytes(18).toString("base64url");
1593
1925
  const clientFirstBare = `n=,r=${nonce}`;
1594
1926
  this.authCtx = { clientNonce: nonce, clientFirstBare, serverFirst: "" };
@@ -1610,7 +1942,6 @@ var PgConnection = class {
1610
1942
  }
1611
1943
  this.authCtx.serverFirst = serverFirst;
1612
1944
  const clientFinal = this.scramFinal(this.authCtx);
1613
- this.authStage = "sasl-final";
1614
1945
  this.send(encodeP(utf82(clientFinal)));
1615
1946
  } else if (code === 12) {
1616
1947
  const serverFinal = new TextDecoder().decode(msg.payload.subarray(4));
@@ -1637,9 +1968,7 @@ var PgConnection = class {
1637
1968
  if (this.opts.statementTimeoutMs > 0 && !this.timeoutSet) {
1638
1969
  this.timeoutSet = true;
1639
1970
  this.awaitingReady = true;
1640
- this.socket?.write(
1641
- Buffer.from(queryMessage(`SET statement_timeout = ${this.opts.statementTimeoutMs}`))
1642
- );
1971
+ this.socket?.write(queryMessage(`SET statement_timeout = ${this.opts.statementTimeoutMs}`));
1643
1972
  return;
1644
1973
  }
1645
1974
  this.onReady?.();
@@ -1651,9 +1980,8 @@ var PgConnection = class {
1651
1980
  case "T": {
1652
1981
  if (this.currentQuery) {
1653
1982
  this.currentQuery.columns = parseRowDescription(msg.payload);
1654
- const entry = this.prepared.get(
1655
- `${this.currentQuery.sql}|${this.currentQuery.params?.length ?? 0}`
1656
- );
1983
+ const sig = `${this.currentQuery.sql}|${this.currentQuery.params?.length ?? 0}`;
1984
+ const entry = this.getPrepared(sig);
1657
1985
  if (entry) entry.columns = this.currentQuery.columns;
1658
1986
  }
1659
1987
  break;
@@ -1676,7 +2004,7 @@ var PgConnection = class {
1676
2004
  if (this.currentQuery?.awaitingDescribe && this.currentQuery.sql !== void 0) {
1677
2005
  this.currentQuery.awaitingDescribe = false;
1678
2006
  if (this.currentQuery.prepKey && this.currentQuery.prepName) {
1679
- this.prepared.set(this.currentQuery.prepKey, {
2007
+ this.setPrepared(this.currentQuery.prepKey, {
1680
2008
  name: this.currentQuery.prepName,
1681
2009
  columns: this.currentQuery.columns
1682
2010
  });
@@ -1760,6 +2088,24 @@ var PgConnection = class {
1760
2088
  const authMessage = `${ctx.clientFirstBare},${ctx.serverFirst},${clientFinalWithoutProof}`;
1761
2089
  return Buffer.from(hmac(serverKey, authMessage)).toString("base64");
1762
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
+ }
1763
2109
  /** 事务:BEGIN → fn(tx) → COMMIT;fn 抛错 → ROLLBACK(回滚失败吞掉,保留原始错误) */
1764
2110
  async transaction(fn) {
1765
2111
  await this.query("BEGIN");
@@ -1791,10 +2137,10 @@ var PgConnection = class {
1791
2137
  resolve: resolve3,
1792
2138
  reject
1793
2139
  };
1794
- this.socket.write(Buffer.from(queryMessage(sql)));
2140
+ this.socket.write(queryMessage(sql));
1795
2141
  } else {
1796
2142
  const sig = `${sql}|${params.length}`;
1797
- let stmtEntry = this.prepared.get(sig);
2143
+ let stmtEntry = this.getPrepared(sig);
1798
2144
  const encoded = encodeParams(params);
1799
2145
  if (!stmtEntry) {
1800
2146
  const name = `wf_s${++this.stmtSeq}`;
@@ -1809,9 +2155,9 @@ var PgConnection = class {
1809
2155
  prepKey: sig,
1810
2156
  prepName: name
1811
2157
  };
1812
- this.socket.write(Buffer.from(parseMessage(name, sql, params.map(() => 0))));
1813
- this.socket.write(Buffer.from(describeMessage("S", name)));
1814
- 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());
1815
2161
  } else {
1816
2162
  this.currentQuery = {
1817
2163
  columns: [...stmtEntry.columns],
@@ -1822,15 +2168,15 @@ var PgConnection = class {
1822
2168
  params: encoded,
1823
2169
  awaitingDescribe: false
1824
2170
  };
1825
- this.socket.write(Buffer.from(bindMessage(stmtEntry.name, encoded)));
1826
- this.socket.write(Buffer.from(executeMessage()));
1827
- this.socket.write(Buffer.from(syncMessage()));
2171
+ this.socket.write(bindMessage(stmtEntry.name, encoded));
2172
+ this.socket.write(executeMessage());
2173
+ this.socket.write(syncMessage());
1828
2174
  }
1829
2175
  }
1830
2176
  });
1831
2177
  }
1832
2178
  send(data) {
1833
- this.socket?.write(Buffer.from(data));
2179
+ this.socket?.write(data);
1834
2180
  }
1835
2181
  notifyIdle() {
1836
2182
  const ws = this.waiters;
@@ -1846,7 +2192,7 @@ var PgConnection = class {
1846
2192
  async close() {
1847
2193
  const sock = this.socket;
1848
2194
  if (this.status === "ready" && sock) {
1849
- sock.write(Buffer.from(terminateMessage()));
2195
+ sock.write(terminateMessage());
1850
2196
  }
1851
2197
  this.status = "closed";
1852
2198
  if (sock) sock.destroy();
@@ -2318,19 +2664,56 @@ var IncompleteError = class extends Error {
2318
2664
  this.name = "IncompleteError";
2319
2665
  }
2320
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
+ }
2321
2672
  function encodeCommand(args) {
2322
- const parts = [`*${args.length}\r
2323
- `];
2324
- for (const arg of args) {
2325
- const s = typeof arg === "string" ? arg : arg instanceof Buffer ? arg.toString() : String(arg);
2326
- parts.push(`$${Buffer.byteLength(s)}\r
2327
- ${s}\r
2328
- `);
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;
2329
2680
  }
2330
- return _encoder.encode(parts.join(""));
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;
2705
+ }
2706
+ return out;
2331
2707
  }
2332
- var _decoder = new TextDecoder();
2333
- 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();
2334
2717
  var RespParser = class {
2335
2718
  buf = new Uint8Array(0);
2336
2719
  off = 0;
@@ -2404,16 +2787,15 @@ var RespParser = class {
2404
2787
  return new RespError(line);
2405
2788
  }
2406
2789
  case ":": {
2407
- const line = this.readLine();
2408
- return parseInt(line, 10);
2790
+ return this.readInt();
2409
2791
  }
2410
2792
  case "$": {
2411
- const len = parseInt(this.readLine(), 10);
2793
+ const len = this.readInt();
2412
2794
  if (len === -1) return null;
2413
- return this.readBulk(len);
2795
+ return this.readBulkBytes(len);
2414
2796
  }
2415
2797
  case "*": {
2416
- const count = parseInt(this.readLine(), 10);
2798
+ const count = this.readInt();
2417
2799
  if (count === -1) return null;
2418
2800
  const items = [];
2419
2801
  for (let i = 0; i < count; i++) items.push(this.parseValue());
@@ -2423,25 +2805,50 @@ var RespParser = class {
2423
2805
  throw new DbError("protocol", `unknown RESP type byte: ${type}`, { code: "RESP" });
2424
2806
  }
2425
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
+ }
2426
2831
  /** 读一行(到 \r\n),返回行内容(不含 type 字节与 \r\n),并推进 off */
2427
2832
  readLine() {
2428
2833
  const idx = indexOfCRLF(this.buf, this.off);
2429
2834
  if (idx === -1) throw new IncompleteError();
2430
- const line = _decoder.decode(this.buf.subarray(this.off, idx));
2835
+ const line = _decoder2.decode(this.buf.subarray(this.off, idx));
2431
2836
  this.off = idx + 2;
2432
2837
  return line;
2433
2838
  }
2434
- /** 读 len 字节的 bulk 内容 + \r\n */
2435
- readBulk(len) {
2839
+ /** 读 len 字节的 bulk 内容 + \r\n(字节中立——不 decode,由调用方决定 string/Buffer) */
2840
+ readBulkBytes(len) {
2436
2841
  if (this.buf.length - this.off < len + 2) throw new IncompleteError();
2437
- const value = _decoder.decode(this.buf.subarray(this.off, this.off + len));
2842
+ const value = this.buf.subarray(this.off, this.off + len);
2438
2843
  this.off += len + 2;
2439
2844
  return value;
2440
2845
  }
2441
2846
  };
2442
2847
  function indexOfCRLF(buf, from = 0) {
2443
- for (let i = from; i < buf.length - 1; i++) {
2444
- 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);
2445
2852
  }
2446
2853
  return -1;
2447
2854
  }
@@ -2454,6 +2861,8 @@ var RedisConnection = class {
2454
2861
  socket = null;
2455
2862
  parser = new RespParser();
2456
2863
  pending = [];
2864
+ /** pending 头指针(避免 shift() O(n)——消费后定期 compact) */
2865
+ pendingHead = 0;
2457
2866
  offlineQueue = [];
2458
2867
  subs = /* @__PURE__ */ new Map();
2459
2868
  psubs = /* @__PURE__ */ new Map();
@@ -2469,7 +2878,8 @@ var RedisConnection = class {
2469
2878
  port: options.port ?? 6379,
2470
2879
  retryDelayMs: options.retryDelayMs ?? 100,
2471
2880
  maxRetries: options.maxRetries ?? 10,
2472
- enableOfflineQueue: options.enableOfflineQueue ?? true
2881
+ enableOfflineQueue: options.enableOfflineQueue ?? true,
2882
+ maxOfflineQueue: options.maxOfflineQueue ?? 5e3
2473
2883
  };
2474
2884
  }
2475
2885
  /** 建立连接并等待 ready。重连失败(超过 maxRetries)抛 ConnectionError。 */
@@ -2519,10 +2929,10 @@ var RedisConnection = class {
2519
2929
  });
2520
2930
  }
2521
2931
  handleDisconnect(err) {
2522
- const queue = this.pending;
2932
+ const queue = this.pending.slice(this.pendingHead);
2523
2933
  this.pending = [];
2524
- const retryErr = err instanceof ConnectionError ? err : err;
2525
- for (const p of queue) p.reject(retryErr);
2934
+ this.pendingHead = 0;
2935
+ for (const p of queue) p.reject(err);
2526
2936
  if (this.closed || this.status === "closed") return;
2527
2937
  this.retries++;
2528
2938
  if (this.opts.maxRetries > 0 && this.retries > this.opts.maxRetries) {
@@ -2540,38 +2950,52 @@ var RedisConnection = class {
2540
2950
  onData(chunk) {
2541
2951
  try {
2542
2952
  const values = this.parser.pushAll(chunk);
2543
- for (const value of values) {
2544
- if (Array.isArray(value) && typeof value[0] === "string" && PUSH_TYPES.has(value[0])) {
2545
- 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));
2546
2957
  continue;
2547
2958
  }
2548
- const p = this.pending.shift();
2959
+ const p = this.pending[this.pendingHead++];
2549
2960
  if (!p) break;
2550
- if (value instanceof RespError) p.reject(value);
2551
- 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;
2552
2967
  }
2553
2968
  } catch (e) {
2554
- const queue = this.pending;
2969
+ const queue = this.pending.slice(this.pendingHead);
2555
2970
  this.pending = [];
2971
+ this.pendingHead = 0;
2556
2972
  for (const q of queue) q.reject(e);
2557
2973
  this.socket?.destroy();
2558
2974
  }
2559
2975
  }
2560
- /** 发送命令并等待响应(单连接严格有序)。未 ready 时入离线队列(enableOfflineQueue)或拒绝。 */
2976
+ /**
2977
+ * 发送命令并等待响应(单连接严格有序)。未 ready 时入离线队列(enableOfflineQueue)或拒绝。
2978
+ * opts.asBuffer=true:响应保留原始字节(Uint8Array)——getBuffer 等二进制场景。
2979
+ */
2561
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;
2562
2983
  if (this.status === "ready" && this.socket) {
2563
- return this.sendNow(name, args);
2984
+ return this.sendNow(name, args, opts?.asBuffer);
2564
2985
  }
2565
2986
  if (this.closed || this.status === "closed" || !this.opts.enableOfflineQueue) {
2566
2987
  return Promise.reject(new ConnectionError("redis: not connected"));
2567
2988
  }
2989
+ if (this.offlineQueue.length >= this.opts.maxOfflineQueue) {
2990
+ return Promise.reject(new ConnectionError(`redis: offline queue full (${this.opts.maxOfflineQueue})`));
2991
+ }
2568
2992
  return new Promise((resolve3, reject) => {
2569
- this.offlineQueue.push({ name, args, resolve: resolve3, reject });
2993
+ this.offlineQueue.push({ name, args, resolve: resolve3, reject, asBuffer: opts?.asBuffer });
2570
2994
  });
2571
2995
  }
2572
- sendNow(name, args) {
2996
+ sendNow(name, args, asBuffer) {
2573
2997
  return new Promise((resolve3, reject) => {
2574
- this.pending.push({ resolve: resolve3, reject });
2998
+ this.pending.push({ resolve: resolve3, reject, asBuffer });
2575
2999
  this.socket.write(encodeCommand([name, ...args]));
2576
3000
  });
2577
3001
  }
@@ -2579,7 +3003,7 @@ var RedisConnection = class {
2579
3003
  const queue = this.offlineQueue;
2580
3004
  this.offlineQueue = [];
2581
3005
  for (const q of queue) {
2582
- 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);
2583
3007
  }
2584
3008
  }
2585
3009
  /** 批量执行:一次 write 发送所有命令字节,响应按序路由(管道) */
@@ -2637,15 +3061,16 @@ var RedisConnection = class {
2637
3061
  clearTimeout(this.reconnectTimer);
2638
3062
  this.reconnectTimer = null;
2639
3063
  }
2640
- const queue = this.pending;
3064
+ const queue = this.pending.slice(this.pendingHead);
2641
3065
  this.pending = [];
3066
+ this.pendingHead = 0;
2642
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"));
2643
3071
  const sock = this.socket;
2644
3072
  this.socket = null;
2645
- if (sock) {
2646
- sock.destroy();
2647
- await new Promise((r) => setTimeout(r, 0));
2648
- }
3073
+ sock?.destroy();
2649
3074
  }
2650
3075
  get connected() {
2651
3076
  return this.status === "ready";
@@ -2664,7 +3089,7 @@ var RedisClient = class _RedisClient {
2664
3089
  await conn.connect();
2665
3090
  return new _RedisClient(conn);
2666
3091
  }
2667
- /** 底层命令透传(RESP 值) */
3092
+ /** 底层命令透传(RESP 值);Buffer 参数字节原样发送 */
2668
3093
  command(name, ...args) {
2669
3094
  return this.conn.command(name, ...args);
2670
3095
  }
@@ -2673,6 +3098,15 @@ var RedisClient = class _RedisClient {
2673
3098
  const v = await this.conn.command("GET", key);
2674
3099
  return v === null ? null : String(v);
2675
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
+ }
2676
3110
  /**
2677
3111
  * SET。ttl 秒可省略;传入即安全生效(内部转 SET key val EX ttl)。
2678
3112
  */
@@ -2809,6 +3243,11 @@ var RedisPool = class _RedisPool {
2809
3243
  await this.ensure();
2810
3244
  return this.next().get(this.k(key));
2811
3245
  }
3246
+ /** 二进制安全读取(原始字节,不解码) */
3247
+ async getBuffer(key) {
3248
+ await this.ensure();
3249
+ return this.next().getBuffer(this.k(key));
3250
+ }
2812
3251
  async set(key, value, ttl) {
2813
3252
  await this.ensure();
2814
3253
  return this.next().set(this.k(key), value, ttl);
@@ -2962,6 +3401,7 @@ export {
2962
3401
  serverError,
2963
3402
  tooManyRequests,
2964
3403
  ui,
3404
+ uiSsr,
2965
3405
  unauthorized,
2966
3406
  unprocessable
2967
3407
  };