@faapi/faapi 1.2.0 → 1.3.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.
package/dist/index.js CHANGED
@@ -1505,9 +1505,14 @@ function getVitestImportActual() {
1505
1505
  if (typeof vi?.importActual !== "function") return void 0;
1506
1506
  return vi.importActual.bind(vi);
1507
1507
  }
1508
- async function importWithCacheBust(filePath) {
1508
+ async function importWithCacheBust(filePath, bustViteCache = false) {
1509
1509
  const importActual = getVitestImportActual();
1510
1510
  if (importActual) {
1511
+ if (bustViteCache) {
1512
+ let url2 = pathToFileURL(filePath).href;
1513
+ url2 += `?t=${Date.now()}`;
1514
+ return await import(url2);
1515
+ }
1511
1516
  return await importActual(filePath);
1512
1517
  }
1513
1518
  let url = pathToFileURL(filePath).href;
@@ -1868,6 +1873,41 @@ function createContext(request, params, config = {}, ip = "") {
1868
1873
  ctxWithSse.__sseResponse = writer.response;
1869
1874
  ctxWithSse.__sseWriter = writer;
1870
1875
  return writer;
1876
+ },
1877
+ /**
1878
+ * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
1879
+ *
1880
+ * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
1881
+ * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
1882
+ */
1883
+ ok(data) {
1884
+ const responseConfig = config.response;
1885
+ const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
1886
+ const body = okFn(data);
1887
+ return ctx.json(body);
1888
+ },
1889
+ /**
1890
+ * 返回错误响应(对象形式参数,status 和 code 均可省略)
1891
+ *
1892
+ * - status 省略时 HTTP 状态码默认 500
1893
+ * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
1894
+ * - status 和 code 独立无关联
1895
+ *
1896
+ * body 用 config.response.fail(或默认实现)包装。
1897
+ */
1898
+ fail(options) {
1899
+ const responseConfig = config.response;
1900
+ const failFn = responseConfig?.fail ?? ((e) => {
1901
+ const error = { message: e.message };
1902
+ if (e.code !== void 0) error.code = e.code;
1903
+ return { error };
1904
+ });
1905
+ const body = failFn({
1906
+ status: options.status,
1907
+ code: options.code,
1908
+ message: options.message
1909
+ });
1910
+ return ctx.json(body, options.status ?? 500);
1871
1911
  }
1872
1912
  };
1873
1913
  const extend = config?.extendContext;
@@ -2047,6 +2087,12 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
2047
2087
  }
2048
2088
 
2049
2089
  // src/runtime/invokeHandler.ts
2090
+ function wrapResult(result, ctx) {
2091
+ if (result instanceof Response) return result;
2092
+ const responseConfig = ctx.config.response;
2093
+ const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
2094
+ return okFn(result);
2095
+ }
2050
2096
  function mergeMeta(response, meta) {
2051
2097
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
2052
2098
  if (!hasMeta) return response;
@@ -2111,7 +2157,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
2111
2157
  const result = await injectParamsAsync(handler, ctx, body, injectors);
2112
2158
  const sseResponse = pickSseAndAutoClose();
2113
2159
  if (sseResponse) return sseResponse;
2114
- return toResponse(result, meta);
2160
+ return toResponse(wrapResult(result, ctx), meta);
2115
2161
  } catch (err) {
2116
2162
  autoCloseSseOnError();
2117
2163
  throw err;
@@ -2122,7 +2168,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
2122
2168
  const result = await injectParamsAsync(handler, ctx, body, injectors);
2123
2169
  const sseResponse = pickSseAndAutoClose();
2124
2170
  if (sseResponse) return sseResponse;
2125
- return toResponse(result, meta);
2171
+ return toResponse(wrapResult(result, ctx), meta);
2126
2172
  } catch (err) {
2127
2173
  autoCloseSseOnError();
2128
2174
  throw err;
@@ -2134,7 +2180,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
2134
2180
  // src/testServer.ts
2135
2181
  import path12 from "path";
2136
2182
  import os from "os";
2137
- import fs9 from "fs/promises";
2183
+ import fs11 from "fs/promises";
2138
2184
 
2139
2185
  // src/router/scanRoutes.ts
2140
2186
  import fg from "fast-glob";
@@ -2398,205 +2444,6 @@ init_generateSchemaFiles();
2398
2444
 
2399
2445
  // src/validator/validateInput.ts
2400
2446
  init_schemaName();
2401
- var moduleCache = /* @__PURE__ */ new Map();
2402
- function invalidateSchemaCache() {
2403
- moduleCache.clear();
2404
- }
2405
- async function loadSchemaModule(schemaPath) {
2406
- let mod = moduleCache.get(schemaPath);
2407
- if (!mod) {
2408
- mod = await importWithCacheBust(schemaPath);
2409
- moduleCache.set(schemaPath, mod);
2410
- }
2411
- return mod;
2412
- }
2413
- async function validateInput(schemaPath, method, inputType, input) {
2414
- const schemaName = getSchemaName(method, inputType);
2415
- const schemaKey = `${schemaName}Schema`;
2416
- let mod;
2417
- try {
2418
- mod = await loadSchemaModule(schemaPath);
2419
- } catch (err) {
2420
- const reason = err instanceof Error ? err.message : String(err);
2421
- throw new InternalError(`Schema \u6A21\u5757\u52A0\u8F7D\u5931\u8D25: ${schemaPath}: ${reason}`);
2422
- }
2423
- const schema = mod[schemaKey];
2424
- if (schema === void 0 || schema === null) {
2425
- const data = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
2426
- return { valid: true, issues: [], data };
2427
- }
2428
- if (typeof schema !== "object" || typeof schema.safeParse !== "function") {
2429
- throw new InternalError(`Schema \u4E0D\u662F\u6709\u6548\u7684 zod schema: ${schemaPath}#${schemaName}`);
2430
- }
2431
- const inputObj = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
2432
- const zodSchema = schema;
2433
- const result = zodSchema.safeParse(inputObj);
2434
- if (result.success) {
2435
- const data = typeof result.data === "object" && result.data !== null && !Array.isArray(result.data) ? result.data : {};
2436
- return { valid: true, issues: [], data };
2437
- }
2438
- const issues = mapZodIssues(result.error);
2439
- return { valid: false, issues, data: inputObj };
2440
- }
2441
- function mapZodIssues(error) {
2442
- return error.issues.map((issue) => {
2443
- const code = mapZodCode(issue.code, issue.message);
2444
- const path15 = issue.path.map(String).join(".") || "";
2445
- return {
2446
- path: path15,
2447
- code,
2448
- expected: issue.expected ?? mapExpectedFromMessage(issue.message),
2449
- received: issue.received ?? mapReceivedFromMessage(issue.message),
2450
- message: issue.message
2451
- };
2452
- });
2453
- }
2454
- function mapZodCode(zodCode, message) {
2455
- switch (zodCode) {
2456
- case "invalid_type":
2457
- case "invalid_union":
2458
- case "invalid_union_discriminator":
2459
- return "TYPE_MISMATCH";
2460
- case "unrecognized_keys":
2461
- return "INVALID_FORMAT";
2462
- case "invalid_value":
2463
- case "invalid_string":
2464
- case "too_small":
2465
- case "too_big":
2466
- case "invalid_intersection_types":
2467
- case "not_multiple_of":
2468
- return "INVALID_VALUE";
2469
- case "custom":
2470
- return "INVALID_VALUE";
2471
- default:
2472
- if (message.includes("Required") || message.includes("required")) {
2473
- return "MISSING_FIELD";
2474
- }
2475
- return "INVALID_VALUE";
2476
- }
2477
- }
2478
- function mapExpectedFromMessage(message) {
2479
- const match = message.match(/Expected\s+(\w+)/i);
2480
- return match ? match[1].toLowerCase() : "unknown";
2481
- }
2482
- function mapReceivedFromMessage(message) {
2483
- const match = message.match(/received\s+(\w+)/i);
2484
- return match ? match[1].toLowerCase() : "unknown";
2485
- }
2486
-
2487
- // src/server/createServer.ts
2488
- import {
2489
- createServer as createHttpServer
2490
- } from "http";
2491
- import { createSecureServer as createHttp2SecureServer } from "http2";
2492
- import { readFileSync } from "fs";
2493
- import { Readable as Readable2 } from "stream";
2494
- import path11 from "path";
2495
-
2496
- // src/router/matchRoute.ts
2497
- function matchRoute(routes, method, path15) {
2498
- for (const route of routes) {
2499
- if (route.method !== method) {
2500
- continue;
2501
- }
2502
- if (!route.isDynamic) {
2503
- if (route.urlPath === path15) {
2504
- return { route, params: {} };
2505
- }
2506
- continue;
2507
- }
2508
- const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2509
- if (params !== null) {
2510
- return { route, params };
2511
- }
2512
- }
2513
- return null;
2514
- }
2515
- function matchWsRoute(wsRoutes, path15) {
2516
- for (const route of wsRoutes) {
2517
- if (!route.isDynamic) {
2518
- if (route.urlPath === path15) {
2519
- return { route, params: {} };
2520
- }
2521
- continue;
2522
- }
2523
- const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2524
- if (params !== null) {
2525
- return { route, params };
2526
- }
2527
- }
2528
- return null;
2529
- }
2530
- function matchDynamicPath(pattern, path15, paramNames, isCatchAll) {
2531
- const patternSegments = pattern.split("/").filter(Boolean);
2532
- const pathSegments = path15.split("/").filter(Boolean);
2533
- if (isCatchAll) {
2534
- const nonCatchAllCount = patternSegments.length - 1;
2535
- if (pathSegments.length <= nonCatchAllCount) {
2536
- return null;
2537
- }
2538
- const params2 = {};
2539
- for (let i = 0; i < nonCatchAllCount; i++) {
2540
- const patternSeg = patternSegments[i];
2541
- const pathSeg = pathSegments[i];
2542
- if (patternSeg.startsWith(":")) {
2543
- const paramName = patternSeg.slice(1);
2544
- params2[paramName] = pathSeg;
2545
- } else if (patternSeg !== pathSeg) {
2546
- return null;
2547
- }
2548
- }
2549
- const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
2550
- const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
2551
- params2[catchAllParamName] = catchAllValue;
2552
- if (Object.keys(params2).length !== paramNames.length) {
2553
- return null;
2554
- }
2555
- return params2;
2556
- }
2557
- if (patternSegments.length !== pathSegments.length) {
2558
- return null;
2559
- }
2560
- const params = {};
2561
- for (let i = 0; i < patternSegments.length; i++) {
2562
- const patternSeg = patternSegments[i];
2563
- const pathSeg = pathSegments[i];
2564
- if (patternSeg.startsWith(":")) {
2565
- const paramName = patternSeg.slice(1);
2566
- params[paramName] = pathSeg;
2567
- } else if (patternSeg !== pathSeg) {
2568
- return null;
2569
- }
2570
- }
2571
- if (Object.keys(params).length !== paramNames.length) {
2572
- return null;
2573
- }
2574
- return params;
2575
- }
2576
-
2577
- // src/loader/resolveExports.ts
2578
- function resolveExport(module, exportName) {
2579
- if (exportName in module && typeof module[exportName] !== "undefined") {
2580
- return module[exportName];
2581
- }
2582
- const defaultExport = module.default;
2583
- if (defaultExport !== null && typeof defaultExport === "object") {
2584
- const value = defaultExport[exportName];
2585
- if (value !== void 0) {
2586
- return value;
2587
- }
2588
- }
2589
- return void 0;
2590
- }
2591
-
2592
- // src/loader/validateRouteModule.ts
2593
- function validateRouteModule(value, method, filePath) {
2594
- if (typeof value !== "function") {
2595
- throw new Error(
2596
- `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
2597
- );
2598
- }
2599
- }
2600
2447
 
2601
2448
  // src/cli/compileOnDemand.ts
2602
2449
  import path9 from "path";
@@ -2945,32 +2792,232 @@ function getDevDist() {
2945
2792
  return devDistDir;
2946
2793
  }
2947
2794
 
2948
- // src/loader/loadRouteModule.ts
2949
- async function loadRouteModule(filePath, method, rootDir) {
2950
- let module;
2795
+ // src/validator/validateInput.ts
2796
+ var moduleCache = /* @__PURE__ */ new Map();
2797
+ function invalidateSchemaCache() {
2798
+ moduleCache.clear();
2799
+ }
2800
+ async function loadSchemaModule(schemaPath) {
2801
+ let mod = moduleCache.get(schemaPath);
2802
+ if (!mod) {
2803
+ mod = await importWithCacheBust(schemaPath, isDevOnDemandEnabled());
2804
+ moduleCache.set(schemaPath, mod);
2805
+ }
2806
+ return mod;
2807
+ }
2808
+ async function validateInput(schemaPath, method, inputType, input) {
2809
+ const schemaName = getSchemaName(method, inputType);
2810
+ const schemaKey = `${schemaName}Schema`;
2811
+ let mod;
2951
2812
  try {
2952
- module = await importWithCacheBust(filePath);
2813
+ mod = await loadSchemaModule(schemaPath);
2953
2814
  } catch (err) {
2954
- if (isDevOnDemandEnabled() && rootDir) {
2955
- const dist = getDevDist();
2956
- if (dist) {
2957
- const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2815
+ const reason = err instanceof Error ? err.message : String(err);
2816
+ throw new InternalError(`Schema \u6A21\u5757\u52A0\u8F7D\u5931\u8D25: ${schemaPath}: ${reason}`);
2817
+ }
2818
+ const schema = mod[schemaKey];
2819
+ if (schema === void 0 || schema === null) {
2820
+ const data = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
2821
+ return { valid: true, issues: [], data };
2822
+ }
2823
+ if (typeof schema !== "object" || typeof schema.safeParse !== "function") {
2824
+ throw new InternalError(`Schema \u4E0D\u662F\u6709\u6548\u7684 zod schema: ${schemaPath}#${schemaName}`);
2825
+ }
2826
+ const inputObj = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
2827
+ const zodSchema = schema;
2828
+ const result = zodSchema.safeParse(inputObj);
2829
+ if (result.success) {
2830
+ const data = typeof result.data === "object" && result.data !== null && !Array.isArray(result.data) ? result.data : {};
2831
+ return { valid: true, issues: [], data };
2832
+ }
2833
+ const issues = mapZodIssues(result.error);
2834
+ return { valid: false, issues, data: inputObj };
2835
+ }
2836
+ function mapZodIssues(error) {
2837
+ return error.issues.map((issue) => {
2838
+ const code = mapZodCode(issue.code, issue.message);
2839
+ const path15 = issue.path.map(String).join(".") || "";
2840
+ return {
2841
+ path: path15,
2842
+ code,
2843
+ expected: issue.expected ?? mapExpectedFromMessage(issue.message),
2844
+ received: issue.received ?? mapReceivedFromMessage(issue.message),
2845
+ message: issue.message
2846
+ };
2847
+ });
2848
+ }
2849
+ function mapZodCode(zodCode, message) {
2850
+ switch (zodCode) {
2851
+ case "invalid_type":
2852
+ case "invalid_union":
2853
+ case "invalid_union_discriminator":
2854
+ return "TYPE_MISMATCH";
2855
+ case "unrecognized_keys":
2856
+ return "INVALID_FORMAT";
2857
+ case "invalid_value":
2858
+ case "invalid_string":
2859
+ case "too_small":
2860
+ case "too_big":
2861
+ case "invalid_intersection_types":
2862
+ case "not_multiple_of":
2863
+ return "INVALID_VALUE";
2864
+ case "custom":
2865
+ return "INVALID_VALUE";
2866
+ default:
2867
+ if (message.includes("Required") || message.includes("required")) {
2868
+ return "MISSING_FIELD";
2869
+ }
2870
+ return "INVALID_VALUE";
2871
+ }
2872
+ }
2873
+ function mapExpectedFromMessage(message) {
2874
+ const match = message.match(/Expected\s+(\w+)/i);
2875
+ return match ? match[1].toLowerCase() : "unknown";
2876
+ }
2877
+ function mapReceivedFromMessage(message) {
2878
+ const match = message.match(/received\s+(\w+)/i);
2879
+ return match ? match[1].toLowerCase() : "unknown";
2880
+ }
2881
+
2882
+ // src/server/createServer.ts
2883
+ import {
2884
+ createServer as createHttpServer
2885
+ } from "http";
2886
+ import { createSecureServer as createHttp2SecureServer } from "http2";
2887
+ import { readFileSync } from "fs";
2888
+ import { Readable as Readable2 } from "stream";
2889
+ import path11 from "path";
2890
+
2891
+ // src/router/matchRoute.ts
2892
+ function matchRoute(routes, method, path15) {
2893
+ for (const route of routes) {
2894
+ if (route.method !== method) {
2895
+ continue;
2896
+ }
2897
+ if (!route.isDynamic) {
2898
+ if (route.urlPath === path15) {
2899
+ return { route, params: {} };
2900
+ }
2901
+ continue;
2902
+ }
2903
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2904
+ if (params !== null) {
2905
+ return { route, params };
2906
+ }
2907
+ }
2908
+ return null;
2909
+ }
2910
+ function matchWsRoute(wsRoutes, path15) {
2911
+ for (const route of wsRoutes) {
2912
+ if (!route.isDynamic) {
2913
+ if (route.urlPath === path15) {
2914
+ return { route, params: {} };
2915
+ }
2916
+ continue;
2917
+ }
2918
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2919
+ if (params !== null) {
2920
+ return { route, params };
2921
+ }
2922
+ }
2923
+ return null;
2924
+ }
2925
+ function matchDynamicPath(pattern, path15, paramNames, isCatchAll) {
2926
+ const patternSegments = pattern.split("/").filter(Boolean);
2927
+ const pathSegments = path15.split("/").filter(Boolean);
2928
+ if (isCatchAll) {
2929
+ const nonCatchAllCount = patternSegments.length - 1;
2930
+ if (pathSegments.length <= nonCatchAllCount) {
2931
+ return null;
2932
+ }
2933
+ const params2 = {};
2934
+ for (let i = 0; i < nonCatchAllCount; i++) {
2935
+ const patternSeg = patternSegments[i];
2936
+ const pathSeg = pathSegments[i];
2937
+ if (patternSeg.startsWith(":")) {
2938
+ const paramName = patternSeg.slice(1);
2939
+ params2[paramName] = pathSeg;
2940
+ } else if (patternSeg !== pathSeg) {
2941
+ return null;
2942
+ }
2943
+ }
2944
+ const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
2945
+ const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
2946
+ params2[catchAllParamName] = catchAllValue;
2947
+ if (Object.keys(params2).length !== paramNames.length) {
2948
+ return null;
2949
+ }
2950
+ return params2;
2951
+ }
2952
+ if (patternSegments.length !== pathSegments.length) {
2953
+ return null;
2954
+ }
2955
+ const params = {};
2956
+ for (let i = 0; i < patternSegments.length; i++) {
2957
+ const patternSeg = patternSegments[i];
2958
+ const pathSeg = pathSegments[i];
2959
+ if (patternSeg.startsWith(":")) {
2960
+ const paramName = patternSeg.slice(1);
2961
+ params[paramName] = pathSeg;
2962
+ } else if (patternSeg !== pathSeg) {
2963
+ return null;
2964
+ }
2965
+ }
2966
+ if (Object.keys(params).length !== paramNames.length) {
2967
+ return null;
2968
+ }
2969
+ return params;
2970
+ }
2971
+
2972
+ // src/loader/loadRouteModule.ts
2973
+ import fs9 from "fs";
2974
+
2975
+ // src/loader/resolveExports.ts
2976
+ function resolveExport(module, exportName) {
2977
+ if (exportName in module && typeof module[exportName] !== "undefined") {
2978
+ return module[exportName];
2979
+ }
2980
+ const defaultExport = module.default;
2981
+ if (defaultExport !== null && typeof defaultExport === "object") {
2982
+ const value = defaultExport[exportName];
2983
+ if (value !== void 0) {
2984
+ return value;
2985
+ }
2986
+ }
2987
+ return void 0;
2988
+ }
2989
+
2990
+ // src/loader/validateRouteModule.ts
2991
+ function validateRouteModule(value, method, filePath) {
2992
+ if (typeof value !== "function") {
2993
+ throw new Error(
2994
+ `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
2995
+ );
2996
+ }
2997
+ }
2998
+
2999
+ // src/loader/loadRouteModule.ts
3000
+ async function loadRouteModule(filePath, method, rootDir) {
3001
+ if (isDevOnDemandEnabled() && rootDir) {
3002
+ const dist = getDevDist();
3003
+ if (dist) {
3004
+ const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
3005
+ if (sourcePath && fs9.existsSync(sourcePath)) {
2958
3006
  try {
2959
- const compiled = await ensureCompiled(sourcePath, rootDir, dist);
2960
- if (compiled) {
2961
- module = await importWithCacheBust(filePath);
2962
- const handler2 = resolveExport(module, method);
2963
- validateRouteModule(handler2, method, filePath);
2964
- return { handler: handler2, method };
2965
- }
3007
+ await ensureCompiled(sourcePath, rootDir, dist);
2966
3008
  } catch (compileErr) {
2967
- const compileReason = compileErr instanceof Error ? compileErr.message : String(compileErr);
2968
- throw new Error(`Failed to compile route module "${sourcePath}": ${compileReason}`, {
3009
+ const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
3010
+ throw new Error(`Failed to compile route module "${sourcePath}": ${reason}`, {
2969
3011
  cause: compileErr
2970
3012
  });
2971
3013
  }
2972
3014
  }
2973
3015
  }
3016
+ }
3017
+ let module;
3018
+ try {
3019
+ module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
3020
+ } catch (err) {
2974
3021
  const reason = err instanceof Error ? err.message : String(err);
2975
3022
  throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
2976
3023
  }
@@ -3105,6 +3152,7 @@ function getClientIp(req) {
3105
3152
  }
3106
3153
 
3107
3154
  // src/server/handleWsUpgrade.ts
3155
+ import fs10 from "fs";
3108
3156
  import { WebSocketServer, WebSocket } from "ws";
3109
3157
  import path10 from "path";
3110
3158
 
@@ -3204,24 +3252,16 @@ function getPathname(req) {
3204
3252
  return idx >= 0 ? url.slice(0, idx) : url;
3205
3253
  }
3206
3254
  async function loadWsHandler(filePath, ctx, rootDir) {
3207
- let module;
3208
- try {
3209
- module = await importWithCacheBust(filePath);
3210
- } catch (err) {
3211
- if (isDevOnDemandEnabled() && rootDir) {
3212
- const dist = getDevDist();
3213
- if (dist) {
3214
- const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
3215
- const compiled = await ensureCompiled(sourcePath, rootDir, dist);
3216
- if (compiled) {
3217
- module = await importWithCacheBust(filePath);
3218
- }
3255
+ if (isDevOnDemandEnabled() && rootDir) {
3256
+ const dist = getDevDist();
3257
+ if (dist) {
3258
+ const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
3259
+ if (sourcePath && fs10.existsSync(sourcePath)) {
3260
+ await ensureCompiled(sourcePath, rootDir, dist);
3219
3261
  }
3220
3262
  }
3221
- if (!module) {
3222
- throw err;
3223
- }
3224
3263
  }
3264
+ const module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
3225
3265
  const handler = module["WS"];
3226
3266
  if (typeof handler !== "function") {
3227
3267
  throw new Error(`WS export not found in ${filePath}`);
@@ -3569,7 +3609,7 @@ async function createTestServer(options) {
3569
3609
  } = options;
3570
3610
  const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
3571
3611
  const sorted = sortRoutes(routes);
3572
- const schemaDist = dist ? path12.isAbsolute(dist) ? dist : path12.resolve(rootDir, dist) : await fs9.mkdtemp(path12.join(os.tmpdir(), "faapi-test-schema-"));
3612
+ const schemaDist = dist ? path12.isAbsolute(dist) ? dist : path12.resolve(rootDir, dist) : await fs11.mkdtemp(path12.join(os.tmpdir(), "faapi-test-schema-"));
3573
3613
  await generateSchemaFiles(sorted, rootDir, schemaDist);
3574
3614
  const { server } = createServer({
3575
3615
  routes: sorted,
@@ -3602,7 +3642,7 @@ async function createTestServer(options) {
3602
3642
  await new Promise((resolve) => {
3603
3643
  server.close(() => resolve());
3604
3644
  });
3605
- await fs9.rm(schemaDist, { recursive: true, force: true }).catch(() => {
3645
+ await fs11.rm(schemaDist, { recursive: true, force: true }).catch(() => {
3606
3646
  });
3607
3647
  invalidateSchemaCache();
3608
3648
  }
@@ -3744,7 +3784,7 @@ async function connectWs(baseUrl, pathname, options = {}) {
3744
3784
  }
3745
3785
 
3746
3786
  // src/cli/createAppCore.ts
3747
- import fs11 from "fs";
3787
+ import fs13 from "fs";
3748
3788
  import path14 from "path";
3749
3789
  import { PassThrough } from "stream";
3750
3790
 
@@ -3802,7 +3842,7 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
3802
3842
  }
3803
3843
 
3804
3844
  // src/cli/generateRoutes.ts
3805
- import fs10 from "fs";
3845
+ import fs12 from "fs";
3806
3846
  import path13 from "path";
3807
3847
  async function hydrateRoutes(manifest) {
3808
3848
  const hydrateRoute = (serialized) => ({
@@ -3901,7 +3941,8 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
3901
3941
  "helmet",
3902
3942
  "bodyLimit",
3903
3943
  "logger",
3904
- "http2"
3944
+ "http2",
3945
+ "response"
3905
3946
  ]);
3906
3947
  function isFaapiConfigKey(key) {
3907
3948
  return FAAPI_CONFIG_KEYS.has(key);
@@ -3910,7 +3951,7 @@ async function createAppBase(options) {
3910
3951
  const rootDir = options?.rootDir ?? process.cwd();
3911
3952
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
3912
3953
  const routesPath = path14.resolve(rootDir, dist, ROUTES_FILE);
3913
- if (!fs11.existsSync(routesPath)) {
3954
+ if (!fs13.existsSync(routesPath)) {
3914
3955
  throw new Error(
3915
3956
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
3916
3957
  );
@@ -4007,41 +4048,40 @@ async function createAppBase(options) {
4007
4048
  } = injectOpts ?? {};
4008
4049
  const queryStr = query ? "?" + new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString() : "";
4009
4050
  return new Promise((resolve, reject) => {
4010
- const mockRes = {
4011
- statusCode: 200,
4012
- _headers: {},
4013
- _body: Buffer.alloc(0),
4014
- setHeader(name, value) {
4015
- this._headers[name.toLowerCase()] = value;
4016
- },
4017
- appendHeader(name, value) {
4018
- const key = name.toLowerCase();
4019
- const existing = this._headers[key];
4020
- this._headers[key] = existing ? `${existing}, ${value}` : value;
4021
- },
4022
- writeHead(status, headers) {
4023
- this.statusCode = status;
4024
- if (headers) {
4025
- Object.assign(this._headers, headers);
4026
- }
4027
- },
4028
- end(data) {
4029
- const buf = Buffer.isBuffer(data) ? data : Buffer.from(data ?? "");
4030
- this._body = buf;
4031
- resolve({
4032
- status: this.statusCode,
4033
- headers: new Headers(this._headers),
4034
- body: this.parseBody()
4035
- });
4036
- },
4037
- parseBody() {
4038
- try {
4039
- return JSON.parse(this._body.toString());
4040
- } catch {
4041
- return this._body.toString();
4042
- }
4051
+ const chunks = [];
4052
+ const mockRes = new PassThrough();
4053
+ mockRes.statusCode = 200;
4054
+ mockRes._headers = {};
4055
+ mockRes.setHeader = function(name, value) {
4056
+ this._headers[name.toLowerCase()] = value;
4057
+ };
4058
+ mockRes.appendHeader = function(name, value) {
4059
+ const key = name.toLowerCase();
4060
+ const existing = this._headers[key];
4061
+ this._headers[key] = existing ? `${existing}, ${value}` : value;
4062
+ };
4063
+ mockRes.writeHead = function(status, headers) {
4064
+ this.statusCode = status;
4065
+ if (headers) {
4066
+ Object.assign(this._headers, headers);
4043
4067
  }
4044
4068
  };
4069
+ mockRes.on("data", (chunk) => chunks.push(chunk));
4070
+ mockRes.on("error", reject);
4071
+ mockRes.on("finish", () => {
4072
+ const body2 = Buffer.concat(chunks);
4073
+ let parsed;
4074
+ try {
4075
+ parsed = JSON.parse(body2.toString());
4076
+ } catch {
4077
+ parsed = body2.toString();
4078
+ }
4079
+ resolve({
4080
+ status: mockRes.statusCode,
4081
+ headers: new Headers(mockRes._headers),
4082
+ body: parsed
4083
+ });
4084
+ });
4045
4085
  const listeners = server.listeners("request");
4046
4086
  const handler = listeners[listeners.length - 1];
4047
4087
  if (typeof handler !== "function") {
@@ -4083,6 +4123,10 @@ async function createAppBase(options) {
4083
4123
  if (config?.lifecycle?.onClose) {
4084
4124
  await config.lifecycle.onClose({ rootDir, routes: sorted, server });
4085
4125
  }
4126
+ if (!server.listening) {
4127
+ app.server = null;
4128
+ return;
4129
+ }
4086
4130
  return new Promise((resolve) => {
4087
4131
  server.close((err) => {
4088
4132
  if (err) console.error("Error closing server:", err);