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.
@@ -1,4 +1,8 @@
1
1
  // src/client/vnode.ts
2
+ var ASYNC_MARK = "__wfAsyncComponent";
3
+ function isAsyncComponent(type) {
4
+ return typeof type === "function" && type?.[ASYNC_MARK] === true;
5
+ }
2
6
  var Fragment = /* @__PURE__ */ Symbol("Fragment");
3
7
  var Portal = /* @__PURE__ */ Symbol("Portal");
4
8
  function h(type, props, ...children) {
@@ -467,10 +471,11 @@ function renderValue(v, ctx) {
467
471
  }
468
472
  if (vnode.type === Fragment) {
469
473
  const frag = document.createDocumentFragment();
470
- forEach(vnode.props?.children, (child) => {
474
+ const children = vnode.props?.children == null ? [] : Array.isArray(vnode.props.children) ? vnode.props.children : [vnode.props.children];
475
+ for (const child of children) {
471
476
  const node = renderValue(child, ctx);
472
477
  if (node != null) frag.appendChild(node);
473
- });
478
+ }
474
479
  return frag;
475
480
  }
476
481
  if (typeof vnode.type === "function") {
@@ -513,6 +518,54 @@ function renderValue(v, ctx) {
513
518
  if (typeof vnode.props?.ref === "function") vnode.props.ref(el);
514
519
  return el;
515
520
  }
521
+ var asyncFactoryCache = /* @__PURE__ */ new WeakMap();
522
+ function startAsyncFactory(Comp, ctx) {
523
+ const existing = asyncFactoryCache.get(Comp);
524
+ if (existing) return existing;
525
+ const entry = { promise: null };
526
+ entry.promise = Promise.resolve().then(() => Comp(ctx)).then((def) => {
527
+ if (typeof def !== "function") {
528
+ throw new Error(
529
+ `asyncComponent factory <${Comp.name || "anonymous"}> must return a Component (initProps, ctx) => (props) => VNode.`
530
+ );
531
+ }
532
+ entry.resolved = def;
533
+ return def;
534
+ });
535
+ asyncFactoryCache.set(Comp, entry);
536
+ return entry;
537
+ }
538
+ function resolveAsyncFactorySync(Comp) {
539
+ return asyncFactoryCache.get(Comp)?.resolved;
540
+ }
541
+ function scheduleFullReRender(ctx) {
542
+ const ui = ctx.ui;
543
+ if (ui && typeof ui.render === "function") ui.render(["_wf_root"]);
544
+ }
545
+ function mountComponent(Comp, props, vnode, ctx) {
546
+ let def;
547
+ if (isAsyncComponent(Comp)) {
548
+ def = resolveAsyncFactorySync(Comp);
549
+ if (!def) {
550
+ void startAsyncFactory(Comp, ctx).promise.then(
551
+ () => scheduleFullReRender(ctx),
552
+ () => {
553
+ }
554
+ );
555
+ return null;
556
+ }
557
+ } else {
558
+ def = Comp;
559
+ }
560
+ let childVNode = def(props, ctx);
561
+ if (typeof childVNode !== "function") {
562
+ throw new Error(
563
+ `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
564
+ );
565
+ }
566
+ vnode._render = childVNode;
567
+ return childVNode(props);
568
+ }
516
569
  function renderComponent(Comp, props, vnode, ctx) {
517
570
  ;
518
571
  ctx.ui = ctx.ui ?? {};
@@ -527,14 +580,7 @@ function renderComponent(Comp, props, vnode, ctx) {
527
580
  vnode._ctxVersion = childCtx.ui._ctxVersion ?? 0;
528
581
  let childVNode;
529
582
  try {
530
- childVNode = Comp(props, childCtx);
531
- if (typeof childVNode !== "function") {
532
- throw new Error(
533
- `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
534
- );
535
- }
536
- vnode._render = childVNode;
537
- childVNode = childVNode(props);
583
+ childVNode = mountComponent(Comp, props, vnode, childCtx);
538
584
  } catch (e) {
539
585
  const errHandler = ctx.ui?._errorHandler;
540
586
  if (errHandler) {
@@ -591,14 +637,6 @@ function renderPortal(vnode, ctx) {
591
637
  if (node != null) sub.appendChild(node);
592
638
  }
593
639
  }
594
- function forEach(children, fn) {
595
- if (children == null) return;
596
- if (Array.isArray(children)) {
597
- children.forEach(fn);
598
- return;
599
- }
600
- fn(children);
601
- }
602
640
  function flattenChildren(children) {
603
641
  if (children == null) return [];
604
642
  if (!Array.isArray(children)) return [children];
@@ -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
  }
@@ -0,0 +1,23 @@
1
+ // src/dev/index.ts
2
+ import { registerHooks } from "node:module";
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { transformSync } from "esbuild";
6
+ registerHooks({
7
+ load(url, context, next) {
8
+ if (url.startsWith("file:") && (url.endsWith(".ts") || url.endsWith(".tsx"))) {
9
+ const file = fileURLToPath(url);
10
+ const code = readFileSync(file, "utf-8");
11
+ const result = transformSync(code, {
12
+ loader: url.endsWith(".tsx") ? "tsx" : "ts",
13
+ jsx: "automatic",
14
+ jsxImportSource: "weifuwu/client",
15
+ format: "esm",
16
+ sourcemap: "inline",
17
+ target: "node22"
18
+ });
19
+ return { format: "module", source: result.code, shortCircuit: true };
20
+ }
21
+ return next(url, context);
22
+ }
23
+ });
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export type { CORSOptions } from './middleware/cors.ts';
12
12
  export { serveStatic } from './middleware/static.ts';
13
13
  export type { ServeStaticOptions } from './middleware/static.ts';
14
14
  export { ui } from './ui/index.ts';
15
+ export { uiSsr } from './ui/ssr-page.ts';
16
+ export type { UiSsrOptions } from './ui/ssr-page.ts';
15
17
  export type { GraphQLOptions, GraphQLHandler } from './graphql.ts';
16
18
  export { postgres, MIGRATIONS_TABLE } from './postgres/index.ts';
17
19
  export type { PostgresOptions, PostgresClient, PostgresInjected } from './postgres/types.ts';