waku 1.0.0-beta.0 → 1.0.0-beta.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 +1 -3
- package/dist/adapters/cloudflare.js +7 -1
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/netlify-build-enhancer.js +4 -4
- package/dist/adapters/netlify-build-enhancer.js.map +1 -1
- package/dist/adapters/vercel-build-enhancer.js +3 -2
- package/dist/adapters/vercel-build-enhancer.js.map +1 -1
- package/dist/lib/utils/path.js +4 -3
- package/dist/lib/utils/path.js.map +1 -1
- package/dist/lib/utils/prefetch.d.ts +23 -0
- package/dist/lib/utils/prefetch.js +49 -0
- package/dist/lib/utils/prefetch.js.map +1 -0
- package/dist/lib/utils/react-debug-channel.d.ts +1 -3
- package/dist/lib/utils/react-debug-channel.js +6 -7
- package/dist/lib/utils/react-debug-channel.js.map +1 -1
- package/dist/lib/utils/request.js +36 -0
- package/dist/lib/utils/request.js.map +1 -1
- package/dist/lib/utils/ssr.js +2 -32
- package/dist/lib/utils/ssr.js.map +1 -1
- package/dist/lib/vite-entries/entry.build.js +3 -6
- package/dist/lib/vite-entries/entry.build.js.map +1 -1
- package/dist/lib/vite-plugins/combined-plugins.js +20 -2
- package/dist/lib/vite-plugins/combined-plugins.js.map +1 -1
- package/dist/lib/vite-plugins/static-build.js +2 -1
- package/dist/lib/vite-plugins/static-build.js.map +1 -1
- package/dist/lib/vite-rsc/handler.js +10 -2
- package/dist/lib/vite-rsc/handler.js.map +1 -1
- package/dist/minimal/client.js +26 -33
- package/dist/minimal/client.js.map +1 -1
- package/dist/router/client.d.ts +1 -3
- package/dist/router/client.js +39 -46
- package/dist/router/client.js.map +1 -1
- package/dist/router/define-router.d.ts +5 -2
- package/dist/router/define-router.js +30 -16
- package/dist/router/define-router.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,8 +15,6 @@ visit [waku.gg](https://waku.gg) or `npm create waku@latest`
|
|
|
15
15
|
|
|
16
16
|
**Waku** _(wah-ku)_ or **わく** is the minimal React framework. It’s lightweight and designed for a fun developer experience, yet supports all the latest React 19 features like server components and actions. Built for marketing sites, headless commerce, and web apps. For large enterprise applications, you may prefer a heavier framework.
|
|
17
17
|
|
|
18
|
-
> Please try Waku on non-production projects and report any issues you find. Contributors are welcome.
|
|
19
|
-
|
|
20
18
|
## Getting started
|
|
21
19
|
|
|
22
20
|
Start a new Waku project with the `create` command for your preferred package manager. It will scaffold a new project with our default [Waku starter](https://github.com/wakujs/waku/tree/main/examples/01_template).
|
|
@@ -31,7 +29,7 @@ npm create waku@latest
|
|
|
31
29
|
- `waku build` to generate a production build
|
|
32
30
|
- `waku start` to serve the production build locally
|
|
33
31
|
|
|
34
|
-
**Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.
|
|
32
|
+
**Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.13.0`
|
|
35
33
|
|
|
36
34
|
## Rendering
|
|
37
35
|
|
|
@@ -100,7 +100,11 @@ export default createServerEntryAdapter(({ processRequest, processBuild, setAllE
|
|
|
100
100
|
next(err);
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
|
-
const response = await fetch(server.baseUrl + internalPathToBuildStaticFiles
|
|
103
|
+
const response = await fetch(server.baseUrl + internalPathToBuildStaticFiles, {
|
|
104
|
+
headers: {
|
|
105
|
+
connection: 'close'
|
|
106
|
+
}
|
|
107
|
+
});
|
|
104
108
|
await consumeMultiplexedStream(response.body, async (key, stream)=>{
|
|
105
109
|
if (key.startsWith(PRUNABLE_KEY_PREFIX)) {
|
|
106
110
|
utils.unstable_registerPrunableFile(key.slice(PRUNABLE_KEY_PREFIX.length));
|
|
@@ -108,6 +112,8 @@ export default createServerEntryAdapter(({ processRequest, processBuild, setAllE
|
|
|
108
112
|
}
|
|
109
113
|
await utils.emitFile(key, stream);
|
|
110
114
|
});
|
|
115
|
+
// https://github.com/nodejs/node/issues/56645
|
|
116
|
+
await new Promise((resolve)=>setTimeout(resolve, 100));
|
|
111
117
|
await server.close();
|
|
112
118
|
},
|
|
113
119
|
buildOptions,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/cloudflare.ts"],"sourcesContent":["import type { MiddlewareHandler } from 'hono';\nimport { Hono } from 'hono/tiny';\nimport {\n unstable_createServerEntryAdapter as createServerEntryAdapter,\n unstable_startPreviewServer as startPreviewServer,\n} from 'waku/adapter-builders';\nimport {\n unstable_constants as constants,\n unstable_consumeMultiplexedStream as consumeMultiplexedStream,\n unstable_honoMiddleware as honoMiddleware,\n unstable_produceMultiplexedStream as produceMultiplexedStream,\n} from 'waku/internals';\nimport type { BuildOptions } from './cloudflare-build-enhancer.js';\n\nconst { DIST_PUBLIC } = constants;\nconst { contextMiddleware, rscMiddleware, middlewareRunner } = honoMiddleware;\n\nconst DO_NOT_BUNDLE = '';\n\nconst PRUNABLE_KEY_PREFIX = '\\0__prunable__/';\n\nconst emptyStream = () =>\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n },\n });\n\nfunction isWranglerDev(req: Request): boolean {\n // This header seems to only be set for production cloudflare workers\n return !req.headers.get('cf-visitor');\n}\n\nfunction removeGzipEncoding(res: Response): Response {\n const contentType = res.headers.get('content-type');\n if (\n !contentType ||\n contentType.includes('text/html') ||\n contentType.includes('text/plain')\n ) {\n const headers = new Headers(res.headers);\n headers.set('content-encoding', 'Identity');\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n }\n return res;\n}\n\nexport default createServerEntryAdapter(\n (\n { processRequest, processBuild, setAllEnv, config, notFoundHtml },\n options?: {\n static?: boolean;\n handlers?: Record<string, unknown>;\n assetsDir?: string;\n middlewareFns?: (() => MiddlewareHandler)[];\n middlewareModules?: Record<string, () => Promise<unknown>>;\n internalPathToBuildStaticFiles?: string;\n },\n ) => {\n const {\n middlewareFns = [],\n middlewareModules = {},\n internalPathToBuildStaticFiles = '__waku_internal_build_static_files',\n } = options || {};\n const app = new Hono();\n app.notFound((c) => {\n if (notFoundHtml) {\n return c.html(notFoundHtml, 404);\n }\n return c.text('404 Not Found', 404);\n });\n app.use(contextMiddleware());\n for (const middlewareFn of middlewareFns) {\n app.use(middlewareFn());\n }\n app.use(middlewareRunner(middlewareModules as never));\n app.use(rscMiddleware({ processRequest }));\n const buildOptions: BuildOptions = {\n srcDir: config.srcDir,\n distDir: config.distDir,\n DIST_PUBLIC,\n serverless: !options?.static,\n };\n\n const buildBody = () =>\n produceMultiplexedStream(async (emitFile) => {\n await processBuild({\n emitFile,\n unstable_registerPrunableFile: (srcPath) =>\n emitFile(PRUNABLE_KEY_PREFIX + srcPath, emptyStream()),\n });\n });\n\n const fetchFn = async (req: Request) => {\n if (new URL(req.url).pathname === `/${internalPathToBuildStaticFiles}`) {\n return new Response(buildBody());\n }\n let cloudflareContext;\n try {\n cloudflareContext = await import(\n /* @vite-ignore */ DO_NOT_BUNDLE + 'cloudflare:workers'\n );\n } catch {\n // Not in a Cloudflare environment\n }\n let res: Response | Promise<Response>;\n if (cloudflareContext) {\n const { env, waitUntil, passThroughOnException } = cloudflareContext;\n res = app.fetch(req, env, {\n waitUntil,\n passThroughOnException,\n props: undefined,\n });\n } else {\n res = app.fetch(req);\n }\n // Workaround https://github.com/cloudflare/workers-sdk/issues/6577\n if (import.meta.env?.PROD && isWranglerDev(req)) {\n if ('then' in res) {\n res = res.then((res) => removeGzipEncoding(res));\n } else {\n res = removeGzipEncoding(res);\n }\n }\n return res;\n };\n\n return {\n fetch: fetchFn,\n build: async (utils) => {\n const server = await startPreviewServer();\n // Fallback middleware for the case without @cloudflare/vite-plugin\n server.middlewares.use(async (_req, res, next) => {\n try {\n const { Readable } = await import(\n /* @vite-ignore */ DO_NOT_BUNDLE + 'node:stream'\n );\n Readable.fromWeb(buildBody() as never).pipe(res);\n } catch (err) {\n next(err);\n }\n });\n const response = await fetch(\n server.baseUrl + internalPathToBuildStaticFiles,\n );\n await consumeMultiplexedStream(response.body!, async (key, stream) => {\n if (key.startsWith(PRUNABLE_KEY_PREFIX)) {\n utils.unstable_registerPrunableFile(\n key.slice(PRUNABLE_KEY_PREFIX.length),\n );\n return;\n }\n await utils.emitFile(key, stream);\n });\n await server.close();\n },\n buildOptions,\n buildEnhancers: ['waku/adapters/cloudflare-build-enhancer'],\n defaultExport: {\n ...options?.handlers,\n fetch(req: Request, env: Record<string, string>) {\n setAllEnv(env);\n return fetchFn(req);\n },\n },\n };\n },\n);\n"],"names":["Hono","unstable_createServerEntryAdapter","createServerEntryAdapter","unstable_startPreviewServer","startPreviewServer","unstable_constants","constants","unstable_consumeMultiplexedStream","consumeMultiplexedStream","unstable_honoMiddleware","honoMiddleware","unstable_produceMultiplexedStream","produceMultiplexedStream","DIST_PUBLIC","contextMiddleware","rscMiddleware","middlewareRunner","DO_NOT_BUNDLE","PRUNABLE_KEY_PREFIX","emptyStream","ReadableStream","start","controller","close","isWranglerDev","req","headers","get","removeGzipEncoding","res","contentType","includes","Headers","set","Response","body","status","statusText","processRequest","processBuild","setAllEnv","config","notFoundHtml","options","middlewareFns","middlewareModules","internalPathToBuildStaticFiles","app","notFound","c","html","text","use","middlewareFn","buildOptions","srcDir","distDir","serverless","static","buildBody","emitFile","unstable_registerPrunableFile","srcPath","fetchFn","URL","url","pathname","cloudflareContext","env","waitUntil","passThroughOnException","fetch","props","undefined","PROD","then","build","utils","server","middlewares","_req","next","Readable","fromWeb","pipe","err","response","baseUrl","key","stream","startsWith","slice","length","buildEnhancers","defaultExport","handlers"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/cloudflare.ts"],"sourcesContent":["import type { MiddlewareHandler } from 'hono';\nimport { Hono } from 'hono/tiny';\nimport {\n unstable_createServerEntryAdapter as createServerEntryAdapter,\n unstable_startPreviewServer as startPreviewServer,\n} from 'waku/adapter-builders';\nimport {\n unstable_constants as constants,\n unstable_consumeMultiplexedStream as consumeMultiplexedStream,\n unstable_honoMiddleware as honoMiddleware,\n unstable_produceMultiplexedStream as produceMultiplexedStream,\n} from 'waku/internals';\nimport type { BuildOptions } from './cloudflare-build-enhancer.js';\n\nconst { DIST_PUBLIC } = constants;\nconst { contextMiddleware, rscMiddleware, middlewareRunner } = honoMiddleware;\n\nconst DO_NOT_BUNDLE = '';\n\nconst PRUNABLE_KEY_PREFIX = '\\0__prunable__/';\n\nconst emptyStream = () =>\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n },\n });\n\nfunction isWranglerDev(req: Request): boolean {\n // This header seems to only be set for production cloudflare workers\n return !req.headers.get('cf-visitor');\n}\n\nfunction removeGzipEncoding(res: Response): Response {\n const contentType = res.headers.get('content-type');\n if (\n !contentType ||\n contentType.includes('text/html') ||\n contentType.includes('text/plain')\n ) {\n const headers = new Headers(res.headers);\n headers.set('content-encoding', 'Identity');\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n }\n return res;\n}\n\nexport default createServerEntryAdapter(\n (\n { processRequest, processBuild, setAllEnv, config, notFoundHtml },\n options?: {\n static?: boolean;\n handlers?: Record<string, unknown>;\n assetsDir?: string;\n middlewareFns?: (() => MiddlewareHandler)[];\n middlewareModules?: Record<string, () => Promise<unknown>>;\n internalPathToBuildStaticFiles?: string;\n },\n ) => {\n const {\n middlewareFns = [],\n middlewareModules = {},\n internalPathToBuildStaticFiles = '__waku_internal_build_static_files',\n } = options || {};\n const app = new Hono();\n app.notFound((c) => {\n if (notFoundHtml) {\n return c.html(notFoundHtml, 404);\n }\n return c.text('404 Not Found', 404);\n });\n app.use(contextMiddleware());\n for (const middlewareFn of middlewareFns) {\n app.use(middlewareFn());\n }\n app.use(middlewareRunner(middlewareModules as never));\n app.use(rscMiddleware({ processRequest }));\n const buildOptions: BuildOptions = {\n srcDir: config.srcDir,\n distDir: config.distDir,\n DIST_PUBLIC,\n serverless: !options?.static,\n };\n\n const buildBody = () =>\n produceMultiplexedStream(async (emitFile) => {\n await processBuild({\n emitFile,\n unstable_registerPrunableFile: (srcPath) =>\n emitFile(PRUNABLE_KEY_PREFIX + srcPath, emptyStream()),\n });\n });\n\n const fetchFn = async (req: Request) => {\n if (new URL(req.url).pathname === `/${internalPathToBuildStaticFiles}`) {\n return new Response(buildBody());\n }\n let cloudflareContext;\n try {\n cloudflareContext = await import(\n /* @vite-ignore */ DO_NOT_BUNDLE + 'cloudflare:workers'\n );\n } catch {\n // Not in a Cloudflare environment\n }\n let res: Response | Promise<Response>;\n if (cloudflareContext) {\n const { env, waitUntil, passThroughOnException } = cloudflareContext;\n res = app.fetch(req, env, {\n waitUntil,\n passThroughOnException,\n props: undefined,\n });\n } else {\n res = app.fetch(req);\n }\n // Workaround https://github.com/cloudflare/workers-sdk/issues/6577\n if (import.meta.env?.PROD && isWranglerDev(req)) {\n if ('then' in res) {\n res = res.then((res) => removeGzipEncoding(res));\n } else {\n res = removeGzipEncoding(res);\n }\n }\n return res;\n };\n\n return {\n fetch: fetchFn,\n build: async (utils) => {\n const server = await startPreviewServer();\n // Fallback middleware for the case without @cloudflare/vite-plugin\n server.middlewares.use(async (_req, res, next) => {\n try {\n const { Readable } = await import(\n /* @vite-ignore */ DO_NOT_BUNDLE + 'node:stream'\n );\n Readable.fromWeb(buildBody() as never).pipe(res);\n } catch (err) {\n next(err);\n }\n });\n const response = await fetch(\n server.baseUrl + internalPathToBuildStaticFiles,\n { headers: { connection: 'close' } },\n );\n await consumeMultiplexedStream(response.body!, async (key, stream) => {\n if (key.startsWith(PRUNABLE_KEY_PREFIX)) {\n utils.unstable_registerPrunableFile(\n key.slice(PRUNABLE_KEY_PREFIX.length),\n );\n return;\n }\n await utils.emitFile(key, stream);\n });\n // https://github.com/nodejs/node/issues/56645\n await new Promise((resolve) => setTimeout(resolve, 100));\n await server.close();\n },\n buildOptions,\n buildEnhancers: ['waku/adapters/cloudflare-build-enhancer'],\n defaultExport: {\n ...options?.handlers,\n fetch(req: Request, env: Record<string, string>) {\n setAllEnv(env);\n return fetchFn(req);\n },\n },\n };\n },\n);\n"],"names":["Hono","unstable_createServerEntryAdapter","createServerEntryAdapter","unstable_startPreviewServer","startPreviewServer","unstable_constants","constants","unstable_consumeMultiplexedStream","consumeMultiplexedStream","unstable_honoMiddleware","honoMiddleware","unstable_produceMultiplexedStream","produceMultiplexedStream","DIST_PUBLIC","contextMiddleware","rscMiddleware","middlewareRunner","DO_NOT_BUNDLE","PRUNABLE_KEY_PREFIX","emptyStream","ReadableStream","start","controller","close","isWranglerDev","req","headers","get","removeGzipEncoding","res","contentType","includes","Headers","set","Response","body","status","statusText","processRequest","processBuild","setAllEnv","config","notFoundHtml","options","middlewareFns","middlewareModules","internalPathToBuildStaticFiles","app","notFound","c","html","text","use","middlewareFn","buildOptions","srcDir","distDir","serverless","static","buildBody","emitFile","unstable_registerPrunableFile","srcPath","fetchFn","URL","url","pathname","cloudflareContext","env","waitUntil","passThroughOnException","fetch","props","undefined","PROD","then","build","utils","server","middlewares","_req","next","Readable","fromWeb","pipe","err","response","baseUrl","connection","key","stream","startsWith","slice","length","Promise","resolve","setTimeout","buildEnhancers","defaultExport","handlers"],"mappings":"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"}
|
|
@@ -7,14 +7,14 @@ async function postBuild({ distDir, privateDir, rscBase, DIST_PUBLIC, serverless
|
|
|
7
7
|
recursive: true
|
|
8
8
|
});
|
|
9
9
|
writeFileSync(path.join(functionsDir, 'serve.js'), `\
|
|
10
|
-
const { INTERNAL_runFetch } = await import(
|
|
10
|
+
const { INTERNAL_runFetch } = await import(${JSON.stringify(`../${distDir}/server/index.js`)});
|
|
11
11
|
|
|
12
12
|
export default async (request, context) =>
|
|
13
13
|
INTERNAL_runFetch(process.env, request, { context });
|
|
14
14
|
|
|
15
15
|
export const config = {
|
|
16
16
|
preferStatic: true,
|
|
17
|
-
path: ['/', '/*',
|
|
17
|
+
path: ['/', '/*', ${JSON.stringify(`/${rscBase}/**/*`)}],
|
|
18
18
|
};
|
|
19
19
|
`);
|
|
20
20
|
}
|
|
@@ -23,9 +23,9 @@ export const config = {
|
|
|
23
23
|
writeFileSync(netlifyTomlFile, `\
|
|
24
24
|
[build]
|
|
25
25
|
command = "npm run build"
|
|
26
|
-
publish =
|
|
26
|
+
publish = ${JSON.stringify(`${distDir}/${DIST_PUBLIC}`)}
|
|
27
27
|
[functions]
|
|
28
|
-
included_files = [
|
|
28
|
+
included_files = [${JSON.stringify(`${privateDir}/**`)}]
|
|
29
29
|
directory = "netlify-functions"
|
|
30
30
|
`);
|
|
31
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/netlify-build-enhancer.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\nexport type BuildOptions = {\n distDir: string;\n privateDir: string;\n rscBase: string;\n DIST_PUBLIC: string;\n serverless: boolean;\n};\n\nasync function postBuild({\n distDir,\n privateDir,\n rscBase,\n DIST_PUBLIC,\n serverless,\n}: BuildOptions) {\n if (serverless) {\n const functionsDir = path.resolve('netlify-functions');\n mkdirSync(functionsDir, {\n recursive: true,\n });\n writeFileSync(\n path.join(functionsDir, 'serve.js'),\n `\\\nconst { INTERNAL_runFetch } = await import(
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/netlify-build-enhancer.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\nexport type BuildOptions = {\n distDir: string;\n privateDir: string;\n rscBase: string;\n DIST_PUBLIC: string;\n serverless: boolean;\n};\n\nasync function postBuild({\n distDir,\n privateDir,\n rscBase,\n DIST_PUBLIC,\n serverless,\n}: BuildOptions) {\n if (serverless) {\n const functionsDir = path.resolve('netlify-functions');\n mkdirSync(functionsDir, {\n recursive: true,\n });\n writeFileSync(\n path.join(functionsDir, 'serve.js'),\n `\\\nconst { INTERNAL_runFetch } = await import(${JSON.stringify(`../${distDir}/server/index.js`)});\n\nexport default async (request, context) =>\n INTERNAL_runFetch(process.env, request, { context });\n\nexport const config = {\n preferStatic: true,\n path: ['/', '/*', ${JSON.stringify(`/${rscBase}/**/*`)}],\n};\n`,\n );\n }\n const netlifyTomlFile = path.resolve('netlify.toml');\n if (!existsSync(netlifyTomlFile)) {\n writeFileSync(\n netlifyTomlFile,\n `\\\n[build]\n command = \"npm run build\"\n publish = ${JSON.stringify(`${distDir}/${DIST_PUBLIC}`)}\n[functions]\n included_files = [${JSON.stringify(`${privateDir}/**`)}]\n directory = \"netlify-functions\"\n`,\n );\n }\n}\n\nexport default async function buildEnhancer(\n build: (utils: unknown, options: BuildOptions) => Promise<void>,\n): Promise<typeof build> {\n return async (utils: unknown, options: BuildOptions) => {\n await build(utils, options);\n await postBuild(options);\n };\n}\n"],"names":["existsSync","mkdirSync","writeFileSync","path","postBuild","distDir","privateDir","rscBase","DIST_PUBLIC","serverless","functionsDir","resolve","recursive","join","JSON","stringify","netlifyTomlFile","buildEnhancer","build","utils","options"],"mappings":"AAAA,SAASA,UAAU,EAAEC,SAAS,EAAEC,aAAa,QAAQ,UAAU;AAC/D,OAAOC,UAAU,YAAY;AAU7B,eAAeC,UAAU,EACvBC,OAAO,EACPC,UAAU,EACVC,OAAO,EACPC,WAAW,EACXC,UAAU,EACG;IACb,IAAIA,YAAY;QACd,MAAMC,eAAeP,KAAKQ,OAAO,CAAC;QAClCV,UAAUS,cAAc;YACtBE,WAAW;QACb;QACAV,cACEC,KAAKU,IAAI,CAACH,cAAc,aACxB,CAAC;2CACoC,EAAEI,KAAKC,SAAS,CAAC,CAAC,GAAG,EAAEV,QAAQ,gBAAgB,CAAC,EAAE;;;;;;;oBAOzE,EAAES,KAAKC,SAAS,CAAC,CAAC,CAAC,EAAER,QAAQ,KAAK,CAAC,EAAE;;AAEzD,CAAC;IAEC;IACA,MAAMS,kBAAkBb,KAAKQ,OAAO,CAAC;IACrC,IAAI,CAACX,WAAWgB,kBAAkB;QAChCd,cACEc,iBACA,CAAC;;;YAGK,EAAEF,KAAKC,SAAS,CAAC,GAAGV,QAAQ,CAAC,EAAEG,aAAa,EAAE;;oBAEtC,EAAEM,KAAKC,SAAS,CAAC,GAAGT,WAAW,GAAG,CAAC,EAAE;;AAEzD,CAAC;IAEC;AACF;AAEA,eAAe,eAAeW,cAC5BC,KAA+D;IAE/D,OAAO,OAAOC,OAAgBC;QAC5B,MAAMF,MAAMC,OAAOC;QACnB,MAAMhB,UAAUgB;IAClB;AACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
const escapeRegExp = (s)=>s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
3
4
|
async function postBuild({ assetsDir, distDir, rscBase, privateDir, basePath, DIST_PUBLIC, serverless }) {
|
|
4
5
|
const SERVE_JS = 'serve-vercel.js';
|
|
5
6
|
const serveCode = `
|
|
@@ -49,7 +50,7 @@ export default getRequestListener(
|
|
|
49
50
|
}
|
|
50
51
|
const routes = [
|
|
51
52
|
{
|
|
52
|
-
src: `^${basePath}${assetsDir}/(.*)$`,
|
|
53
|
+
src: `^${escapeRegExp(basePath)}${escapeRegExp(assetsDir)}/(.*)$`,
|
|
53
54
|
headers: {
|
|
54
55
|
'cache-control': 'public, immutable, max-age=31536000'
|
|
55
56
|
}
|
|
@@ -59,7 +60,7 @@ export default getRequestListener(
|
|
|
59
60
|
handle: 'filesystem'
|
|
60
61
|
},
|
|
61
62
|
{
|
|
62
|
-
src: basePath + '(.*)',
|
|
63
|
+
src: escapeRegExp(basePath) + '(.*)',
|
|
63
64
|
dest: basePath + rscBase + '/'
|
|
64
65
|
}
|
|
65
66
|
] : []
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/vercel-build-enhancer.ts"],"sourcesContent":["import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\nexport type BuildOptions = {\n assetsDir: string;\n distDir: string;\n rscBase: string;\n privateDir: string;\n basePath: string;\n DIST_PUBLIC: string;\n serverless: boolean;\n};\n\nasync function postBuild({\n assetsDir,\n distDir,\n rscBase,\n privateDir,\n basePath,\n DIST_PUBLIC,\n serverless,\n}: BuildOptions) {\n const SERVE_JS = 'serve-vercel.js';\n const serveCode = `\nimport { INTERNAL_runFetch } from './server/index.js';\n\nconst getRequestListener = globalThis.__WAKU_HONO_NODE_SERVER_GET_REQUEST_LISTENER__;\n\nexport default getRequestListener(\n (req, ...args) => INTERNAL_runFetch(process.env, req, ...args)\n);\n`;\n const publicDir = path.resolve(distDir, DIST_PUBLIC);\n const outputDir = path.resolve('.vercel', 'output');\n cpSync(publicDir, path.join(outputDir, 'static'), { recursive: true });\n\n if (serverless) {\n // for serverless function\n // TODO(waku): can use `@vercel/nft` to packaging with native dependencies\n const serverlessDir = path.join(outputDir, 'functions', rscBase + '.func');\n rmSync(serverlessDir, { recursive: true, force: true });\n mkdirSync(path.join(serverlessDir, distDir), {\n recursive: true,\n });\n writeFileSync(path.resolve(distDir, SERVE_JS), serveCode);\n cpSync(path.resolve(distDir), path.join(serverlessDir, distDir), {\n recursive: true,\n });\n if (existsSync(path.resolve(privateDir))) {\n cpSync(path.resolve(privateDir), path.join(serverlessDir, privateDir), {\n recursive: true,\n dereference: true,\n });\n }\n const vcConfigJson = {\n runtime: 'nodejs22.x',\n handler: `${distDir}/${SERVE_JS}`,\n launcherType: 'Nodejs',\n };\n writeFileSync(\n path.join(serverlessDir, '.vc-config.json'),\n JSON.stringify(vcConfigJson, null, 2),\n );\n writeFileSync(\n path.join(serverlessDir, 'package.json'),\n JSON.stringify({ type: 'module' }, null, 2),\n );\n }\n const routes = [\n {\n src: `^${basePath}${assetsDir}/(.*)$`,\n headers: {\n 'cache-control': 'public, immutable, max-age=31536000',\n },\n },\n ...(serverless\n ? [\n { handle: 'filesystem' },\n {\n src: basePath + '(.*)',\n dest: basePath + rscBase + '/',\n },\n ]\n : []),\n ];\n const configJson = { version: 3, routes };\n mkdirSync(outputDir, { recursive: true });\n writeFileSync(\n path.join(outputDir, 'config.json'),\n JSON.stringify(configJson, null, 2),\n );\n}\n\nexport default async function buildEnhancer(\n build: (utils: unknown, options: BuildOptions) => Promise<void>,\n): Promise<typeof build> {\n return async (utils: unknown, options: BuildOptions) => {\n await build(utils, options);\n await postBuild(options);\n };\n}\n"],"names":["cpSync","existsSync","mkdirSync","rmSync","writeFileSync","path","postBuild","assetsDir","distDir","rscBase","privateDir","basePath","DIST_PUBLIC","serverless","SERVE_JS","serveCode","publicDir","resolve","outputDir","join","recursive","serverlessDir","force","dereference","vcConfigJson","runtime","handler","launcherType","JSON","stringify","type","routes","src","headers","handle","dest","configJson","version","buildEnhancer","build","utils","options"],"mappings":"AAAA,SAASA,MAAM,EAAEC,UAAU,EAAEC,SAAS,EAAEC,MAAM,EAAEC,aAAa,QAAQ,UAAU;AAC/E,OAAOC,UAAU,YAAY;
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/vercel-build-enhancer.ts"],"sourcesContent":["import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\nconst escapeRegExp = (s: string) => s.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&');\n\nexport type BuildOptions = {\n assetsDir: string;\n distDir: string;\n rscBase: string;\n privateDir: string;\n basePath: string;\n DIST_PUBLIC: string;\n serverless: boolean;\n};\n\nasync function postBuild({\n assetsDir,\n distDir,\n rscBase,\n privateDir,\n basePath,\n DIST_PUBLIC,\n serverless,\n}: BuildOptions) {\n const SERVE_JS = 'serve-vercel.js';\n const serveCode = `\nimport { INTERNAL_runFetch } from './server/index.js';\n\nconst getRequestListener = globalThis.__WAKU_HONO_NODE_SERVER_GET_REQUEST_LISTENER__;\n\nexport default getRequestListener(\n (req, ...args) => INTERNAL_runFetch(process.env, req, ...args)\n);\n`;\n const publicDir = path.resolve(distDir, DIST_PUBLIC);\n const outputDir = path.resolve('.vercel', 'output');\n cpSync(publicDir, path.join(outputDir, 'static'), { recursive: true });\n\n if (serverless) {\n // for serverless function\n // TODO(waku): can use `@vercel/nft` to packaging with native dependencies\n const serverlessDir = path.join(outputDir, 'functions', rscBase + '.func');\n rmSync(serverlessDir, { recursive: true, force: true });\n mkdirSync(path.join(serverlessDir, distDir), {\n recursive: true,\n });\n writeFileSync(path.resolve(distDir, SERVE_JS), serveCode);\n cpSync(path.resolve(distDir), path.join(serverlessDir, distDir), {\n recursive: true,\n });\n if (existsSync(path.resolve(privateDir))) {\n cpSync(path.resolve(privateDir), path.join(serverlessDir, privateDir), {\n recursive: true,\n dereference: true,\n });\n }\n const vcConfigJson = {\n runtime: 'nodejs22.x',\n handler: `${distDir}/${SERVE_JS}`,\n launcherType: 'Nodejs',\n };\n writeFileSync(\n path.join(serverlessDir, '.vc-config.json'),\n JSON.stringify(vcConfigJson, null, 2),\n );\n writeFileSync(\n path.join(serverlessDir, 'package.json'),\n JSON.stringify({ type: 'module' }, null, 2),\n );\n }\n const routes = [\n {\n src: `^${escapeRegExp(basePath)}${escapeRegExp(assetsDir)}/(.*)$`,\n headers: {\n 'cache-control': 'public, immutable, max-age=31536000',\n },\n },\n ...(serverless\n ? [\n { handle: 'filesystem' },\n {\n src: escapeRegExp(basePath) + '(.*)',\n dest: basePath + rscBase + '/',\n },\n ]\n : []),\n ];\n const configJson = { version: 3, routes };\n mkdirSync(outputDir, { recursive: true });\n writeFileSync(\n path.join(outputDir, 'config.json'),\n JSON.stringify(configJson, null, 2),\n );\n}\n\nexport default async function buildEnhancer(\n build: (utils: unknown, options: BuildOptions) => Promise<void>,\n): Promise<typeof build> {\n return async (utils: unknown, options: BuildOptions) => {\n await build(utils, options);\n await postBuild(options);\n };\n}\n"],"names":["cpSync","existsSync","mkdirSync","rmSync","writeFileSync","path","escapeRegExp","s","replace","postBuild","assetsDir","distDir","rscBase","privateDir","basePath","DIST_PUBLIC","serverless","SERVE_JS","serveCode","publicDir","resolve","outputDir","join","recursive","serverlessDir","force","dereference","vcConfigJson","runtime","handler","launcherType","JSON","stringify","type","routes","src","headers","handle","dest","configJson","version","buildEnhancer","build","utils","options"],"mappings":"AAAA,SAASA,MAAM,EAAEC,UAAU,EAAEC,SAAS,EAAEC,MAAM,EAAEC,aAAa,QAAQ,UAAU;AAC/E,OAAOC,UAAU,YAAY;AAE7B,MAAMC,eAAe,CAACC,IAAcA,EAAEC,OAAO,CAAC,uBAAuB;AAYrE,eAAeC,UAAU,EACvBC,SAAS,EACTC,OAAO,EACPC,OAAO,EACPC,UAAU,EACVC,QAAQ,EACRC,WAAW,EACXC,UAAU,EACG;IACb,MAAMC,WAAW;IACjB,MAAMC,YAAY,CAAC;;;;;;;;AAQrB,CAAC;IACC,MAAMC,YAAYd,KAAKe,OAAO,CAACT,SAASI;IACxC,MAAMM,YAAYhB,KAAKe,OAAO,CAAC,WAAW;IAC1CpB,OAAOmB,WAAWd,KAAKiB,IAAI,CAACD,WAAW,WAAW;QAAEE,WAAW;IAAK;IAEpE,IAAIP,YAAY;QACd,0BAA0B;QAC1B,0EAA0E;QAC1E,MAAMQ,gBAAgBnB,KAAKiB,IAAI,CAACD,WAAW,aAAaT,UAAU;QAClET,OAAOqB,eAAe;YAAED,WAAW;YAAME,OAAO;QAAK;QACrDvB,UAAUG,KAAKiB,IAAI,CAACE,eAAeb,UAAU;YAC3CY,WAAW;QACb;QACAnB,cAAcC,KAAKe,OAAO,CAACT,SAASM,WAAWC;QAC/ClB,OAAOK,KAAKe,OAAO,CAACT,UAAUN,KAAKiB,IAAI,CAACE,eAAeb,UAAU;YAC/DY,WAAW;QACb;QACA,IAAItB,WAAWI,KAAKe,OAAO,CAACP,cAAc;YACxCb,OAAOK,KAAKe,OAAO,CAACP,aAAaR,KAAKiB,IAAI,CAACE,eAAeX,aAAa;gBACrEU,WAAW;gBACXG,aAAa;YACf;QACF;QACA,MAAMC,eAAe;YACnBC,SAAS;YACTC,SAAS,GAAGlB,QAAQ,CAAC,EAAEM,UAAU;YACjCa,cAAc;QAChB;QACA1B,cACEC,KAAKiB,IAAI,CAACE,eAAe,oBACzBO,KAAKC,SAAS,CAACL,cAAc,MAAM;QAErCvB,cACEC,KAAKiB,IAAI,CAACE,eAAe,iBACzBO,KAAKC,SAAS,CAAC;YAAEC,MAAM;QAAS,GAAG,MAAM;IAE7C;IACA,MAAMC,SAAS;QACb;YACEC,KAAK,CAAC,CAAC,EAAE7B,aAAaQ,YAAYR,aAAaI,WAAW,MAAM,CAAC;YACjE0B,SAAS;gBACP,iBAAiB;YACnB;QACF;WACIpB,aACA;YACE;gBAAEqB,QAAQ;YAAa;YACvB;gBACEF,KAAK7B,aAAaQ,YAAY;gBAC9BwB,MAAMxB,WAAWF,UAAU;YAC7B;SACD,GACD,EAAE;KACP;IACD,MAAM2B,aAAa;QAAEC,SAAS;QAAGN;IAAO;IACxChC,UAAUmB,WAAW;QAAEE,WAAW;IAAK;IACvCnB,cACEC,KAAKiB,IAAI,CAACD,WAAW,gBACrBU,KAAKC,SAAS,CAACO,YAAY,MAAM;AAErC;AAEA,eAAe,eAAeE,cAC5BC,KAA+D;IAE/D,OAAO,OAAOC,OAAgBC;QAC5B,MAAMF,MAAMC,OAAOC;QACnB,MAAMnC,UAAUmC;IAClB;AACF"}
|
package/dist/lib/utils/path.js
CHANGED
|
@@ -90,15 +90,16 @@ export const parseExactPath = (path)=>path.split('/').filter(Boolean).map((name)
|
|
|
90
90
|
type: 'literal',
|
|
91
91
|
name
|
|
92
92
|
}));
|
|
93
|
+
const escapeRegExp = (s)=>s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
93
94
|
/**
|
|
94
95
|
* Transform a path spec to a regular expression.
|
|
95
96
|
*/ export const path2regexp = (path)=>{
|
|
96
97
|
const parts = path.map((item)=>{
|
|
97
98
|
if (item.type === 'literal') {
|
|
98
|
-
return item.name;
|
|
99
|
+
return escapeRegExp(item.name);
|
|
99
100
|
} else if (item.type === 'group') {
|
|
100
|
-
const prefix = item.prefix ?? '';
|
|
101
|
-
const suffix = item.suffix ?? '';
|
|
101
|
+
const prefix = escapeRegExp(item.prefix ?? '');
|
|
102
|
+
const suffix = escapeRegExp(item.suffix ?? '');
|
|
102
103
|
return `${prefix}([^/]+)${suffix}`;
|
|
103
104
|
} else {
|
|
104
105
|
return `(.*)`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils/path.ts"],"sourcesContent":["// Terminology:\n// - filePath: posix-like file path, e.g. `/foo/bar.js` or `c:/foo/bar.js`\n// This is used by Vite.\n// - fileURL: file URL, e.g. `file:///foo/bar.js` or `file:///c:/foo/bar.js`\n// This is used by import().\n// - osPath: os dependent path, e.g. `/foo/bar.js` or `c:\\foo\\bar.js`\n// This is used by node:fs.\n\nconst ABSOLUTE_WIN32_PATH_REGEXP = /^\\/[a-zA-Z]:\\//;\n\nexport const encodeFilePathToAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n throw new Error('Unsupported absolute file path: ' + filePath);\n }\n if (filePath.startsWith('/')) {\n return filePath;\n }\n return '/' + filePath;\n};\n\nexport const decodeFilePathFromAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n return filePath.slice(1);\n }\n return filePath;\n};\n\nexport const filePathToFileURL = (filePath: string) =>\n 'file://' + encodeURI(filePath);\n\nexport const fileURLToFilePath = (fileURL: string) => {\n if (!fileURL.startsWith('file://')) {\n throw new Error('Not a file URL');\n }\n return decodeURI(fileURL.slice('file://'.length));\n};\n\n// for filePath\nexport const joinPath = (...paths: string[]) => {\n const isAbsolute = paths[0]?.startsWith('/');\n const items = ([] as string[]).concat(\n ...paths.map((path) => path.split('/')),\n );\n const stack: string[] = [];\n for (const item of items) {\n if (item === '..') {\n if (stack.length && stack[stack.length - 1] !== '..') {\n stack.pop();\n } else if (!isAbsolute) {\n stack.push('..');\n }\n } else if (item && item !== '.') {\n stack.push(item);\n }\n }\n return (isAbsolute ? '/' : '') + stack.join('/') || '.';\n};\n\nexport const extname = (filePath: string) => {\n const index = filePath.lastIndexOf('.');\n if (index <= 0) {\n return '';\n }\n if (['/', '.'].includes(filePath[index - 1]!)) {\n return '';\n }\n return filePath.slice(index);\n};\n\nexport type PathSpecItem =\n | { type: 'literal'; name: string }\n | { type: 'group'; name?: string; prefix?: string; suffix?: string }\n | { type: 'wildcard'; name?: string };\nexport type PathSpec = readonly PathSpecItem[];\n\nconst SLUG_PATTERN = /^(.*?)\\[([^\\]]+)\\](.*)$/;\n\nexport const parsePathWithSlug = (path: string): PathSpec =>\n path\n .split('/')\n .filter(Boolean)\n .map((name) => {\n const match = SLUG_PATTERN.exec(name);\n if (!match) {\n return { type: 'literal' as const, name };\n }\n const [, prefix, inner, suffix] = match;\n if (inner!.startsWith('...')) {\n return {\n type: 'wildcard' as const,\n name: inner!.slice(3),\n };\n }\n return {\n type: 'group' as const,\n name: inner!,\n ...(prefix ? { prefix } : {}),\n ...(suffix ? { suffix } : {}),\n };\n });\n\nexport const parseExactPath = (path: string): PathSpec =>\n path\n .split('/')\n .filter(Boolean)\n .map((name) => ({ type: 'literal', name }));\n\n/**\n * Transform a path spec to a regular expression.\n */\nexport const path2regexp = (path: PathSpec) => {\n const parts = path.map((item) => {\n if (item.type === 'literal') {\n return item.name;\n } else if (item.type === 'group') {\n const prefix = item.prefix ?? '';\n const suffix = item.suffix ?? '';\n return `${prefix}([^/]+)${suffix}`;\n } else {\n return `(.*)`;\n }\n });\n return `^/${parts.join('/')}$`;\n};\n\n/** Convert a path spec to a string for the path */\nexport const pathSpecAsString = (path: PathSpec) => {\n return (\n '/' +\n path\n .map((item) => {\n if (item.type === 'literal') {\n return item.name;\n } else if (item.type === 'group') {\n const prefix = item.prefix ?? '';\n const suffix = item.suffix ?? '';\n return `${prefix}[${item.name}]${suffix}`;\n } else {\n return `[...${item.name}]`;\n }\n })\n .join('/')\n );\n};\n\n/**\n * Helper function to get the path mapping from the path spec and the pathname.\n *\n * @param pathSpec\n * @param pathname - route as a string\n * @example\n * getPathMapping(\n * [\n * { type: 'literal', name: 'foo' },\n * { type: 'group', name: 'a' },\n * ],\n * '/foo/bar',\n * );\n * // => { a: 'bar' }\n */\nexport const getPathMapping = (\n pathSpec: PathSpec,\n pathname: string,\n): Record<string, string | string[]> | null => {\n const actual = pathname.split('/').filter(Boolean);\n if (pathSpec.length > actual.length) {\n const wildcardIndex = pathSpec.findIndex(\n (spec) => spec.type === 'wildcard',\n );\n if (wildcardIndex === -1) {\n return null;\n }\n const isTerminalWildcard = wildcardIndex === pathSpec.length - 1;\n if (isTerminalWildcard) {\n // Terminal wildcards only pass through with zero actual segments\n // (handled by the root-wildcard special case below)\n if (actual.length > 0) {\n return null;\n }\n } else if (actual.length < pathSpec.length - 1) {\n // Non-terminal wildcards can match zero segments; just need enough\n // actual segments for every non-wildcard spec in the pathSpec\n return null;\n }\n }\n const mapping: Record<string, string | string[]> = {};\n let wildcardStartIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const spec = pathSpec[i]!;\n if (spec.type === 'literal') {\n if (spec.name !== actual[i]) {\n return null;\n }\n } else if (spec.type === 'wildcard') {\n wildcardStartIndex = i;\n break;\n } else {\n const segment = actual[i];\n if (segment === undefined) {\n return null;\n }\n const prefix = spec.prefix ?? '';\n const suffix = spec.suffix ?? '';\n if (prefix || suffix) {\n if (!segment.startsWith(prefix) || !segment.endsWith(suffix || '')) {\n return null;\n }\n const value = segment.slice(\n prefix.length,\n suffix ? -suffix.length : undefined,\n );\n if (!value) {\n return null;\n }\n if (spec.name) {\n mapping[spec.name] = value;\n }\n } else if (spec.name) {\n mapping[spec.name] = segment;\n }\n }\n }\n if (wildcardStartIndex === -1) {\n if (pathSpec.length !== actual.length) {\n return null;\n }\n return mapping;\n }\n\n if (wildcardStartIndex === 0 && actual.length === 0) {\n const wildcardName = pathSpec[wildcardStartIndex]!.name;\n if (wildcardName) {\n mapping[wildcardName] = [];\n }\n return mapping;\n }\n\n let wildcardEndIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const spec = pathSpec[pathSpec.length - i - 1]!;\n if (spec.type === 'literal') {\n if (spec.name !== actual[actual.length - i - 1]) {\n return null;\n }\n } else if (spec.type === 'wildcard') {\n wildcardEndIndex = actual.length - i - 1;\n break;\n } else {\n const segment = actual[actual.length - i - 1];\n if (segment === undefined) {\n return null;\n }\n const prefix = spec.prefix ?? '';\n const suffix = spec.suffix ?? '';\n if (prefix || suffix) {\n if (!segment.startsWith(prefix) || !segment.endsWith(suffix || '')) {\n return null;\n }\n const value = segment.slice(\n prefix.length,\n suffix ? -suffix.length : undefined,\n );\n if (!value) {\n return null;\n }\n if (spec.name) {\n mapping[spec.name] = value;\n }\n } else if (spec.name) {\n mapping[spec.name] = segment;\n }\n }\n }\n if (wildcardStartIndex === -1) {\n throw new Error('Invalid wildcard path');\n }\n const wildcardName = pathSpec[wildcardStartIndex]!.name;\n if (wildcardName) {\n mapping[wildcardName] = actual.slice(\n wildcardStartIndex,\n wildcardEndIndex + 1,\n );\n }\n return mapping;\n};\n\n// basePath config is ensured to have trailing slash (see plugin)\nexport function removeBase(url: string, base: string) {\n if (base !== '/') {\n if (!url.startsWith(base)) {\n throw new Error('pathname must start with basePath: ' + url);\n }\n return url.slice(base.length - 1);\n }\n return url;\n}\n\nexport function addBase(url: string, base: string) {\n if (base !== '/' && url.startsWith('/')) {\n return base.slice(0, -1) + url;\n }\n return url;\n}\n\nexport function countSlugsAndWildcards(pathSpec: PathSpec) {\n let numSlugs = 0;\n let numWildcards = 0;\n for (const slug of pathSpec) {\n if (slug.type !== 'literal') {\n numSlugs++;\n }\n if (slug.type === 'wildcard') {\n numWildcards++;\n }\n }\n return { numSlugs, numWildcards };\n}\n"],"names":["ABSOLUTE_WIN32_PATH_REGEXP","encodeFilePathToAbsolute","filePath","test","Error","startsWith","decodeFilePathFromAbsolute","slice","filePathToFileURL","encodeURI","fileURLToFilePath","fileURL","decodeURI","length","joinPath","paths","isAbsolute","items","concat","map","path","split","stack","item","pop","push","join","extname","index","lastIndexOf","includes","SLUG_PATTERN","parsePathWithSlug","filter","Boolean","name","match","exec","type","prefix","inner","suffix","parseExactPath","path2regexp","parts","pathSpecAsString","getPathMapping","pathSpec","pathname","actual","wildcardIndex","findIndex","spec","isTerminalWildcard","mapping","wildcardStartIndex","i","segment","undefined","endsWith","value","wildcardName","wildcardEndIndex","removeBase","url","base","addBase","countSlugsAndWildcards","numSlugs","numWildcards","slug"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/path.ts"],"sourcesContent":["// Terminology:\n// - filePath: posix-like file path, e.g. `/foo/bar.js` or `c:/foo/bar.js`\n// This is used by Vite.\n// - fileURL: file URL, e.g. `file:///foo/bar.js` or `file:///c:/foo/bar.js`\n// This is used by import().\n// - osPath: os dependent path, e.g. `/foo/bar.js` or `c:\\foo\\bar.js`\n// This is used by node:fs.\n\nconst ABSOLUTE_WIN32_PATH_REGEXP = /^\\/[a-zA-Z]:\\//;\n\nexport const encodeFilePathToAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n throw new Error('Unsupported absolute file path: ' + filePath);\n }\n if (filePath.startsWith('/')) {\n return filePath;\n }\n return '/' + filePath;\n};\n\nexport const decodeFilePathFromAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n return filePath.slice(1);\n }\n return filePath;\n};\n\nexport const filePathToFileURL = (filePath: string) =>\n 'file://' + encodeURI(filePath);\n\nexport const fileURLToFilePath = (fileURL: string) => {\n if (!fileURL.startsWith('file://')) {\n throw new Error('Not a file URL');\n }\n return decodeURI(fileURL.slice('file://'.length));\n};\n\n// for filePath\nexport const joinPath = (...paths: string[]) => {\n const isAbsolute = paths[0]?.startsWith('/');\n const items = ([] as string[]).concat(\n ...paths.map((path) => path.split('/')),\n );\n const stack: string[] = [];\n for (const item of items) {\n if (item === '..') {\n if (stack.length && stack[stack.length - 1] !== '..') {\n stack.pop();\n } else if (!isAbsolute) {\n stack.push('..');\n }\n } else if (item && item !== '.') {\n stack.push(item);\n }\n }\n return (isAbsolute ? '/' : '') + stack.join('/') || '.';\n};\n\nexport const extname = (filePath: string) => {\n const index = filePath.lastIndexOf('.');\n if (index <= 0) {\n return '';\n }\n if (['/', '.'].includes(filePath[index - 1]!)) {\n return '';\n }\n return filePath.slice(index);\n};\n\nexport type PathSpecItem =\n | { type: 'literal'; name: string }\n | { type: 'group'; name?: string; prefix?: string; suffix?: string }\n | { type: 'wildcard'; name?: string };\nexport type PathSpec = readonly PathSpecItem[];\n\nconst SLUG_PATTERN = /^(.*?)\\[([^\\]]+)\\](.*)$/;\n\nexport const parsePathWithSlug = (path: string): PathSpec =>\n path\n .split('/')\n .filter(Boolean)\n .map((name) => {\n const match = SLUG_PATTERN.exec(name);\n if (!match) {\n return { type: 'literal' as const, name };\n }\n const [, prefix, inner, suffix] = match;\n if (inner!.startsWith('...')) {\n return {\n type: 'wildcard' as const,\n name: inner!.slice(3),\n };\n }\n return {\n type: 'group' as const,\n name: inner!,\n ...(prefix ? { prefix } : {}),\n ...(suffix ? { suffix } : {}),\n };\n });\n\nexport const parseExactPath = (path: string): PathSpec =>\n path\n .split('/')\n .filter(Boolean)\n .map((name) => ({ type: 'literal', name }));\n\nconst escapeRegExp = (s: string) => s.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&');\n\n/**\n * Transform a path spec to a regular expression.\n */\nexport const path2regexp = (path: PathSpec) => {\n const parts = path.map((item) => {\n if (item.type === 'literal') {\n return escapeRegExp(item.name);\n } else if (item.type === 'group') {\n const prefix = escapeRegExp(item.prefix ?? '');\n const suffix = escapeRegExp(item.suffix ?? '');\n return `${prefix}([^/]+)${suffix}`;\n } else {\n return `(.*)`;\n }\n });\n return `^/${parts.join('/')}$`;\n};\n\n/** Convert a path spec to a string for the path */\nexport const pathSpecAsString = (path: PathSpec) => {\n return (\n '/' +\n path\n .map((item) => {\n if (item.type === 'literal') {\n return item.name;\n } else if (item.type === 'group') {\n const prefix = item.prefix ?? '';\n const suffix = item.suffix ?? '';\n return `${prefix}[${item.name}]${suffix}`;\n } else {\n return `[...${item.name}]`;\n }\n })\n .join('/')\n );\n};\n\n/**\n * Helper function to get the path mapping from the path spec and the pathname.\n *\n * @param pathSpec\n * @param pathname - route as a string\n * @example\n * getPathMapping(\n * [\n * { type: 'literal', name: 'foo' },\n * { type: 'group', name: 'a' },\n * ],\n * '/foo/bar',\n * );\n * // => { a: 'bar' }\n */\nexport const getPathMapping = (\n pathSpec: PathSpec,\n pathname: string,\n): Record<string, string | string[]> | null => {\n const actual = pathname.split('/').filter(Boolean);\n if (pathSpec.length > actual.length) {\n const wildcardIndex = pathSpec.findIndex(\n (spec) => spec.type === 'wildcard',\n );\n if (wildcardIndex === -1) {\n return null;\n }\n const isTerminalWildcard = wildcardIndex === pathSpec.length - 1;\n if (isTerminalWildcard) {\n // Terminal wildcards only pass through with zero actual segments\n // (handled by the root-wildcard special case below)\n if (actual.length > 0) {\n return null;\n }\n } else if (actual.length < pathSpec.length - 1) {\n // Non-terminal wildcards can match zero segments; just need enough\n // actual segments for every non-wildcard spec in the pathSpec\n return null;\n }\n }\n const mapping: Record<string, string | string[]> = {};\n let wildcardStartIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const spec = pathSpec[i]!;\n if (spec.type === 'literal') {\n if (spec.name !== actual[i]) {\n return null;\n }\n } else if (spec.type === 'wildcard') {\n wildcardStartIndex = i;\n break;\n } else {\n const segment = actual[i];\n if (segment === undefined) {\n return null;\n }\n const prefix = spec.prefix ?? '';\n const suffix = spec.suffix ?? '';\n if (prefix || suffix) {\n if (!segment.startsWith(prefix) || !segment.endsWith(suffix || '')) {\n return null;\n }\n const value = segment.slice(\n prefix.length,\n suffix ? -suffix.length : undefined,\n );\n if (!value) {\n return null;\n }\n if (spec.name) {\n mapping[spec.name] = value;\n }\n } else if (spec.name) {\n mapping[spec.name] = segment;\n }\n }\n }\n if (wildcardStartIndex === -1) {\n if (pathSpec.length !== actual.length) {\n return null;\n }\n return mapping;\n }\n\n if (wildcardStartIndex === 0 && actual.length === 0) {\n const wildcardName = pathSpec[wildcardStartIndex]!.name;\n if (wildcardName) {\n mapping[wildcardName] = [];\n }\n return mapping;\n }\n\n let wildcardEndIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const spec = pathSpec[pathSpec.length - i - 1]!;\n if (spec.type === 'literal') {\n if (spec.name !== actual[actual.length - i - 1]) {\n return null;\n }\n } else if (spec.type === 'wildcard') {\n wildcardEndIndex = actual.length - i - 1;\n break;\n } else {\n const segment = actual[actual.length - i - 1];\n if (segment === undefined) {\n return null;\n }\n const prefix = spec.prefix ?? '';\n const suffix = spec.suffix ?? '';\n if (prefix || suffix) {\n if (!segment.startsWith(prefix) || !segment.endsWith(suffix || '')) {\n return null;\n }\n const value = segment.slice(\n prefix.length,\n suffix ? -suffix.length : undefined,\n );\n if (!value) {\n return null;\n }\n if (spec.name) {\n mapping[spec.name] = value;\n }\n } else if (spec.name) {\n mapping[spec.name] = segment;\n }\n }\n }\n if (wildcardStartIndex === -1) {\n throw new Error('Invalid wildcard path');\n }\n const wildcardName = pathSpec[wildcardStartIndex]!.name;\n if (wildcardName) {\n mapping[wildcardName] = actual.slice(\n wildcardStartIndex,\n wildcardEndIndex + 1,\n );\n }\n return mapping;\n};\n\n// basePath config is ensured to have trailing slash (see plugin)\nexport function removeBase(url: string, base: string) {\n if (base !== '/') {\n if (!url.startsWith(base)) {\n throw new Error('pathname must start with basePath: ' + url);\n }\n return url.slice(base.length - 1);\n }\n return url;\n}\n\nexport function addBase(url: string, base: string) {\n if (base !== '/' && url.startsWith('/')) {\n return base.slice(0, -1) + url;\n }\n return url;\n}\n\nexport function countSlugsAndWildcards(pathSpec: PathSpec) {\n let numSlugs = 0;\n let numWildcards = 0;\n for (const slug of pathSpec) {\n if (slug.type !== 'literal') {\n numSlugs++;\n }\n if (slug.type === 'wildcard') {\n numWildcards++;\n }\n }\n return { numSlugs, numWildcards };\n}\n"],"names":["ABSOLUTE_WIN32_PATH_REGEXP","encodeFilePathToAbsolute","filePath","test","Error","startsWith","decodeFilePathFromAbsolute","slice","filePathToFileURL","encodeURI","fileURLToFilePath","fileURL","decodeURI","length","joinPath","paths","isAbsolute","items","concat","map","path","split","stack","item","pop","push","join","extname","index","lastIndexOf","includes","SLUG_PATTERN","parsePathWithSlug","filter","Boolean","name","match","exec","type","prefix","inner","suffix","parseExactPath","escapeRegExp","s","replace","path2regexp","parts","pathSpecAsString","getPathMapping","pathSpec","pathname","actual","wildcardIndex","findIndex","spec","isTerminalWildcard","mapping","wildcardStartIndex","i","segment","undefined","endsWith","value","wildcardName","wildcardEndIndex","removeBase","url","base","addBase","countSlugsAndWildcards","numSlugs","numWildcards","slug"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const KEY_RSC_PATH = "p";
|
|
2
|
+
declare const KEY_RESPONSE = "r";
|
|
3
|
+
declare const KEY_CLOSE = "x";
|
|
4
|
+
declare const KEY_RSC_PARAMS = "q";
|
|
5
|
+
declare const KEY_TEMPORARY_REFERENCES = "t";
|
|
6
|
+
declare const KEY_DEBUG_ID = "d";
|
|
7
|
+
export type INTERNAL_PrefetchedEntry = {
|
|
8
|
+
[KEY_RSC_PATH]: string;
|
|
9
|
+
[KEY_RESPONSE]: Promise<Response>;
|
|
10
|
+
[KEY_CLOSE]?: () => void;
|
|
11
|
+
[KEY_RSC_PARAMS]?: unknown;
|
|
12
|
+
[KEY_TEMPORARY_REFERENCES]?: unknown;
|
|
13
|
+
[KEY_DEBUG_ID]?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const createPrefetchedEntry: (rscPath: string, response: Promise<Response>, rscParams?: unknown, temporaryReferences?: unknown) => INTERNAL_PrefetchedEntry;
|
|
16
|
+
export declare const getPrefetchedRscPath: (entry: INTERNAL_PrefetchedEntry) => string;
|
|
17
|
+
export declare const getPrefetchedResponse: (entry: INTERNAL_PrefetchedEntry) => Promise<Response>;
|
|
18
|
+
export declare const getPrefetchedClose: (entry: INTERNAL_PrefetchedEntry) => (() => void) | undefined;
|
|
19
|
+
export declare const getPrefetchedRscParams: (entry: INTERNAL_PrefetchedEntry) => unknown;
|
|
20
|
+
export declare const getPrefetchedTemporaryReferences: <T>(entry: INTERNAL_PrefetchedEntry) => T | undefined;
|
|
21
|
+
export declare const getPrefetchedDebugId: (entry: INTERNAL_PrefetchedEntry) => string | undefined;
|
|
22
|
+
export declare const createInitialPrefetchedEntryCode: (rscPath: string, debugId: string | undefined) => string;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const KEY_RSC_PATH = 'p';
|
|
2
|
+
const KEY_RESPONSE = 'r';
|
|
3
|
+
const KEY_CLOSE = 'x';
|
|
4
|
+
const KEY_RSC_PARAMS = 'q';
|
|
5
|
+
const KEY_TEMPORARY_REFERENCES = 't';
|
|
6
|
+
const KEY_DEBUG_ID = 'd';
|
|
7
|
+
export const createPrefetchedEntry = (rscPath, response, rscParams, temporaryReferences)=>({
|
|
8
|
+
[KEY_RSC_PATH]: rscPath,
|
|
9
|
+
[KEY_RESPONSE]: response,
|
|
10
|
+
...rscParams !== undefined ? {
|
|
11
|
+
[KEY_RSC_PARAMS]: rscParams
|
|
12
|
+
} : {},
|
|
13
|
+
...temporaryReferences !== undefined ? {
|
|
14
|
+
[KEY_TEMPORARY_REFERENCES]: temporaryReferences
|
|
15
|
+
} : {}
|
|
16
|
+
});
|
|
17
|
+
export const getPrefetchedRscPath = (entry)=>entry[KEY_RSC_PATH];
|
|
18
|
+
export const getPrefetchedResponse = (entry)=>entry[KEY_RESPONSE];
|
|
19
|
+
export const getPrefetchedClose = (entry)=>entry[KEY_CLOSE];
|
|
20
|
+
export const getPrefetchedRscParams = (entry)=>entry[KEY_RSC_PARAMS];
|
|
21
|
+
export const getPrefetchedTemporaryReferences = (entry)=>entry[KEY_TEMPORARY_REFERENCES];
|
|
22
|
+
export const getPrefetchedDebugId = (entry)=>entry[KEY_DEBUG_ID];
|
|
23
|
+
export const createInitialPrefetchedEntryCode = (rscPath, debugId)=>`
|
|
24
|
+
(() => {
|
|
25
|
+
const e = {};
|
|
26
|
+
e.${KEY_RSC_PATH} = ${JSON.stringify(rscPath)};
|
|
27
|
+
e.${KEY_RESPONSE} = Promise.resolve(new Response(new ReadableStream({
|
|
28
|
+
start(c) {
|
|
29
|
+
const d = (window.__FLIGHT_DATA ||= []);
|
|
30
|
+
const t = new TextEncoder();
|
|
31
|
+
const f = (s) => c.enqueue(typeof s === 'string' ? t.encode(s) : s);
|
|
32
|
+
d.forEach(f);
|
|
33
|
+
d.length = 0;
|
|
34
|
+
d.push = f;
|
|
35
|
+
e.${KEY_CLOSE} = () => {
|
|
36
|
+
if (document.readyState === 'loading') {
|
|
37
|
+
document.addEventListener('DOMContentLoaded', () => c.close());
|
|
38
|
+
} else {
|
|
39
|
+
c.close();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
})));
|
|
44
|
+
${debugId ? `e.${KEY_DEBUG_ID} = ${JSON.stringify(debugId)};` : ''}
|
|
45
|
+
return e;
|
|
46
|
+
})()
|
|
47
|
+
`.split('\n').map((line)=>line.trim()).join('');
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=prefetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/prefetch.ts"],"sourcesContent":["const KEY_RSC_PATH = 'p';\nconst KEY_RESPONSE = 'r';\nconst KEY_CLOSE = 'x';\nconst KEY_RSC_PARAMS = 'q';\nconst KEY_TEMPORARY_REFERENCES = 't';\nconst KEY_DEBUG_ID = 'd';\n\nexport type INTERNAL_PrefetchedEntry = {\n [KEY_RSC_PATH]: string;\n [KEY_RESPONSE]: Promise<Response>;\n [KEY_CLOSE]?: () => void;\n [KEY_RSC_PARAMS]?: unknown;\n [KEY_TEMPORARY_REFERENCES]?: unknown;\n [KEY_DEBUG_ID]?: string;\n};\n\nexport const createPrefetchedEntry = (\n rscPath: string,\n response: Promise<Response>,\n rscParams?: unknown,\n temporaryReferences?: unknown,\n): INTERNAL_PrefetchedEntry => ({\n [KEY_RSC_PATH]: rscPath,\n [KEY_RESPONSE]: response,\n ...(rscParams !== undefined ? { [KEY_RSC_PARAMS]: rscParams } : {}),\n ...(temporaryReferences !== undefined\n ? { [KEY_TEMPORARY_REFERENCES]: temporaryReferences }\n : {}),\n});\n\nexport const getPrefetchedRscPath = (entry: INTERNAL_PrefetchedEntry) =>\n entry[KEY_RSC_PATH];\n\nexport const getPrefetchedResponse = (entry: INTERNAL_PrefetchedEntry) =>\n entry[KEY_RESPONSE];\n\nexport const getPrefetchedClose = (entry: INTERNAL_PrefetchedEntry) =>\n entry[KEY_CLOSE];\n\nexport const getPrefetchedRscParams = (entry: INTERNAL_PrefetchedEntry) =>\n entry[KEY_RSC_PARAMS];\n\nexport const getPrefetchedTemporaryReferences = <T>(\n entry: INTERNAL_PrefetchedEntry,\n) => entry[KEY_TEMPORARY_REFERENCES] as T | undefined;\n\nexport const getPrefetchedDebugId = (entry: INTERNAL_PrefetchedEntry) =>\n entry[KEY_DEBUG_ID];\n\nexport const createInitialPrefetchedEntryCode = (\n rscPath: string,\n debugId: string | undefined,\n) =>\n `\n (() => {\n const e = {};\n e.${KEY_RSC_PATH} = ${JSON.stringify(rscPath)};\n e.${KEY_RESPONSE} = Promise.resolve(new Response(new ReadableStream({\n start(c) {\n const d = (window.__FLIGHT_DATA ||= []);\n const t = new TextEncoder();\n const f = (s) => c.enqueue(typeof s === 'string' ? t.encode(s) : s);\n d.forEach(f);\n d.length = 0;\n d.push = f;\n e.${KEY_CLOSE} = () => {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => c.close());\n } else {\n c.close();\n }\n };\n }\n })));\n ${debugId ? `e.${KEY_DEBUG_ID} = ${JSON.stringify(debugId)};` : ''}\n return e;\n })()\n`\n .split('\\n')\n .map((line) => line.trim())\n .join('');\n"],"names":["KEY_RSC_PATH","KEY_RESPONSE","KEY_CLOSE","KEY_RSC_PARAMS","KEY_TEMPORARY_REFERENCES","KEY_DEBUG_ID","createPrefetchedEntry","rscPath","response","rscParams","temporaryReferences","undefined","getPrefetchedRscPath","entry","getPrefetchedResponse","getPrefetchedClose","getPrefetchedRscParams","getPrefetchedTemporaryReferences","getPrefetchedDebugId","createInitialPrefetchedEntryCode","debugId","JSON","stringify","split","map","line","trim","join"],"mappings":"AAAA,MAAMA,eAAe;AACrB,MAAMC,eAAe;AACrB,MAAMC,YAAY;AAClB,MAAMC,iBAAiB;AACvB,MAAMC,2BAA2B;AACjC,MAAMC,eAAe;AAWrB,OAAO,MAAMC,wBAAwB,CACnCC,SACAC,UACAC,WACAC,sBAC8B,CAAA;QAC9B,CAACV,aAAa,EAAEO;QAChB,CAACN,aAAa,EAAEO;QAChB,GAAIC,cAAcE,YAAY;YAAE,CAACR,eAAe,EAAEM;QAAU,IAAI,CAAC,CAAC;QAClE,GAAIC,wBAAwBC,YACxB;YAAE,CAACP,yBAAyB,EAAEM;QAAoB,IAClD,CAAC,CAAC;IACR,CAAA,EAAG;AAEH,OAAO,MAAME,uBAAuB,CAACC,QACnCA,KAAK,CAACb,aAAa,CAAC;AAEtB,OAAO,MAAMc,wBAAwB,CAACD,QACpCA,KAAK,CAACZ,aAAa,CAAC;AAEtB,OAAO,MAAMc,qBAAqB,CAACF,QACjCA,KAAK,CAACX,UAAU,CAAC;AAEnB,OAAO,MAAMc,yBAAyB,CAACH,QACrCA,KAAK,CAACV,eAAe,CAAC;AAExB,OAAO,MAAMc,mCAAmC,CAC9CJ,QACGA,KAAK,CAACT,yBAAyB,CAAkB;AAEtD,OAAO,MAAMc,uBAAuB,CAACL,QACnCA,KAAK,CAACR,aAAa,CAAC;AAEtB,OAAO,MAAMc,mCAAmC,CAC9CZ,SACAa,UAEA,CAAC;;;MAGG,EAAEpB,aAAa,GAAG,EAAEqB,KAAKC,SAAS,CAACf,SAAS;MAC5C,EAAEN,aAAa;;;;;;;;UAQX,EAAEC,UAAU;;;;;;;;;IASlB,EAAEkB,UAAU,CAAC,EAAE,EAAEf,aAAa,GAAG,EAAEgB,KAAKC,SAAS,CAACF,SAAS,CAAC,CAAC,GAAG,GAAG;;;AAGvE,CAAC,CACIG,KAAK,CAAC,MACNC,GAAG,CAAC,CAACC,OAASA,KAAKC,IAAI,IACvBC,IAAI,CAAC,IAAI"}
|
|
@@ -22,9 +22,7 @@ type DebugDataEventDonePayload = {
|
|
|
22
22
|
};
|
|
23
23
|
export type DebugEventPayload = DebugCmdEventReadyPayload | DebugCmdEventChunkPayload | DebugCmdEventDonePayload | DebugDataEventChunkPayload | DebugDataEventDonePayload;
|
|
24
24
|
export declare function assertIsDebugEventPayload(payload: unknown): asserts payload is DebugEventPayload;
|
|
25
|
-
export declare const setupDebugChannel: (baseFetchFn: typeof fetch,
|
|
26
|
-
d?: string;
|
|
27
|
-
} | undefined) => {
|
|
25
|
+
export declare const setupDebugChannel: (baseFetchFn: typeof fetch, prefetched: boolean, debugId?: string) => {
|
|
28
26
|
debugChannel: {
|
|
29
27
|
readable: ReadableStream<Uint8Array<ArrayBufferLike>>;
|
|
30
28
|
writable: WritableStream<Uint8Array<ArrayBufferLike>>;
|
|
@@ -63,7 +63,7 @@ const createWsDebugChannel = (debugId)=>{
|
|
|
63
63
|
const writable = new WritableStream({
|
|
64
64
|
write (chunk) {
|
|
65
65
|
if (closed) {
|
|
66
|
-
throw new
|
|
66
|
+
throw new Error('Channel is closed');
|
|
67
67
|
}
|
|
68
68
|
hot.send(DEBUG_CMD_EVENT, {
|
|
69
69
|
i: debugId,
|
|
@@ -82,9 +82,8 @@ const createWsDebugChannel = (debugId)=>{
|
|
|
82
82
|
writable
|
|
83
83
|
};
|
|
84
84
|
};
|
|
85
|
-
export const setupDebugChannel = (baseFetchFn,
|
|
86
|
-
if (
|
|
87
|
-
const debugId = prefetchedEntry.d;
|
|
85
|
+
export const setupDebugChannel = (baseFetchFn, prefetched, debugId)=>{
|
|
86
|
+
if (prefetched) {
|
|
88
87
|
if (debugId) {
|
|
89
88
|
const debugChannel = createWsDebugChannel(debugId);
|
|
90
89
|
return {
|
|
@@ -93,11 +92,11 @@ export const setupDebugChannel = (baseFetchFn, prefetchedEntry)=>{
|
|
|
93
92
|
}
|
|
94
93
|
return {};
|
|
95
94
|
}
|
|
96
|
-
const
|
|
97
|
-
const debugChannel = createWsDebugChannel(
|
|
95
|
+
const newDebugId = crypto.randomUUID();
|
|
96
|
+
const debugChannel = createWsDebugChannel(newDebugId);
|
|
98
97
|
const fetchFn = (input, init)=>{
|
|
99
98
|
const headers = new Headers(init?.headers);
|
|
100
|
-
headers.set(DEBUG_ID_HEADER,
|
|
99
|
+
headers.set(DEBUG_ID_HEADER, newDebugId);
|
|
101
100
|
return baseFetchFn(input, {
|
|
102
101
|
...init,
|
|
103
102
|
headers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils/react-debug-channel.ts"],"sourcesContent":["// This file should not include Node specific code.\n\nexport const DEBUG_ID_HEADER = 'X-Waku-Debug-Id';\nexport const DEBUG_CMD_EVENT = 'waku:debug-cmd';\nexport const DEBUG_DATA_EVENT = 'waku:debug-data';\n\nconst bytesToBase64 = (bytes: Uint8Array) => {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n};\n\nconst base64ToBytes = (base64: string) =>\n Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));\n\ntype DebugCmdEventReadyPayload = {\n i: string; // debugId\n};\ntype DebugCmdEventChunkPayload = {\n i: string; // debugId\n b: string; // base64 encoded chunk\n};\ntype DebugCmdEventDonePayload = {\n i: string; // debugId\n d: true; // done flag\n};\ntype DebugDataEventChunkPayload = {\n i: string; // debugId\n b: string; // base64 encoded chunk\n};\ntype DebugDataEventDonePayload = {\n i: string; // debugId\n d: true; // done flag\n};\nexport type DebugEventPayload =\n | DebugCmdEventReadyPayload\n | DebugCmdEventChunkPayload\n | DebugCmdEventDonePayload\n | DebugDataEventChunkPayload\n | DebugDataEventDonePayload;\n\nexport function assertIsDebugEventPayload(\n payload: unknown,\n): asserts payload is DebugEventPayload {\n if (\n !payload ||\n typeof payload !== 'object' ||\n typeof (payload as { i?: unknown }).i !== 'string' ||\n ('b' in payload && typeof (payload as { b?: unknown }).b !== 'string') ||\n ('d' in payload && (payload as { d?: unknown }).d !== true)\n ) {\n throw new Error('Invalid debug event payload');\n }\n}\n\nconst createWsDebugChannel = (debugId: string) => {\n const hot = import.meta.hot!;\n let closed = false;\n let onDataEvent: ((payload: unknown) => void) | undefined;\n\n const cleanup = (notify?: boolean) => {\n if (closed) {\n return;\n }\n closed = true;\n if (onDataEvent) {\n hot.off(DEBUG_DATA_EVENT, onDataEvent);\n }\n if (notify) {\n hot.send(DEBUG_CMD_EVENT, {\n i: debugId,\n d: true,\n } satisfies DebugEventPayload);\n }\n };\n\n const readable = new ReadableStream<Uint8Array>({\n start(controller) {\n onDataEvent = (payload: unknown) => {\n assertIsDebugEventPayload(payload);\n if (closed || payload.i !== debugId) {\n return;\n }\n if ('b' in payload) {\n // chunk\n controller.enqueue(base64ToBytes(payload.b));\n }\n if ('d' in payload) {\n // done\n cleanup();\n controller.close();\n }\n };\n hot.on(DEBUG_DATA_EVENT, onDataEvent);\n hot.send(DEBUG_CMD_EVENT, { i: debugId } satisfies DebugEventPayload);\n },\n cancel() {\n cleanup(true);\n },\n });\n\n const writable = new WritableStream<Uint8Array>({\n write(chunk) {\n if (closed) {\n throw new
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/react-debug-channel.ts"],"sourcesContent":["// This file should not include Node specific code.\n\nexport const DEBUG_ID_HEADER = 'X-Waku-Debug-Id';\nexport const DEBUG_CMD_EVENT = 'waku:debug-cmd';\nexport const DEBUG_DATA_EVENT = 'waku:debug-data';\n\nconst bytesToBase64 = (bytes: Uint8Array) => {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n};\n\nconst base64ToBytes = (base64: string) =>\n Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));\n\ntype DebugCmdEventReadyPayload = {\n i: string; // debugId\n};\ntype DebugCmdEventChunkPayload = {\n i: string; // debugId\n b: string; // base64 encoded chunk\n};\ntype DebugCmdEventDonePayload = {\n i: string; // debugId\n d: true; // done flag\n};\ntype DebugDataEventChunkPayload = {\n i: string; // debugId\n b: string; // base64 encoded chunk\n};\ntype DebugDataEventDonePayload = {\n i: string; // debugId\n d: true; // done flag\n};\nexport type DebugEventPayload =\n | DebugCmdEventReadyPayload\n | DebugCmdEventChunkPayload\n | DebugCmdEventDonePayload\n | DebugDataEventChunkPayload\n | DebugDataEventDonePayload;\n\nexport function assertIsDebugEventPayload(\n payload: unknown,\n): asserts payload is DebugEventPayload {\n if (\n !payload ||\n typeof payload !== 'object' ||\n typeof (payload as { i?: unknown }).i !== 'string' ||\n ('b' in payload && typeof (payload as { b?: unknown }).b !== 'string') ||\n ('d' in payload && (payload as { d?: unknown }).d !== true)\n ) {\n throw new Error('Invalid debug event payload');\n }\n}\n\nconst createWsDebugChannel = (debugId: string) => {\n const hot = import.meta.hot!;\n let closed = false;\n let onDataEvent: ((payload: unknown) => void) | undefined;\n\n const cleanup = (notify?: boolean) => {\n if (closed) {\n return;\n }\n closed = true;\n if (onDataEvent) {\n hot.off(DEBUG_DATA_EVENT, onDataEvent);\n }\n if (notify) {\n hot.send(DEBUG_CMD_EVENT, {\n i: debugId,\n d: true,\n } satisfies DebugEventPayload);\n }\n };\n\n const readable = new ReadableStream<Uint8Array>({\n start(controller) {\n onDataEvent = (payload: unknown) => {\n assertIsDebugEventPayload(payload);\n if (closed || payload.i !== debugId) {\n return;\n }\n if ('b' in payload) {\n // chunk\n controller.enqueue(base64ToBytes(payload.b));\n }\n if ('d' in payload) {\n // done\n cleanup();\n controller.close();\n }\n };\n hot.on(DEBUG_DATA_EVENT, onDataEvent);\n hot.send(DEBUG_CMD_EVENT, { i: debugId } satisfies DebugEventPayload);\n },\n cancel() {\n cleanup(true);\n },\n });\n\n const writable = new WritableStream<Uint8Array>({\n write(chunk) {\n if (closed) {\n throw new Error('Channel is closed');\n }\n hot.send(DEBUG_CMD_EVENT, {\n i: debugId,\n b: bytesToBase64(chunk),\n } satisfies DebugEventPayload);\n },\n close() {\n cleanup(true);\n },\n abort() {\n cleanup(true);\n },\n });\n\n return { readable, writable };\n};\n\nexport const setupDebugChannel = (\n baseFetchFn: typeof fetch,\n prefetched: boolean,\n debugId?: string,\n) => {\n if (prefetched) {\n if (debugId) {\n const debugChannel = createWsDebugChannel(debugId);\n return { debugChannel };\n }\n return {};\n }\n\n const newDebugId = crypto.randomUUID();\n const debugChannel = createWsDebugChannel(newDebugId);\n const fetchFn = ((input: RequestInfo | URL, init?: RequestInit) => {\n const headers = new Headers(init?.headers);\n headers.set(DEBUG_ID_HEADER, newDebugId);\n return baseFetchFn(input, {\n ...init,\n headers,\n });\n }) as typeof fetch;\n return { fetchFn, debugChannel };\n};\n"],"names":["DEBUG_ID_HEADER","DEBUG_CMD_EVENT","DEBUG_DATA_EVENT","bytesToBase64","bytes","binary","i","length","String","fromCharCode","btoa","base64ToBytes","base64","Uint8Array","from","atob","char","charCodeAt","assertIsDebugEventPayload","payload","b","d","Error","createWsDebugChannel","debugId","hot","closed","onDataEvent","cleanup","notify","off","send","readable","ReadableStream","start","controller","enqueue","close","on","cancel","writable","WritableStream","write","chunk","abort","setupDebugChannel","baseFetchFn","prefetched","debugChannel","newDebugId","crypto","randomUUID","fetchFn","input","init","headers","Headers","set"],"mappings":"AAAA,mDAAmD;AAEnD,OAAO,MAAMA,kBAAkB,kBAAkB;AACjD,OAAO,MAAMC,kBAAkB,iBAAiB;AAChD,OAAO,MAAMC,mBAAmB,kBAAkB;AAElD,MAAMC,gBAAgB,CAACC;IACrB,IAAIC,SAAS;IACb,IAAK,IAAIC,IAAI,GAAGA,IAAIF,MAAMG,MAAM,EAAED,IAAK;QACrCD,UAAUG,OAAOC,YAAY,CAACL,KAAK,CAACE,EAAE;IACxC;IACA,OAAOI,KAAKL;AACd;AAEA,MAAMM,gBAAgB,CAACC,SACrBC,WAAWC,IAAI,CAACC,KAAKH,SAAS,CAACI,OAASA,KAAKC,UAAU,CAAC;AA4B1D,OAAO,SAASC,0BACdC,OAAgB;IAEhB,IACE,CAACA,WACD,OAAOA,YAAY,YACnB,OAAO,AAACA,QAA4Bb,CAAC,KAAK,YACzC,OAAOa,WAAW,OAAO,AAACA,QAA4BC,CAAC,KAAK,YAC5D,OAAOD,WAAW,AAACA,QAA4BE,CAAC,KAAK,MACtD;QACA,MAAM,IAAIC,MAAM;IAClB;AACF;AAEA,MAAMC,uBAAuB,CAACC;IAC5B,MAAMC,MAAM,YAAYA,GAAG;IAC3B,IAAIC,SAAS;IACb,IAAIC;IAEJ,MAAMC,UAAU,CAACC;QACf,IAAIH,QAAQ;YACV;QACF;QACAA,SAAS;QACT,IAAIC,aAAa;YACfF,IAAIK,GAAG,CAAC5B,kBAAkByB;QAC5B;QACA,IAAIE,QAAQ;YACVJ,IAAIM,IAAI,CAAC9B,iBAAiB;gBACxBK,GAAGkB;gBACHH,GAAG;YACL;QACF;IACF;IAEA,MAAMW,WAAW,IAAIC,eAA2B;QAC9CC,OAAMC,UAAU;YACdR,cAAc,CAACR;gBACbD,0BAA0BC;gBAC1B,IAAIO,UAAUP,QAAQb,CAAC,KAAKkB,SAAS;oBACnC;gBACF;gBACA,IAAI,OAAOL,SAAS;oBAClB,QAAQ;oBACRgB,WAAWC,OAAO,CAACzB,cAAcQ,QAAQC,CAAC;gBAC5C;gBACA,IAAI,OAAOD,SAAS;oBAClB,OAAO;oBACPS;oBACAO,WAAWE,KAAK;gBAClB;YACF;YACAZ,IAAIa,EAAE,CAACpC,kBAAkByB;YACzBF,IAAIM,IAAI,CAAC9B,iBAAiB;gBAAEK,GAAGkB;YAAQ;QACzC;QACAe;YACEX,QAAQ;QACV;IACF;IAEA,MAAMY,WAAW,IAAIC,eAA2B;QAC9CC,OAAMC,KAAK;YACT,IAAIjB,QAAQ;gBACV,MAAM,IAAIJ,MAAM;YAClB;YACAG,IAAIM,IAAI,CAAC9B,iBAAiB;gBACxBK,GAAGkB;gBACHJ,GAAGjB,cAAcwC;YACnB;QACF;QACAN;YACET,QAAQ;QACV;QACAgB;YACEhB,QAAQ;QACV;IACF;IAEA,OAAO;QAAEI;QAAUQ;IAAS;AAC9B;AAEA,OAAO,MAAMK,oBAAoB,CAC/BC,aACAC,YACAvB;IAEA,IAAIuB,YAAY;QACd,IAAIvB,SAAS;YACX,MAAMwB,eAAezB,qBAAqBC;YAC1C,OAAO;gBAAEwB;YAAa;QACxB;QACA,OAAO,CAAC;IACV;IAEA,MAAMC,aAAaC,OAAOC,UAAU;IACpC,MAAMH,eAAezB,qBAAqB0B;IAC1C,MAAMG,UAAW,CAACC,OAA0BC;QAC1C,MAAMC,UAAU,IAAIC,QAAQF,MAAMC;QAClCA,QAAQE,GAAG,CAACzD,iBAAiBiD;QAC7B,OAAOH,YAAYO,OAAO;YACxB,GAAGC,IAAI;YACPC;QACF;IACF;IACA,OAAO;QAAEH;QAASJ;IAAa;AACjC,EAAE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { decodeFuncId, decodeRscPath } from '../utils/rsc-path.js';
|
|
2
|
+
import { createCustomError } from './custom-errors.js';
|
|
2
3
|
import { removeBase } from './path.js';
|
|
3
4
|
export async function getInput(req, config, temporaryReferences, decodeReply, decodeAction, decodeFormState, loadServerAction) {
|
|
4
5
|
const url = new URL(req.url);
|
|
@@ -11,6 +12,7 @@ export async function getInput(req, config, temporaryReferences, decodeReply, de
|
|
|
11
12
|
// server action: js
|
|
12
13
|
const actionId = decodeFuncId(rscPath);
|
|
13
14
|
if (actionId) {
|
|
15
|
+
validateServerActionRequest(req);
|
|
14
16
|
const body = await getActionBody(req);
|
|
15
17
|
const args = await decodeReply(body, {
|
|
16
18
|
temporaryReferences
|
|
@@ -44,6 +46,7 @@ export async function getInput(req, config, temporaryReferences, decodeReply, de
|
|
|
44
46
|
const contentType = req.headers.get('content-type');
|
|
45
47
|
if (typeof contentType === 'string' && contentType.startsWith('multipart/form-data')) {
|
|
46
48
|
// server action: no js (progressive enhancement)
|
|
49
|
+
validateServerActionRequest(req);
|
|
47
50
|
input = {
|
|
48
51
|
type: 'action',
|
|
49
52
|
fn: async ()=>{
|
|
@@ -73,6 +76,39 @@ export async function getInput(req, config, temporaryReferences, decodeReply, de
|
|
|
73
76
|
}
|
|
74
77
|
return input;
|
|
75
78
|
}
|
|
79
|
+
function validateServerActionRequest(req) {
|
|
80
|
+
if (req.method !== 'POST') {
|
|
81
|
+
throw createCustomError('Method Not Allowed', {
|
|
82
|
+
status: 405
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const origin = req.headers.get('origin');
|
|
86
|
+
if (origin) {
|
|
87
|
+
if (origin === 'null') {
|
|
88
|
+
throw createCustomError('Forbidden', {
|
|
89
|
+
status: 403
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const requestOrigin = new URL(req.url).origin;
|
|
93
|
+
let originUrl;
|
|
94
|
+
try {
|
|
95
|
+
originUrl = new URL(origin);
|
|
96
|
+
} catch {
|
|
97
|
+
throw createCustomError('Forbidden', {
|
|
98
|
+
status: 403
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (originUrl.origin !== requestOrigin) {
|
|
102
|
+
throw createCustomError('Forbidden', {
|
|
103
|
+
status: 403
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} else if (req.headers.get('sec-fetch-site') === 'cross-site') {
|
|
107
|
+
throw createCustomError('Forbidden', {
|
|
108
|
+
status: 403
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
76
112
|
async function getActionBody(req) {
|
|
77
113
|
if (!req.body) {
|
|
78
114
|
throw new Error('missing request body for server function');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils/request.ts"],"sourcesContent":["import type { ReactFormState } from 'react-dom/client';\nimport type { Config } from '../../config.js';\nimport type { Unstable_HandleRequest as HandleRequest } from '../types.js';\nimport { decodeFuncId, decodeRscPath } from '../utils/rsc-path.js';\nimport { removeBase } from './path.js';\n\ntype HandleRequestInput = Parameters<HandleRequest>[0];\n\nexport async function getInput(\n req: Request,\n config: Omit<Required<Config>, 'vite'>,\n temporaryReferences: unknown,\n decodeReply: (\n body: string | FormData,\n options?: object,\n ) => Promise<unknown[]>,\n decodeAction: (body: FormData) => Promise<() => Promise<void>>,\n decodeFormState: (\n actionResult: unknown,\n body: FormData,\n ) => Promise<ReactFormState | undefined>,\n loadServerAction: (id: string) => Promise<unknown>,\n) {\n const url = new URL(req.url);\n const pathname = removeBase(url.pathname, config.basePath);\n const rscPathPrefix = '/' + config.rscBase + '/';\n let rscPath: string | undefined;\n let input: HandleRequestInput;\n if (pathname.startsWith(rscPathPrefix)) {\n rscPath = decodeRscPath(pathname.slice(rscPathPrefix.length));\n // server action: js\n const actionId = decodeFuncId(rscPath);\n if (actionId) {\n const body = await getActionBody(req);\n const args = await decodeReply(body, { temporaryReferences });\n const action = await loadServerAction(actionId);\n input = {\n type: 'function',\n fn: action as any,\n args,\n pathname,\n req,\n };\n } else {\n // client RSC request\n let rscParams: unknown = url.searchParams;\n if (req.body) {\n const body = await getActionBody(req);\n rscParams = await decodeReply(body, {\n temporaryReferences,\n });\n }\n input = {\n type: 'component',\n rscPath,\n rscParams,\n pathname,\n req,\n };\n }\n } else if (req.method === 'POST') {\n const contentType = req.headers.get('content-type');\n if (\n typeof contentType === 'string' &&\n contentType.startsWith('multipart/form-data')\n ) {\n // server action: no js (progressive enhancement)\n input = {\n type: 'action',\n fn: async () => {\n const formData = (await getActionBody(req)) as FormData;\n const decodedAction = await decodeAction(formData);\n const result = await decodedAction();\n return await decodeFormState(result, formData);\n },\n pathname,\n req,\n };\n } else {\n // POST API request\n input = {\n type: 'custom',\n pathname,\n req,\n };\n }\n } else {\n // SSR\n input = {\n type: 'custom',\n pathname,\n req,\n };\n }\n return input;\n}\n\nasync function getActionBody(req: Request) {\n if (!req.body) {\n throw new Error('missing request body for server function');\n }\n const contentType = req.headers.get('content-type');\n if (contentType?.startsWith('multipart/form-data')) {\n return req.formData();\n } else {\n return req.text();\n }\n}\n"],"names":["decodeFuncId","decodeRscPath","removeBase","getInput","req","config","temporaryReferences","decodeReply","decodeAction","decodeFormState","loadServerAction","url","URL","pathname","basePath","rscPathPrefix","rscBase","rscPath","input","startsWith","slice","length","actionId","body","getActionBody","args","action","type","fn","rscParams","searchParams","method","contentType","headers","get","formData","decodedAction","result","Error","text"],"mappings":"AAGA,SAASA,YAAY,EAAEC,aAAa,QAAQ,uBAAuB;AACnE,SAASC,UAAU,QAAQ,YAAY;AAIvC,OAAO,eAAeC,SACpBC,GAAY,EACZC,MAAsC,EACtCC,mBAA4B,EAC5BC,WAGuB,EACvBC,YAA8D,EAC9DC,eAGwC,EACxCC,gBAAkD;IAElD,MAAMC,MAAM,IAAIC,IAAIR,IAAIO,GAAG;IAC3B,MAAME,WAAWX,WAAWS,IAAIE,QAAQ,EAAER,OAAOS,QAAQ;IACzD,MAAMC,gBAAgB,MAAMV,OAAOW,OAAO,GAAG;IAC7C,IAAIC;IACJ,IAAIC;IACJ,IAAIL,SAASM,UAAU,CAACJ,gBAAgB;QACtCE,
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/request.ts"],"sourcesContent":["import type { ReactFormState } from 'react-dom/client';\nimport type { Config } from '../../config.js';\nimport type { Unstable_HandleRequest as HandleRequest } from '../types.js';\nimport { decodeFuncId, decodeRscPath } from '../utils/rsc-path.js';\nimport { createCustomError } from './custom-errors.js';\nimport { removeBase } from './path.js';\n\ntype HandleRequestInput = Parameters<HandleRequest>[0];\n\nexport async function getInput(\n req: Request,\n config: Omit<Required<Config>, 'vite'>,\n temporaryReferences: unknown,\n decodeReply: (\n body: string | FormData,\n options?: object,\n ) => Promise<unknown[]>,\n decodeAction: (body: FormData) => Promise<() => Promise<void>>,\n decodeFormState: (\n actionResult: unknown,\n body: FormData,\n ) => Promise<ReactFormState | undefined>,\n loadServerAction: (id: string) => Promise<unknown>,\n) {\n const url = new URL(req.url);\n const pathname = removeBase(url.pathname, config.basePath);\n const rscPathPrefix = '/' + config.rscBase + '/';\n let rscPath: string | undefined;\n let input: HandleRequestInput;\n if (pathname.startsWith(rscPathPrefix)) {\n rscPath = decodeRscPath(pathname.slice(rscPathPrefix.length));\n // server action: js\n const actionId = decodeFuncId(rscPath);\n if (actionId) {\n validateServerActionRequest(req);\n const body = await getActionBody(req);\n const args = await decodeReply(body, { temporaryReferences });\n const action = await loadServerAction(actionId);\n input = {\n type: 'function',\n fn: action as any,\n args,\n pathname,\n req,\n };\n } else {\n // client RSC request\n let rscParams: unknown = url.searchParams;\n if (req.body) {\n const body = await getActionBody(req);\n rscParams = await decodeReply(body, {\n temporaryReferences,\n });\n }\n input = {\n type: 'component',\n rscPath,\n rscParams,\n pathname,\n req,\n };\n }\n } else if (req.method === 'POST') {\n const contentType = req.headers.get('content-type');\n if (\n typeof contentType === 'string' &&\n contentType.startsWith('multipart/form-data')\n ) {\n // server action: no js (progressive enhancement)\n validateServerActionRequest(req);\n input = {\n type: 'action',\n fn: async () => {\n const formData = (await getActionBody(req)) as FormData;\n const decodedAction = await decodeAction(formData);\n const result = await decodedAction();\n return await decodeFormState(result, formData);\n },\n pathname,\n req,\n };\n } else {\n // POST API request\n input = {\n type: 'custom',\n pathname,\n req,\n };\n }\n } else {\n // SSR\n input = {\n type: 'custom',\n pathname,\n req,\n };\n }\n return input;\n}\n\nfunction validateServerActionRequest(req: Request) {\n if (req.method !== 'POST') {\n throw createCustomError('Method Not Allowed', { status: 405 });\n }\n const origin = req.headers.get('origin');\n if (origin) {\n if (origin === 'null') {\n throw createCustomError('Forbidden', { status: 403 });\n }\n const requestOrigin = new URL(req.url).origin;\n let originUrl: URL;\n try {\n originUrl = new URL(origin);\n } catch {\n throw createCustomError('Forbidden', { status: 403 });\n }\n if (originUrl.origin !== requestOrigin) {\n throw createCustomError('Forbidden', { status: 403 });\n }\n } else if (req.headers.get('sec-fetch-site') === 'cross-site') {\n throw createCustomError('Forbidden', { status: 403 });\n }\n}\n\nasync function getActionBody(req: Request) {\n if (!req.body) {\n throw new Error('missing request body for server function');\n }\n const contentType = req.headers.get('content-type');\n if (contentType?.startsWith('multipart/form-data')) {\n return req.formData();\n } else {\n return req.text();\n }\n}\n"],"names":["decodeFuncId","decodeRscPath","createCustomError","removeBase","getInput","req","config","temporaryReferences","decodeReply","decodeAction","decodeFormState","loadServerAction","url","URL","pathname","basePath","rscPathPrefix","rscBase","rscPath","input","startsWith","slice","length","actionId","validateServerActionRequest","body","getActionBody","args","action","type","fn","rscParams","searchParams","method","contentType","headers","get","formData","decodedAction","result","status","origin","requestOrigin","originUrl","Error","text"],"mappings":"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"}
|