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/README.md +222 -18
- package/dist/client/app.d.ts +3 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +337 -87
- package/dist/client/reactive.d.ts +6 -0
- package/dist/client/render.d.ts +11 -0
- package/dist/client/route-match.d.ts +22 -0
- package/dist/client/types.d.ts +17 -0
- package/dist/client/vnode.d.ts +30 -1
- package/dist/components/index.js +56 -18
- package/dist/db/postgres/connection.d.ts +5 -1
- package/dist/db/postgres/protocol.d.ts +4 -1
- package/dist/db/redis/client.d.ts +7 -2
- package/dist/db/redis/connection.d.ts +11 -2
- package/dist/db/redis/pool.d.ts +2 -0
- package/dist/db/redis/resp.d.ts +12 -5
- package/dist/dev/index.js +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +529 -89
- package/dist/ui/html-safe.d.ts +11 -0
- package/dist/ui/index.d.ts +11 -0
- package/dist/ui/ssr-page.d.ts +41 -0
- package/dist/ui/ssr.d.ts +35 -0
- package/package.json +6 -3
package/dist/components/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
46
|
-
|
|
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 发送所有命令字节,响应按序路由(管道) */
|
package/dist/db/redis/pool.d.ts
CHANGED
|
@@ -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>;
|
package/dist/db/redis/resp.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
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';
|