@faapi/next 0.0.0-canary.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.
@@ -0,0 +1,29 @@
1
+ import { FaapiPlugin } from '@faapi/faapi';
2
+
3
+ /** 插件选项(来自 faapi.config.ts plugins 声明的第二个元素或 options 字段) */
4
+ interface NextPluginOptions {
5
+ /** 开发模式,默认 NODE_ENV !== 'production' */
6
+ dev?: boolean;
7
+ /** Next.js 项目目录,默认 '.' */
8
+ dir?: string;
9
+ /** faapi API 路径前缀,默认 '/api';匹配此前缀的请求走 faapi,其余走 Next.js */
10
+ apiPrefix?: string;
11
+ }
12
+ /**
13
+ * Next.js + faapi 集成插件
14
+ *
15
+ * 通过 faapi.config.ts 的 plugins 字段加载:
16
+ *
17
+ * ```ts
18
+ * export default {
19
+ * plugins: [
20
+ * ['@faapi/next', { dir: '.' }] // 带选项的元组
21
+ * ],
22
+ * } satisfies FaapiConfig;
23
+ * ```
24
+ *
25
+ * 启动时用 `faapi` 命令,自动集成 Next.js,无需写 custom server 代码。
26
+ */
27
+ declare const nextPlugin: FaapiPlugin;
28
+
29
+ export { type NextPluginOptions, nextPlugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ // src/createNextServer.ts
2
+ import path from "path";
3
+ var DEFAULT_API_PREFIX = "/api";
4
+ function isApiPath(pathname, apiPrefix) {
5
+ return pathname === apiPrefix || pathname.startsWith(apiPrefix + "/");
6
+ }
7
+ var nextPlugin = {
8
+ name: "@faapi/next",
9
+ async setup(ctx) {
10
+ const options = ctx.options ?? {};
11
+ const dev = options.dev ?? process.env.NODE_ENV !== "production";
12
+ const dir = path.resolve(ctx.rootDir, options.dir ?? ".");
13
+ const apiPrefix = options.apiPrefix ?? DEFAULT_API_PREFIX;
14
+ let nextFactory;
15
+ try {
16
+ nextFactory = (await import("next")).default;
17
+ } catch {
18
+ throw new Error("[faapi-next] next \u5305\u672A\u5B89\u88C5\u3002\u8BF7\u8FD0\u884C `pnpm add next` \u5B89\u88C5 Next.js\u3002");
19
+ }
20
+ const nextApp = nextFactory({ dev, dir });
21
+ const nextHandle = nextApp.getRequestHandler();
22
+ await nextApp.prepare();
23
+ const nextUpgradeHandler = typeof nextApp.getUpgradeHandler === "function" ? nextApp.getUpgradeHandler() : null;
24
+ ctx.wrapHandler?.((original) => {
25
+ return (req, res) => {
26
+ const { pathname } = new URL(req.url ?? "/", "http://localhost");
27
+ if (isApiPath(pathname, apiPrefix)) {
28
+ original(req, res);
29
+ } else {
30
+ Promise.resolve(nextHandle(req, res)).catch((err) => {
31
+ console.error("[faapi-next] Next.js handler error:", err);
32
+ if (!res.headersSent) {
33
+ res.statusCode = 500;
34
+ res.end("Next.js handler error");
35
+ }
36
+ });
37
+ }
38
+ };
39
+ });
40
+ ctx.wrapUpgradeHandler?.((original) => {
41
+ return (req, socket, head) => {
42
+ const { pathname } = new URL(req.url ?? "/", "http://localhost");
43
+ if (isApiPath(pathname, apiPrefix) && original) {
44
+ original(req, socket, head);
45
+ } else if (nextUpgradeHandler) {
46
+ nextUpgradeHandler(req, socket, head);
47
+ } else {
48
+ socket.destroy();
49
+ }
50
+ };
51
+ });
52
+ console.log(
53
+ `- Next.js integration: ${dev ? "dev" : "prod"} mode, dir=${dir}, apiPrefix=${apiPrefix}`
54
+ );
55
+ }
56
+ };
57
+ var createNextServer_default = nextPlugin;
58
+ export {
59
+ createNextServer_default as default
60
+ };
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/createNextServer.ts"],"sourcesContent":["/**\n * @faapi/next — Next.js + faapi 集成插件\n *\n * 通过 faapi.config.ts 的 plugins 字段加载,在 server.listen 之前包装 handler:\n * - /api/* 走 faapi handler\n * - 其余走 Next.js getRequestHandler\n *\n * WS upgrade 同步分流:faapi WS 路由走原始 upgrade handler,其余走 Next.js HMR。\n *\n * @see createNextServer.md 设计要点与使用场景\n */\nimport path from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { FaapiPlugin, PluginContext, RequestHandler, UpgradeHandler } from '@faapi/faapi';\n\n/**\n * Next.js 应用最小类型定义\n *\n * next 作为 optional peerDependency,不强制安装;这里只声明插件用到的字段,\n * 避免直接依赖 next 包的类型。\n */\ninterface NextApp {\n getRequestHandler(): (req: IncomingMessage, res: ServerResponse) => Promise<unknown>;\n prepare(): Promise<void>;\n getUpgradeHandler?(): (req: IncomingMessage, socket: unknown, head: Buffer) => void;\n}\n\n/** 插件选项(来自 faapi.config.ts plugins 声明的第二个元素或 options 字段) */\nexport interface NextPluginOptions {\n /** 开发模式,默认 NODE_ENV !== 'production' */\n dev?: boolean;\n /** Next.js 项目目录,默认 '.' */\n dir?: string;\n /** faapi API 路径前缀,默认 '/api';匹配此前缀的请求走 faapi,其余走 Next.js */\n apiPrefix?: string;\n}\n\n/** 默认 API 前缀 */\nconst DEFAULT_API_PREFIX = '/api';\n\n/**\n * 判断 pathname 是否匹配 apiPrefix\n *\n * apiPrefix '/api' 匹配 '/api' 和 '/api/*',不匹配 '/api2'\n */\nfunction isApiPath(pathname: string, apiPrefix: string): boolean {\n return pathname === apiPrefix || pathname.startsWith(apiPrefix + '/');\n}\n\n/**\n * Next.js + faapi 集成插件\n *\n * 通过 faapi.config.ts 的 plugins 字段加载:\n *\n * ```ts\n * export default {\n * plugins: [\n * ['@faapi/next', { dir: '.' }] // 带选项的元组\n * ],\n * } satisfies FaapiConfig;\n * ```\n *\n * 启动时用 `faapi` 命令,自动集成 Next.js,无需写 custom server 代码。\n */\nconst nextPlugin: FaapiPlugin = {\n name: '@faapi/next',\n\n async setup(ctx: PluginContext): Promise<void> {\n const options = (ctx.options as NextPluginOptions) ?? {};\n const dev = options.dev ?? process.env.NODE_ENV !== 'production';\n // dir 相对于项目根目录解析(CLI 形态下 rootDir === process.cwd(),行为不变;\n // 库 API 形态下 rootDir 可能不同,按 rootDir 解析更合理)\n const dir = path.resolve(ctx.rootDir, options.dir ?? '.');\n const apiPrefix = options.apiPrefix ?? DEFAULT_API_PREFIX;\n\n // 1. 动态 import next(peerDependency,未安装时报错)\n let nextFactory: (opts: { dev: boolean; dir: string }) => NextApp;\n try {\n nextFactory = (await import('next')).default as typeof nextFactory;\n } catch {\n throw new Error('[faapi-next] next 包未安装。请运行 `pnpm add next` 安装 Next.js。');\n }\n\n // 2. 启动 Next.js\n const nextApp = nextFactory({ dev, dir });\n const nextHandle = nextApp.getRequestHandler();\n await nextApp.prepare();\n\n // 3. Next.js upgrade handler(dev 模式 HMR)\n const nextUpgradeHandler =\n typeof nextApp.getUpgradeHandler === 'function' ? nextApp.getUpgradeHandler() : null;\n\n // 4. 包装 HTTP handler:/api/* 走 faapi,其余走 Next.js\n ctx.wrapHandler?.((original: RequestHandler): RequestHandler => {\n return (req, res) => {\n const { pathname } = new URL(req.url ?? '/', 'http://localhost');\n if (isApiPath(pathname, apiPrefix)) {\n original(req, res);\n } else {\n // 不传 parsedUrl,让 Next.js 内部解析 req.url\n // NextUrlWithParsedQuery 是 url.parse() 返回的结构,不是 URL 对象\n // 用 Promise.resolve 包装,兼容 handler 返回 undefined 的情况\n Promise.resolve(nextHandle(req, res)).catch((err: unknown) => {\n console.error('[faapi-next] Next.js handler error:', err);\n if (!res.headersSent) {\n res.statusCode = 500;\n res.end('Next.js handler error');\n }\n });\n }\n };\n });\n\n // 5. 包装 WS upgrade handler:/api/* 走 faapi,其余走 Next.js HMR\n ctx.wrapUpgradeHandler?.((original: UpgradeHandler | undefined): UpgradeHandler => {\n return (req, socket, head) => {\n const { pathname } = new URL(req.url ?? '/', 'http://localhost');\n if (isApiPath(pathname, apiPrefix) && original) {\n original(req, socket, head);\n } else if (nextUpgradeHandler) {\n nextUpgradeHandler(req, socket, head);\n } else {\n socket.destroy();\n }\n };\n });\n\n console.log(\n `- Next.js integration: ${dev ? 'dev' : 'prod'} mode, dir=${dir}, apiPrefix=${apiPrefix}`,\n );\n },\n};\n\nexport default nextPlugin;\nexport type { FaapiPlugin, PluginContext, RequestHandler, UpgradeHandler } from '@faapi/faapi';\n"],"mappings":";AAWA,OAAO,UAAU;AA2BjB,IAAM,qBAAqB;AAO3B,SAAS,UAAU,UAAkB,WAA4B;AAC/D,SAAO,aAAa,aAAa,SAAS,WAAW,YAAY,GAAG;AACtE;AAiBA,IAAM,aAA0B;AAAA,EAC9B,MAAM;AAAA,EAEN,MAAM,MAAM,KAAmC;AAC7C,UAAM,UAAW,IAAI,WAAiC,CAAC;AACvD,UAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI,aAAa;AAGpD,UAAM,MAAM,KAAK,QAAQ,IAAI,SAAS,QAAQ,OAAO,GAAG;AACxD,UAAM,YAAY,QAAQ,aAAa;AAGvC,QAAI;AACJ,QAAI;AACF,qBAAe,MAAM,OAAO,MAAM,GAAG;AAAA,IACvC,QAAQ;AACN,YAAM,IAAI,MAAM,+GAAwD;AAAA,IAC1E;AAGA,UAAM,UAAU,YAAY,EAAE,KAAK,IAAI,CAAC;AACxC,UAAM,aAAa,QAAQ,kBAAkB;AAC7C,UAAM,QAAQ,QAAQ;AAGtB,UAAM,qBACJ,OAAO,QAAQ,sBAAsB,aAAa,QAAQ,kBAAkB,IAAI;AAGlF,QAAI,cAAc,CAAC,aAA6C;AAC9D,aAAO,CAAC,KAAK,QAAQ;AACnB,cAAM,EAAE,SAAS,IAAI,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AAC/D,YAAI,UAAU,UAAU,SAAS,GAAG;AAClC,mBAAS,KAAK,GAAG;AAAA,QACnB,OAAO;AAIL,kBAAQ,QAAQ,WAAW,KAAK,GAAG,CAAC,EAAE,MAAM,CAAC,QAAiB;AAC5D,oBAAQ,MAAM,uCAAuC,GAAG;AACxD,gBAAI,CAAC,IAAI,aAAa;AACpB,kBAAI,aAAa;AACjB,kBAAI,IAAI,uBAAuB;AAAA,YACjC;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAGD,QAAI,qBAAqB,CAAC,aAAyD;AACjF,aAAO,CAAC,KAAK,QAAQ,SAAS;AAC5B,cAAM,EAAE,SAAS,IAAI,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AAC/D,YAAI,UAAU,UAAU,SAAS,KAAK,UAAU;AAC9C,mBAAS,KAAK,QAAQ,IAAI;AAAA,QAC5B,WAAW,oBAAoB;AAC7B,6BAAmB,KAAK,QAAQ,IAAI;AAAA,QACtC,OAAO;AACL,iBAAO,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,IACF,CAAC;AAED,YAAQ;AAAA,MACN,0BAA0B,MAAM,QAAQ,MAAM,cAAc,GAAG,eAAe,SAAS;AAAA,IACzF;AAAA,EACF;AACF;AAEA,IAAO,2BAAQ;","names":[]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@faapi/next",
3
+ "version": "0.0.0-canary.0",
4
+ "description": "Next.js integration for faapi — serve faapi APIs and Next.js pages from a single server",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "import": "./src/index.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "prepublishOnly": "pnpm build",
23
+ "test": "vitest run --passWithNoTests",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "dependencies": {
27
+ "@faapi/faapi": "workspace:*"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.15.0",
31
+ "@types/react": "^19.2.17",
32
+ "@types/react-dom": "^19.2.3",
33
+ "@types/ws": "^8.18.1",
34
+ "next": "^16.2.9",
35
+ "react": "^19.2.7",
36
+ "react-dom": "^19.2.7",
37
+ "tsup": "^8.4.0",
38
+ "typescript": "^5.7.0",
39
+ "vitest": "^3.1.0",
40
+ "ws": "^8.21.0"
41
+ },
42
+ "peerDependencies": {
43
+ "next": ">=13.0.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "next": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/faapi/faapi.git",
54
+ "directory": "packages/next"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public",
58
+ "main": "./dist/index.js",
59
+ "types": "./dist/index.d.ts",
60
+ "exports": {
61
+ ".": {
62
+ "types": "./dist/index.d.ts",
63
+ "import": "./dist/index.js"
64
+ }
65
+ }
66
+ }
67
+ }