@weifuwujs/weifuwu 0.9.0

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.
Files changed (122) hide show
  1. package/README.md +605 -0
  2. package/dist/app.d.ts +122 -0
  3. package/dist/app.d.ts.map +1 -0
  4. package/dist/app.js +484 -0
  5. package/dist/app.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +506 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/compose.d.ts +12 -0
  11. package/dist/compose.d.ts.map +1 -0
  12. package/dist/compose.js +29 -0
  13. package/dist/compose.js.map +1 -0
  14. package/dist/env.d.ts +12 -0
  15. package/dist/env.d.ts.map +1 -0
  16. package/dist/env.js +46 -0
  17. package/dist/env.js.map +1 -0
  18. package/dist/group.d.ts +32 -0
  19. package/dist/group.d.ts.map +1 -0
  20. package/dist/group.js +66 -0
  21. package/dist/group.js.map +1 -0
  22. package/dist/helpers.d.ts +37 -0
  23. package/dist/helpers.d.ts.map +1 -0
  24. package/dist/helpers.js +101 -0
  25. package/dist/helpers.js.map +1 -0
  26. package/dist/index.d.ts +15 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +9 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/ioredis.d.ts +16 -0
  31. package/dist/ioredis.d.ts.map +1 -0
  32. package/dist/ioredis.js +42 -0
  33. package/dist/ioredis.js.map +1 -0
  34. package/dist/methods.d.ts +3 -0
  35. package/dist/methods.d.ts.map +1 -0
  36. package/dist/methods.js +10 -0
  37. package/dist/methods.js.map +1 -0
  38. package/dist/middleware/cors.d.ts +24 -0
  39. package/dist/middleware/cors.d.ts.map +1 -0
  40. package/dist/middleware/cors.js +77 -0
  41. package/dist/middleware/cors.js.map +1 -0
  42. package/dist/middleware/index.d.ts +7 -0
  43. package/dist/middleware/index.d.ts.map +1 -0
  44. package/dist/middleware/index.js +4 -0
  45. package/dist/middleware/index.js.map +1 -0
  46. package/dist/middleware/logger.d.ts +27 -0
  47. package/dist/middleware/logger.d.ts.map +1 -0
  48. package/dist/middleware/logger.js +48 -0
  49. package/dist/middleware/logger.js.map +1 -0
  50. package/dist/middleware/static.d.ts +27 -0
  51. package/dist/middleware/static.d.ts.map +1 -0
  52. package/dist/middleware/static.js +150 -0
  53. package/dist/middleware/static.js.map +1 -0
  54. package/dist/node.d.ts +27 -0
  55. package/dist/node.d.ts.map +1 -0
  56. package/dist/node.js +193 -0
  57. package/dist/node.js.map +1 -0
  58. package/dist/path.d.ts +9 -0
  59. package/dist/path.d.ts.map +1 -0
  60. package/dist/path.js +31 -0
  61. package/dist/path.js.map +1 -0
  62. package/dist/postgres.d.ts +19 -0
  63. package/dist/postgres.d.ts.map +1 -0
  64. package/dist/postgres.js +27 -0
  65. package/dist/postgres.js.map +1 -0
  66. package/dist/react-router.d.ts +31 -0
  67. package/dist/react-router.d.ts.map +1 -0
  68. package/dist/react-router.js +25 -0
  69. package/dist/react-router.js.map +1 -0
  70. package/dist/resources.d.ts +35 -0
  71. package/dist/resources.d.ts.map +1 -0
  72. package/dist/resources.js +54 -0
  73. package/dist/resources.js.map +1 -0
  74. package/dist/router.d.ts +30 -0
  75. package/dist/router.d.ts.map +1 -0
  76. package/dist/router.js +122 -0
  77. package/dist/router.js.map +1 -0
  78. package/dist/types.d.ts +124 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +2 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/vite.d.ts +21 -0
  83. package/dist/vite.d.ts.map +1 -0
  84. package/dist/vite.js +101 -0
  85. package/dist/vite.js.map +1 -0
  86. package/dist/websocket/accept.d.ts +22 -0
  87. package/dist/websocket/accept.d.ts.map +1 -0
  88. package/dist/websocket/accept.js +104 -0
  89. package/dist/websocket/accept.js.map +1 -0
  90. package/dist/websocket/close-code.d.ts +22 -0
  91. package/dist/websocket/close-code.d.ts.map +1 -0
  92. package/dist/websocket/close-code.js +35 -0
  93. package/dist/websocket/close-code.js.map +1 -0
  94. package/dist/websocket/connection.d.ts +71 -0
  95. package/dist/websocket/connection.d.ts.map +1 -0
  96. package/dist/websocket/connection.js +287 -0
  97. package/dist/websocket/connection.js.map +1 -0
  98. package/dist/websocket/index.d.ts +7 -0
  99. package/dist/websocket/index.d.ts.map +1 -0
  100. package/dist/websocket/index.js +4 -0
  101. package/dist/websocket/index.js.map +1 -0
  102. package/dist/websocket/types.d.ts +66 -0
  103. package/dist/websocket/types.d.ts.map +1 -0
  104. package/dist/websocket/types.js +2 -0
  105. package/dist/websocket/types.js.map +1 -0
  106. package/docs/ai.md +175 -0
  107. package/docs/data.md +81 -0
  108. package/docs/migrating.md +116 -0
  109. package/docs/react-router.md +187 -0
  110. package/docs/styling.md +69 -0
  111. package/package.json +144 -0
  112. package/templates/react-router/_gitignore +3 -0
  113. package/templates/react-router/app/app.css +1 -0
  114. package/templates/react-router/app/context.ts +4 -0
  115. package/templates/react-router/app/root.tsx +50 -0
  116. package/templates/react-router/app/routes/home.tsx +17 -0
  117. package/templates/react-router/app/routes.ts +3 -0
  118. package/templates/react-router/app/server.ts +29 -0
  119. package/templates/react-router/package.json +29 -0
  120. package/templates/react-router/react-router.config.ts +5 -0
  121. package/templates/react-router/tsconfig.json +19 -0
  122. package/templates/react-router/vite.config.ts +6 -0
@@ -0,0 +1,27 @@
1
+ import type { Middleware } from '../types.ts';
2
+ export interface ServeStaticOptions {
3
+ /** 目录默认文件,设为 `false` 关闭。默认 `'index.html'` */
4
+ index?: string | false;
5
+ /** 文件不存在时回退到的文件(SPA 常用 `'index.html'`) */
6
+ fallback?: string;
7
+ /** Cache-Control: max-age 秒数 */
8
+ maxAge?: number;
9
+ /** 追加 `immutable` 指令(配合带 hash 的文件名使用) */
10
+ immutable?: boolean;
11
+ /** 是否处理点文件,默认 `'ignore'`(交给后续路由) */
12
+ dotfiles?: 'allow' | 'ignore' | 'deny';
13
+ /** 附加响应头 */
14
+ headers?: Record<string, string>;
15
+ /** 是否生成 ETag,默认 `true` */
16
+ etag?: boolean;
17
+ }
18
+ /**
19
+ * 静态文件中间件。未命中时调用 `next()`,因此可以和 API 路由共存。
20
+ *
21
+ * ```ts
22
+ * app.use(serveStatic('./public'))
23
+ * app.use('/assets', serveStatic('./dist/assets', { maxAge: 31536000, immutable: true }))
24
+ * ```
25
+ */
26
+ export declare function serveStatic(root: string, options?: ServeStaticOptions): Middleware;
27
+ //# sourceMappingURL=static.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/middleware/static.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AA2C7C,MAAM,WAAW,kBAAkB;IACjC,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yCAAyC;IACzC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IACtC,YAAY;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,0BAA0B;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,UAAU,CAkFZ"}
@@ -0,0 +1,150 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { stat } from 'node:fs/promises';
3
+ import { extname, resolve, sep } from 'node:path';
4
+ import { Readable } from 'node:stream';
5
+ const MIME_TYPES = {
6
+ '.aac': 'audio/aac',
7
+ '.avif': 'image/avif',
8
+ '.bin': 'application/octet-stream',
9
+ '.bmp': 'image/bmp',
10
+ '.css': 'text/css; charset=utf-8',
11
+ '.csv': 'text/csv; charset=utf-8',
12
+ '.gif': 'image/gif',
13
+ '.gz': 'application/gzip',
14
+ '.htm': 'text/html; charset=utf-8',
15
+ '.html': 'text/html; charset=utf-8',
16
+ '.ico': 'image/x-icon',
17
+ '.jpeg': 'image/jpeg',
18
+ '.jpg': 'image/jpeg',
19
+ '.js': 'text/javascript; charset=utf-8',
20
+ '.json': 'application/json; charset=utf-8',
21
+ '.map': 'application/json; charset=utf-8',
22
+ '.mjs': 'text/javascript; charset=utf-8',
23
+ '.mp3': 'audio/mpeg',
24
+ '.mp4': 'video/mp4',
25
+ '.oga': 'audio/ogg',
26
+ '.ogg': 'audio/ogg',
27
+ '.ogv': 'video/ogg',
28
+ '.otf': 'font/otf',
29
+ '.pdf': 'application/pdf',
30
+ '.png': 'image/png',
31
+ '.svg': 'image/svg+xml',
32
+ '.tar': 'application/x-tar',
33
+ '.tiff': 'image/tiff',
34
+ '.ttf': 'font/ttf',
35
+ '.txt': 'text/plain; charset=utf-8',
36
+ '.wasm': 'application/wasm',
37
+ '.wav': 'audio/wav',
38
+ '.webm': 'video/webm',
39
+ '.webp': 'image/webp',
40
+ '.woff': 'font/woff',
41
+ '.woff2': 'font/woff2',
42
+ '.xml': 'application/xml; charset=utf-8',
43
+ '.zip': 'application/zip',
44
+ };
45
+ /**
46
+ * 静态文件中间件。未命中时调用 `next()`,因此可以和 API 路由共存。
47
+ *
48
+ * ```ts
49
+ * app.use(serveStatic('./public'))
50
+ * app.use('/assets', serveStatic('./dist/assets', { maxAge: 31536000, immutable: true }))
51
+ * ```
52
+ */
53
+ export function serveStatic(root, options = {}) {
54
+ const rootDir = resolve(root);
55
+ const indexFile = options.index === undefined ? 'index.html' : options.index;
56
+ const etagEnabled = options.etag !== false;
57
+ return async (req, ctx, next) => {
58
+ if (req.method !== 'GET' && req.method !== 'HEAD')
59
+ return next(req, ctx);
60
+ let pathname;
61
+ try {
62
+ pathname = new URL(req.url).pathname;
63
+ }
64
+ catch {
65
+ return next(req, ctx);
66
+ }
67
+ const decoded = safeDecode(pathname);
68
+ if (decoded === null)
69
+ return next(req, ctx);
70
+ let filePath = resolve(rootDir, `.${decoded}`);
71
+ if (!isInside(rootDir, filePath))
72
+ return next(req, ctx);
73
+ let info = await statOrNull(filePath);
74
+ if (info?.isDirectory() && indexFile) {
75
+ filePath = resolve(filePath, indexFile);
76
+ if (!isInside(rootDir, filePath))
77
+ return next(req, ctx);
78
+ info = await statOrNull(filePath);
79
+ }
80
+ if (!info?.isFile()) {
81
+ if (!options.fallback)
82
+ return next(req, ctx);
83
+ const fallbackPath = resolve(rootDir, `.${ensureLeadingSlash(options.fallback)}`);
84
+ if (!isInside(rootDir, fallbackPath))
85
+ return next(req, ctx);
86
+ const fallbackInfo = await statOrNull(fallbackPath);
87
+ if (!fallbackInfo?.isFile())
88
+ return next(req, ctx);
89
+ filePath = fallbackPath;
90
+ info = fallbackInfo;
91
+ }
92
+ const relative = filePath.slice(rootDir.length + 1);
93
+ if (options.dotfiles !== 'allow' &&
94
+ relative.split(sep).some((part) => part.startsWith('.'))) {
95
+ if (options.dotfiles === 'deny') {
96
+ return new Response('Forbidden', { status: 403 });
97
+ }
98
+ return next(req, ctx);
99
+ }
100
+ const headers = new Headers(options.headers);
101
+ if (!headers.has('content-type')) {
102
+ const contentType = MIME_TYPES[extname(filePath).toLowerCase()];
103
+ if (contentType)
104
+ headers.set('content-type', contentType);
105
+ }
106
+ headers.set('content-length', String(info.size));
107
+ headers.set('last-modified', info.mtime.toUTCString());
108
+ if (options.maxAge !== undefined || options.immutable) {
109
+ const directives = ['public', `max-age=${options.maxAge ?? 0}`];
110
+ if (options.immutable)
111
+ directives.push('immutable');
112
+ headers.set('cache-control', directives.join(', '));
113
+ }
114
+ if (etagEnabled) {
115
+ const etag = `W/"${info.size.toString(16)}-${Math.floor(info.mtimeMs).toString(16)}"`;
116
+ headers.set('etag', etag);
117
+ if (req.headers.get('if-none-match') === etag) {
118
+ const notModified = new Headers(headers);
119
+ notModified.delete('content-length');
120
+ return new Response(null, { status: 304, headers: notModified });
121
+ }
122
+ }
123
+ const stream = createReadStream(filePath);
124
+ return new Response(Readable.toWeb(stream), { status: 200, headers });
125
+ };
126
+ }
127
+ function safeDecode(value) {
128
+ try {
129
+ const decoded = decodeURIComponent(value);
130
+ return decoded.includes('\0') ? null : decoded;
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ }
136
+ function isInside(root, file) {
137
+ return file === root || file.startsWith(root + sep);
138
+ }
139
+ async function statOrNull(path) {
140
+ try {
141
+ return await stat(path);
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ }
147
+ function ensureLeadingSlash(path) {
148
+ return path.startsWith('/') ? path : `/${path}`;
149
+ }
150
+ //# sourceMappingURL=static.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/middleware/static.ts"],"names":[],"mappings":"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"}
package/dist/node.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { type IncomingMessage, type RequestListener, type Server } from 'node:http';
2
+ import { type Duplex } from 'node:stream';
3
+ /** 任何实现了 `fetch(Request)` 的对象都可以跑在 Node 上 */
4
+ export interface FetchApp {
5
+ fetch(request: Request): Promise<Response>;
6
+ }
7
+ /** 额外实现了 upgrade 的应用(如 `App`),支持 WebSocket */
8
+ export interface UpgradeApp extends FetchApp {
9
+ upgrade(request: Request, socket: Duplex, head?: Uint8Array, rawRequest?: IncomingMessage): Promise<Response | undefined>;
10
+ }
11
+ export interface NodeHandlerOptions {
12
+ /** 信任 `X-Forwarded-Proto` / `X-Forwarded-Host`(反向代理场景) */
13
+ trustProxy?: boolean;
14
+ }
15
+ export interface ServeOptions extends NodeHandlerOptions {
16
+ port?: number;
17
+ hostname?: string;
18
+ }
19
+ /** 把 Node 的 `(req, res)` 适配为 Fetch 处理函数 */
20
+ export declare function toNodeHandler(app: FetchApp, options?: NodeHandlerOptions): RequestListener;
21
+ /** 把 Node 的 `(req, socket, head)` 适配为 WebSocket 升级处理函数 */
22
+ export declare function toUpgradeHandler(app: UpgradeApp, options?: NodeHandlerOptions): (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
23
+ /** 把 `request` + `upgrade` 监听器挂到已有 server 上 */
24
+ export declare function attachToServer(app: Partial<UpgradeApp> & FetchApp, server: Server, options?: NodeHandlerOptions): Server;
25
+ /** 创建 Node HTTP Server 并开始监听(自动挂载 WebSocket upgrade) */
26
+ export declare function serve(app: FetchApp, options?: ServeOptions): Server;
27
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,MAAM,EAEZ,MAAM,WAAW,CAAA;AAClB,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AAInD,6CAA6C;AAC7C,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C;AAED,8CAA8C;AAC9C,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,OAAO,CACL,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,UAAU,EACjB,UAAU,CAAC,EAAE,eAAe,GAC3B,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,YAAa,SAAQ,kBAAkB;IACtD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAaD,2CAA2C;AAC3C,wBAAgB,aAAa,CAC3B,GAAG,EAAE,QAAQ,EACb,OAAO,GAAE,kBAAuB,GAC/B,eAAe,CAIjB;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,kBAAuB,GAC/B,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAI9D;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,QAAQ,EACnC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAMR;AAED,wDAAwD;AACxD,wBAAgB,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAOvE"}
package/dist/node.js ADDED
@@ -0,0 +1,193 @@
1
+ import { createServer, } from 'node:http';
2
+ import { Readable } from 'node:stream';
3
+ import { getStatusText } from "./helpers.js";
4
+ const HOP_BY_HOP_HEADERS = new Set([
5
+ 'connection',
6
+ 'keep-alive',
7
+ 'proxy-authenticate',
8
+ 'proxy-authorization',
9
+ 'te',
10
+ 'trailer',
11
+ 'transfer-encoding',
12
+ 'upgrade',
13
+ ]);
14
+ /** 把 Node 的 `(req, res)` 适配为 Fetch 处理函数 */
15
+ export function toNodeHandler(app, options = {}) {
16
+ return (req, res) => {
17
+ void handleRequest(app, req, res, options);
18
+ };
19
+ }
20
+ /** 把 Node 的 `(req, socket, head)` 适配为 WebSocket 升级处理函数 */
21
+ export function toUpgradeHandler(app, options = {}) {
22
+ return (req, socket, head) => {
23
+ void handleUpgrade(app, req, socket, head, options);
24
+ };
25
+ }
26
+ /** 把 `request` + `upgrade` 监听器挂到已有 server 上 */
27
+ export function attachToServer(app, server, options = {}) {
28
+ server.on('request', toNodeHandler(app, options));
29
+ if (typeof app.upgrade === 'function') {
30
+ server.on('upgrade', toUpgradeHandler(app, options));
31
+ }
32
+ return server;
33
+ }
34
+ /** 创建 Node HTTP Server 并开始监听(自动挂载 WebSocket upgrade) */
35
+ export function serve(app, options = {}) {
36
+ const server = createServer();
37
+ attachToServer(app, server, options);
38
+ const { port = 3000, hostname } = options;
39
+ if (hostname)
40
+ server.listen(port, hostname);
41
+ else
42
+ server.listen(port);
43
+ return server;
44
+ }
45
+ async function handleRequest(app, req, res, options) {
46
+ let response;
47
+ try {
48
+ response = await app.fetch(toRequest(req, options));
49
+ }
50
+ catch (error) {
51
+ console.error(error);
52
+ response = new Response('Internal Server Error', { status: 500 });
53
+ }
54
+ await sendResponse(res, response, req.method ?? 'GET');
55
+ }
56
+ async function handleUpgrade(app, req, socket, head, options) {
57
+ let response;
58
+ try {
59
+ const request = toRequest(req, options, true);
60
+ const result = await app.upgrade(request, socket, head, req);
61
+ if (!result)
62
+ return; // 已完成升级,socket 由连接接管
63
+ response = result;
64
+ }
65
+ catch (error) {
66
+ console.error(error);
67
+ response = new Response('Internal Server Error', { status: 500 });
68
+ }
69
+ await writeRawResponse(socket, response);
70
+ }
71
+ /** 在原始 socket 上写回一个完整的 HTTP 响应(升级被拒绝时使用) */
72
+ async function writeRawResponse(socket, response) {
73
+ if (socket.destroyed)
74
+ return;
75
+ const body = response.body ? Buffer.from(await response.arrayBuffer()) : null;
76
+ const headers = new Headers(response.headers);
77
+ headers.set('connection', 'close');
78
+ if (body)
79
+ headers.set('content-length', String(body.length));
80
+ const statusText = response.statusText || getStatusText(response.status);
81
+ const lines = [`HTTP/1.1 ${response.status} ${statusText}`.trimEnd()];
82
+ for (const [key, value] of headers) {
83
+ if (key.toLowerCase() === 'set-cookie')
84
+ continue;
85
+ lines.push(`${key}: ${value}`);
86
+ }
87
+ for (const cookie of headers.getSetCookie?.() ?? []) {
88
+ lines.push(`set-cookie: ${cookie}`);
89
+ }
90
+ socket.write(`${lines.join('\r\n')}\r\n\r\n`);
91
+ if (body)
92
+ socket.write(body);
93
+ socket.end();
94
+ }
95
+ /** IncomingMessage -> Request */
96
+ function toRequest(req, options, upgrade = false) {
97
+ const headers = new Headers();
98
+ for (const [key, value] of Object.entries(req.headers)) {
99
+ if (value === undefined)
100
+ continue;
101
+ // 升级请求需要保留 Upgrade / Connection 头
102
+ if (!upgrade && HOP_BY_HOP_HEADERS.has(key.toLowerCase()))
103
+ continue;
104
+ if (Array.isArray(value)) {
105
+ for (const item of value)
106
+ headers.append(key, item);
107
+ }
108
+ else {
109
+ headers.set(key, value);
110
+ }
111
+ }
112
+ const forwardedProto = options.trustProxy
113
+ ? headers.get('x-forwarded-proto')?.split(',')[0]?.trim()
114
+ : undefined;
115
+ const protocol = forwardedProto ||
116
+ (req.socket.encrypted ? 'https' : 'http');
117
+ const forwardedHost = options.trustProxy
118
+ ? headers.get('x-forwarded-host')?.split(',')[0]?.trim()
119
+ : undefined;
120
+ const host = forwardedHost ?? headers.get('host') ?? 'localhost';
121
+ let url;
122
+ try {
123
+ url = new URL(req.url ?? '/', `${protocol}://${host}`);
124
+ }
125
+ catch {
126
+ url = new URL(req.url ?? '/', 'http://localhost');
127
+ }
128
+ const method = req.method ?? 'GET';
129
+ const init = { method, headers };
130
+ if (!upgrade && method !== 'GET' && method !== 'HEAD') {
131
+ init.body = Readable.toWeb(req);
132
+ init.duplex = 'half';
133
+ }
134
+ else if (!upgrade) {
135
+ // 消费掉可能存在的请求体,避免 keep-alive 连接被污染
136
+ req.resume();
137
+ }
138
+ return new Request(url, init);
139
+ }
140
+ /** Response -> ServerResponse */
141
+ async function sendResponse(res, response, method) {
142
+ if (res.writableEnded || res.destroyed)
143
+ return;
144
+ res.statusCode = response.status;
145
+ if (response.statusText) {
146
+ try {
147
+ res.statusMessage = response.statusText;
148
+ }
149
+ catch {
150
+ // 非法的 reason phrase,忽略并使用 Node 默认值
151
+ }
152
+ }
153
+ const setCookies = response.headers.getSetCookie?.() ?? [];
154
+ try {
155
+ for (const [key, value] of response.headers) {
156
+ const lower = key.toLowerCase();
157
+ if (lower === 'set-cookie' && setCookies.length > 0)
158
+ continue;
159
+ if (HOP_BY_HOP_HEADERS.has(lower))
160
+ continue;
161
+ res.setHeader(key, value);
162
+ }
163
+ if (setCookies.length > 0)
164
+ res.setHeader('set-cookie', setCookies);
165
+ }
166
+ catch (error) {
167
+ console.error(error);
168
+ if (!res.headersSent) {
169
+ res.statusCode = 500;
170
+ res.end('Internal Server Error');
171
+ }
172
+ else {
173
+ res.destroy();
174
+ }
175
+ return;
176
+ }
177
+ const canHaveBody = method !== 'HEAD' && response.status !== 204 && response.status !== 304;
178
+ if (!canHaveBody || !response.body) {
179
+ await response.body?.cancel().catch(() => { });
180
+ res.end();
181
+ return;
182
+ }
183
+ const body = Readable.fromWeb(response.body);
184
+ res.on('close', () => {
185
+ if (!res.writableFinished)
186
+ body.destroy();
187
+ });
188
+ body.on('error', (error) => {
189
+ res.destroy(error instanceof Error ? error : undefined);
190
+ });
191
+ body.pipe(res);
192
+ }
193
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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"}
package/dist/path.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /** 把路径拆成段,忽略空段(因此尾斜杠 / 连续斜杠都会被归一化) */
2
+ export declare function splitPath(path: string): string[];
3
+ /** 归一化 `app.use` 的前缀:去掉尾部的 `/` 与 `/*` */
4
+ export declare function normalizePrefix(prefix: string): string;
5
+ /** 判断 `pathname` 是否落在前缀 `prefix` 下(按路径段边界匹配) */
6
+ export declare function matchesPrefix(prefix: string, pathname: string): boolean;
7
+ /** 拼接 group / 路由前缀 */
8
+ export declare function joinPaths(prefix: string, path: string): string;
9
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhD;AAED,yCAAyC;AACzC,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGvE;AAED,sBAAsB;AACtB,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9D"}
package/dist/path.js ADDED
@@ -0,0 +1,31 @@
1
+ /** 把路径拆成段,忽略空段(因此尾斜杠 / 连续斜杠都会被归一化) */
2
+ export function splitPath(path) {
3
+ return path.split('/').filter((segment) => segment.length > 0);
4
+ }
5
+ /** 归一化 `app.use` 的前缀:去掉尾部的 `/` 与 `/*` */
6
+ export function normalizePrefix(prefix) {
7
+ if (!prefix.startsWith('/')) {
8
+ throw new Error(`Prefix must start with "/": ${prefix}`);
9
+ }
10
+ if (prefix === '/' || prefix === '/*')
11
+ return '/';
12
+ let normalized = prefix.replace(/\/+$/, '');
13
+ if (normalized.endsWith('/*'))
14
+ normalized = normalized.slice(0, -2);
15
+ return normalized === '' ? '/' : normalized;
16
+ }
17
+ /** 判断 `pathname` 是否落在前缀 `prefix` 下(按路径段边界匹配) */
18
+ export function matchesPrefix(prefix, pathname) {
19
+ if (prefix === '/' || prefix === '')
20
+ return true;
21
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
22
+ }
23
+ /** 拼接 group / 路由前缀 */
24
+ export function joinPaths(prefix, path) {
25
+ if (path === '')
26
+ return prefix === '' ? '/' : prefix;
27
+ const left = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
28
+ const right = path.startsWith('/') ? path : `/${path}`;
29
+ return `${left}${right}`;
30
+ }
31
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AAChE,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,EAAE,CAAC,CAAA;IAC1D,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,GAAG,CAAA;IACjD,IAAI,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC3C,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACnE,OAAO,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAA;AAC7C,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,QAAgB;IAC5D,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAChD,OAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAA;AACjE,CAAC;AAED,sBAAsB;AACtB,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,IAAY;IACpD,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAA;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAChE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAA;IACtD,OAAO,GAAG,IAAI,GAAG,KAAK,EAAE,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type postgresJs from 'postgres';
2
+ import { type Resource } from './resources.ts';
3
+ /** postgres.js 的客户端类型(`ctx.sql`) */
4
+ export type Sql = postgresJs.Sql;
5
+ /** postgres.js 的 options(`Parameters<typeof postgresJs>[1]`,其泛型无默认值) */
6
+ export type PostgresOptions = NonNullable<Parameters<typeof postgresJs>[1]>;
7
+ /**
8
+ * 注入 `ctx.sql`(postgres.js 客户端)的中间件。
9
+ *
10
+ * - 首次请求时懒加载 `postgres` 并建连(池由 postgres.js 维护)
11
+ * - `app.close()` / CLI SIGTERM 时调用 `sql.end()`
12
+ * - 默认连接串:`process.env.DATABASE_URL`
13
+ *
14
+ * ```ts
15
+ * app.use(postgres()) // ctx.sql: Sql
16
+ * ```
17
+ */
18
+ export declare function postgres(urlOrOptions?: string | PostgresOptions, options?: PostgresOptions): Resource<'sql', Sql>;
19
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,UAAU,CAAA;AACtC,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAExD,oCAAoC;AACpC,MAAM,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAA;AAEhC,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE3E;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CACtB,YAAY,CAAC,EAAE,MAAM,GAAG,eAAe,EACvC,OAAO,CAAC,EAAE,eAAe,GACxB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAkBtB"}
@@ -0,0 +1,27 @@
1
+ import { resource } from "./resources.js";
2
+ /**
3
+ * 注入 `ctx.sql`(postgres.js 客户端)的中间件。
4
+ *
5
+ * - 首次请求时懒加载 `postgres` 并建连(池由 postgres.js 维护)
6
+ * - `app.close()` / CLI SIGTERM 时调用 `sql.end()`
7
+ * - 默认连接串:`process.env.DATABASE_URL`
8
+ *
9
+ * ```ts
10
+ * app.use(postgres()) // ctx.sql: Sql
11
+ * ```
12
+ */
13
+ export function postgres(urlOrOptions, options) {
14
+ return resource('sql', async () => {
15
+ const { default: createPostgres } = (await import('postgres'));
16
+ if (typeof urlOrOptions === 'string') {
17
+ return createPostgres(urlOrOptions, options);
18
+ }
19
+ const url = process.env.DATABASE_URL;
20
+ if (url)
21
+ return createPostgres(url, urlOrOptions);
22
+ return createPostgres(urlOrOptions);
23
+ }, {
24
+ close: (sql) => sql.end({ timeout: 5 }).then(() => undefined),
25
+ });
26
+ }
27
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAiB,MAAM,gBAAgB,CAAA;AAQxD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CACtB,YAAuC,EACvC,OAAyB;IAEzB,OAAO,QAAQ,CACb,KAAK,EACL,KAAK,IAAI,EAAE;QACT,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,CAE5D,CAAA;QACD,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrC,OAAO,cAAc,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;QAC9C,CAAC;QACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;QACpC,IAAI,GAAG;YAAE,OAAO,cAAc,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACjD,OAAO,cAAc,CAAC,YAAY,CAAC,CAAA;IACrC,CAAC,EACD;QACE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;KAC9D,CACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { RouterContextProvider, ServerBuild } from 'react-router';
2
+ import type { Context, Handler } from './types.ts';
3
+ export interface ReactRouterOptions<C extends Context = Context> {
4
+ /**
5
+ * RR server build。默认从 Vite 虚拟模块 `virtual:react-router/server-build`
6
+ * 加载:dev 由 Vite 解析,prod 由 `react-router build` 构建时替换。
7
+ */
8
+ build?: ServerBuild | (() => ServerBuild | Promise<ServerBuild>);
9
+ /** 运行模式,默认 `process.env.NODE_ENV` */
10
+ mode?: string;
11
+ /**
12
+ * 把 weifuwu 的上下文桥接为 RR 的 load context。
13
+ * 在 loader/action 中通过 `context.get(...)` 读取。
14
+ */
15
+ getLoadContext?: (request: Request, ctx: C) => RouterContextProvider | void | Promise<RouterContextProvider | void>;
16
+ }
17
+ /**
18
+ * 把 React Router 挂载为 weifuwu 的兜底处理器。
19
+ *
20
+ * ```ts
21
+ * app.notFound(reactRouter({
22
+ * getLoadContext(request, ctx) {
23
+ * const context = new RouterContextProvider()
24
+ * context.set(dbContext, ctx.db)
25
+ * return context
26
+ * },
27
+ * }))
28
+ * ```
29
+ */
30
+ export declare function reactRouter<C extends Context = Context>(options?: ReactRouterOptions<C>): Handler<C>;
31
+ //# sourceMappingURL=react-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAElD,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;IAC7D;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;IAChE,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,cAAc,CAAC,EAAE,CACf,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,CAAC,KACH,qBAAqB,GAAG,IAAI,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;CAC1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EACrD,OAAO,GAAE,kBAAkB,CAAC,CAAC,CAAM,GAClC,OAAO,CAAC,CAAC,CAAC,CAUZ"}
@@ -0,0 +1,25 @@
1
+ import { createRequestHandler } from 'react-router';
2
+ /**
3
+ * 把 React Router 挂载为 weifuwu 的兜底处理器。
4
+ *
5
+ * ```ts
6
+ * app.notFound(reactRouter({
7
+ * getLoadContext(request, ctx) {
8
+ * const context = new RouterContextProvider()
9
+ * context.set(dbContext, ctx.db)
10
+ * return context
11
+ * },
12
+ * }))
13
+ * ```
14
+ */
15
+ export function reactRouter(options = {}) {
16
+ const build = options.build ?? (() => import('virtual:react-router/server-build'));
17
+ const handle = createRequestHandler(build, options.mode ?? process.env.NODE_ENV);
18
+ return async (request, ctx) => {
19
+ if (!options.getLoadContext)
20
+ return handle(request);
21
+ const loadContext = await options.getLoadContext(request, ctx);
22
+ return loadContext ? handle(request, loadContext) : handle(request);
23
+ };
24
+ }
25
+ //# sourceMappingURL=react-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-router.js","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAsBnD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CACzB,UAAiC,EAAE;IAEnC,MAAM,KAAK,GACT,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mCAAmC,CAAC,CAAC,CAAA;IACtE,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;QAC5B,IAAI,CAAC,OAAO,CAAC,cAAc;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAA;QACnD,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QAC9D,OAAO,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACrE,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { Context, Middleware } from './types.ts';
2
+ /** `App.use()` 识别该 symbol 上的 dispose 并注册到 `app.close()`(内部协议) */
3
+ export declare const RESOURCE_DISPOSE: unique symbol;
4
+ export interface ResourceOptions<V> {
5
+ /** 进程关闭时清理(`app.close()` / CLI 的 SIGINT、SIGTERM 触发) */
6
+ close?: (value: V) => void | Promise<void>;
7
+ }
8
+ /**
9
+ * `resource()` 返回的中间件:
10
+ * - 注入 `ctx[key]`(类型精确到 `V`)
11
+ * - 懒初始化:首个请求时才 `create()`
12
+ * - 单例 + single-flight:并发首请求只创建一次
13
+ * - 失败不缓存:下次请求重试
14
+ * - `app.close()` 时按 LIFO 调用 `options.close`
15
+ */
16
+ export interface Resource<K extends string = string, V = unknown, C extends Context = Context> extends Middleware<C, C & Record<K, V>> {
17
+ readonly key: K;
18
+ /** 已创建则返回实例,否则 `undefined`(测试 / 工具用) */
19
+ peek(): V | undefined;
20
+ /** 立即创建(预热用),失败不缓存 */
21
+ load(): Promise<V>;
22
+ /** 手动关闭(`app.close()` 之外);幂等 */
23
+ close(): Promise<void>;
24
+ }
25
+ /**
26
+ * 懒初始化的共享资源中间件。
27
+ *
28
+ * ```ts
29
+ * const db = resource('db', () => openDatabase(), { close: (db) => db.end() })
30
+ * const app = createApp().use(db)
31
+ * app.get('/', (req, ctx) => json(ctx.db.query()))
32
+ * ```
33
+ */
34
+ export declare function resource<const K extends string, V, C extends Context = Context>(key: K, create: () => V | Promise<V>, options?: ResourceOptions<V>): Resource<K, V, C>;
35
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAW,UAAU,EAAE,MAAM,YAAY,CAAA;AAE9D,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,EAAE,OAAO,MAA2C,CAAA;AAEjF,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,uDAAuD;IACvD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ,CACvB,CAAC,SAAS,MAAM,GAAG,MAAM,EACzB,CAAC,GAAG,OAAO,EACX,CAAC,SAAS,OAAO,GAAG,OAAO,CAC3B,SAAQ,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAA;IACf,wCAAwC;IACxC,IAAI,IAAI,CAAC,GAAG,SAAS,CAAA;IACrB,sBAAsB;IACtB,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,CAAA;IAClB,gCAAgC;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,KAAK,CAAC,CAAC,SAAS,MAAM,EACtB,CAAC,EACD,CAAC,SAAS,OAAO,GAAG,OAAO,EAE3B,GAAG,EAAE,CAAC,EACN,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,eAAe,CAAC,CAAC,CAAM,GAC/B,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAoDnB"}