@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/cli/index.js CHANGED
@@ -545,9 +545,14 @@ function getVitestImportActual() {
545
545
  if (typeof vi?.importActual !== "function") return void 0;
546
546
  return vi.importActual.bind(vi);
547
547
  }
548
- async function importWithCacheBust(filePath) {
548
+ async function importWithCacheBust(filePath, bustViteCache = false) {
549
549
  const importActual = getVitestImportActual();
550
550
  if (importActual) {
551
+ if (bustViteCache) {
552
+ let url2 = pathToFileURL(filePath).href;
553
+ url2 += `?t=${Date.now()}`;
554
+ return await import(url2);
555
+ }
551
556
  return await importActual(filePath);
552
557
  }
553
558
  let url = pathToFileURL(filePath).href;
@@ -4324,31 +4329,28 @@ var init_compileOnDemand = __esm({
4324
4329
  });
4325
4330
 
4326
4331
  // src/loader/loadRouteModule.ts
4332
+ import fs11 from "fs";
4327
4333
  async function loadRouteModule(filePath, method, rootDir) {
4328
- let module;
4329
- try {
4330
- module = await importWithCacheBust(filePath);
4331
- } catch (err) {
4332
- if (isDevOnDemandEnabled() && rootDir) {
4333
- const dist = getDevDist();
4334
- if (dist) {
4335
- const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
4334
+ if (isDevOnDemandEnabled() && rootDir) {
4335
+ const dist = getDevDist();
4336
+ if (dist) {
4337
+ const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
4338
+ if (sourcePath && fs11.existsSync(sourcePath)) {
4336
4339
  try {
4337
- const compiled = await ensureCompiled(sourcePath, rootDir, dist);
4338
- if (compiled) {
4339
- module = await importWithCacheBust(filePath);
4340
- const handler2 = resolveExport(module, method);
4341
- validateRouteModule(handler2, method, filePath);
4342
- return { handler: handler2, method };
4343
- }
4340
+ await ensureCompiled(sourcePath, rootDir, dist);
4344
4341
  } catch (compileErr) {
4345
- const compileReason = compileErr instanceof Error ? compileErr.message : String(compileErr);
4346
- throw new Error(`Failed to compile route module "${sourcePath}": ${compileReason}`, {
4342
+ const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
4343
+ throw new Error(`Failed to compile route module "${sourcePath}": ${reason}`, {
4347
4344
  cause: compileErr
4348
4345
  });
4349
4346
  }
4350
4347
  }
4351
4348
  }
4349
+ }
4350
+ let module;
4351
+ try {
4352
+ module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
4353
+ } catch (err) {
4352
4354
  const reason = err instanceof Error ? err.message : String(err);
4353
4355
  throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
4354
4356
  }
@@ -4572,6 +4574,41 @@ function createContext(request, params, config = {}, ip = "") {
4572
4574
  ctxWithSse.__sseResponse = writer.response;
4573
4575
  ctxWithSse.__sseWriter = writer;
4574
4576
  return writer;
4577
+ },
4578
+ /**
4579
+ * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
4580
+ *
4581
+ * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
4582
+ * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
4583
+ */
4584
+ ok(data) {
4585
+ const responseConfig = config.response;
4586
+ const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
4587
+ const body = okFn(data);
4588
+ return ctx.json(body);
4589
+ },
4590
+ /**
4591
+ * 返回错误响应(对象形式参数,status 和 code 均可省略)
4592
+ *
4593
+ * - status 省略时 HTTP 状态码默认 500
4594
+ * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
4595
+ * - status 和 code 独立无关联
4596
+ *
4597
+ * body 用 config.response.fail(或默认实现)包装。
4598
+ */
4599
+ fail(options) {
4600
+ const responseConfig = config.response;
4601
+ const failFn = responseConfig?.fail ?? ((e) => {
4602
+ const error = { message: e.message };
4603
+ if (e.code !== void 0) error.code = e.code;
4604
+ return { error };
4605
+ });
4606
+ const body = failFn({
4607
+ status: options.status,
4608
+ code: options.code,
4609
+ message: options.message
4610
+ });
4611
+ return ctx.json(body, options.status ?? 500);
4575
4612
  }
4576
4613
  };
4577
4614
  const extend = config?.extendContext;
@@ -4936,6 +4973,12 @@ var init_injectParams = __esm({
4936
4973
  });
4937
4974
 
4938
4975
  // src/runtime/invokeHandler.ts
4976
+ function wrapResult(result, ctx) {
4977
+ if (result instanceof Response) return result;
4978
+ const responseConfig = ctx.config.response;
4979
+ const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
4980
+ return okFn(result);
4981
+ }
4939
4982
  function mergeMeta(response, meta) {
4940
4983
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
4941
4984
  if (!hasMeta) return response;
@@ -5000,7 +5043,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
5000
5043
  const result = await injectParamsAsync(handler, ctx, body, injectors);
5001
5044
  const sseResponse = pickSseAndAutoClose();
5002
5045
  if (sseResponse) return sseResponse;
5003
- return toResponse(result, meta);
5046
+ return toResponse(wrapResult(result, ctx), meta);
5004
5047
  } catch (err) {
5005
5048
  autoCloseSseOnError();
5006
5049
  throw err;
@@ -5011,7 +5054,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
5011
5054
  const result = await injectParamsAsync(handler, ctx, body, injectors);
5012
5055
  const sseResponse = pickSseAndAutoClose();
5013
5056
  if (sseResponse) return sseResponse;
5014
- return toResponse(result, meta);
5057
+ return toResponse(wrapResult(result, ctx), meta);
5015
5058
  } catch (err) {
5016
5059
  autoCloseSseOnError();
5017
5060
  throw err;
@@ -5063,7 +5106,7 @@ function invalidateSchemaCache() {
5063
5106
  async function loadSchemaModule(schemaPath) {
5064
5107
  let mod = moduleCache.get(schemaPath);
5065
5108
  if (!mod) {
5066
- mod = await importWithCacheBust(schemaPath);
5109
+ mod = await importWithCacheBust(schemaPath, isDevOnDemandEnabled());
5067
5110
  moduleCache.set(schemaPath, mod);
5068
5111
  }
5069
5112
  return mod;
@@ -5148,6 +5191,7 @@ var init_validateInput = __esm({
5148
5191
  init_schemaName();
5149
5192
  init_httpErrors();
5150
5193
  init_importWithCacheBust();
5194
+ init_compileOnDemand();
5151
5195
  moduleCache = /* @__PURE__ */ new Map();
5152
5196
  }
5153
5197
  });
@@ -5459,6 +5503,7 @@ var init_wsHandler = __esm({
5459
5503
  });
5460
5504
 
5461
5505
  // src/server/handleWsUpgrade.ts
5506
+ import fs12 from "fs";
5462
5507
  import { WebSocketServer, WebSocket } from "ws";
5463
5508
  import path13 from "path";
5464
5509
  function getPathname(req) {
@@ -5467,24 +5512,16 @@ function getPathname(req) {
5467
5512
  return idx >= 0 ? url.slice(0, idx) : url;
5468
5513
  }
5469
5514
  async function loadWsHandler(filePath, ctx, rootDir) {
5470
- let module;
5471
- try {
5472
- module = await importWithCacheBust(filePath);
5473
- } catch (err) {
5474
- if (isDevOnDemandEnabled() && rootDir) {
5475
- const dist = getDevDist();
5476
- if (dist) {
5477
- const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
5478
- const compiled = await ensureCompiled(sourcePath, rootDir, dist);
5479
- if (compiled) {
5480
- module = await importWithCacheBust(filePath);
5481
- }
5515
+ if (isDevOnDemandEnabled() && rootDir) {
5516
+ const dist = getDevDist();
5517
+ if (dist) {
5518
+ const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
5519
+ if (sourcePath && fs12.existsSync(sourcePath)) {
5520
+ await ensureCompiled(sourcePath, rootDir, dist);
5482
5521
  }
5483
5522
  }
5484
- if (!module) {
5485
- throw err;
5486
- }
5487
5523
  }
5524
+ const module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
5488
5525
  const handler = module["WS"];
5489
5526
  if (typeof handler !== "function") {
5490
5527
  throw new Error(`WS export not found in ${filePath}`);
@@ -5955,7 +5992,7 @@ var init_loadPlugins = __esm({
5955
5992
  });
5956
5993
 
5957
5994
  // src/cli/createAppCore.ts
5958
- import fs11 from "fs";
5995
+ import fs13 from "fs";
5959
5996
  import path15 from "path";
5960
5997
  import { PassThrough } from "stream";
5961
5998
  function isFaapiConfigKey(key) {
@@ -5965,7 +6002,7 @@ async function createAppBase(options) {
5965
6002
  const rootDir = options?.rootDir ?? process.cwd();
5966
6003
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
5967
6004
  const routesPath = path15.resolve(rootDir, dist, ROUTES_FILE);
5968
- if (!fs11.existsSync(routesPath)) {
6005
+ if (!fs13.existsSync(routesPath)) {
5969
6006
  throw new Error(
5970
6007
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
5971
6008
  );
@@ -6062,41 +6099,40 @@ async function createAppBase(options) {
6062
6099
  } = injectOpts ?? {};
6063
6100
  const queryStr = query ? "?" + new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString() : "";
6064
6101
  return new Promise((resolve3, reject) => {
6065
- const mockRes = {
6066
- statusCode: 200,
6067
- _headers: {},
6068
- _body: Buffer.alloc(0),
6069
- setHeader(name, value) {
6070
- this._headers[name.toLowerCase()] = value;
6071
- },
6072
- appendHeader(name, value) {
6073
- const key = name.toLowerCase();
6074
- const existing = this._headers[key];
6075
- this._headers[key] = existing ? `${existing}, ${value}` : value;
6076
- },
6077
- writeHead(status, headers) {
6078
- this.statusCode = status;
6079
- if (headers) {
6080
- Object.assign(this._headers, headers);
6081
- }
6082
- },
6083
- end(data) {
6084
- const buf = Buffer.isBuffer(data) ? data : Buffer.from(data ?? "");
6085
- this._body = buf;
6086
- resolve3({
6087
- status: this.statusCode,
6088
- headers: new Headers(this._headers),
6089
- body: this.parseBody()
6090
- });
6091
- },
6092
- parseBody() {
6093
- try {
6094
- return JSON.parse(this._body.toString());
6095
- } catch {
6096
- return this._body.toString();
6097
- }
6102
+ const chunks = [];
6103
+ const mockRes = new PassThrough();
6104
+ mockRes.statusCode = 200;
6105
+ mockRes._headers = {};
6106
+ mockRes.setHeader = function(name, value) {
6107
+ this._headers[name.toLowerCase()] = value;
6108
+ };
6109
+ mockRes.appendHeader = function(name, value) {
6110
+ const key = name.toLowerCase();
6111
+ const existing = this._headers[key];
6112
+ this._headers[key] = existing ? `${existing}, ${value}` : value;
6113
+ };
6114
+ mockRes.writeHead = function(status, headers) {
6115
+ this.statusCode = status;
6116
+ if (headers) {
6117
+ Object.assign(this._headers, headers);
6098
6118
  }
6099
6119
  };
6120
+ mockRes.on("data", (chunk) => chunks.push(chunk));
6121
+ mockRes.on("error", reject);
6122
+ mockRes.on("finish", () => {
6123
+ const body2 = Buffer.concat(chunks);
6124
+ let parsed;
6125
+ try {
6126
+ parsed = JSON.parse(body2.toString());
6127
+ } catch {
6128
+ parsed = body2.toString();
6129
+ }
6130
+ resolve3({
6131
+ status: mockRes.statusCode,
6132
+ headers: new Headers(mockRes._headers),
6133
+ body: parsed
6134
+ });
6135
+ });
6100
6136
  const listeners = server.listeners("request");
6101
6137
  const handler = listeners[listeners.length - 1];
6102
6138
  if (typeof handler !== "function") {
@@ -6138,6 +6174,10 @@ async function createAppBase(options) {
6138
6174
  if (config?.lifecycle?.onClose) {
6139
6175
  await config.lifecycle.onClose({ rootDir, routes: sorted, server });
6140
6176
  }
6177
+ if (!server.listening) {
6178
+ app.server = null;
6179
+ return;
6180
+ }
6141
6181
  return new Promise((resolve3) => {
6142
6182
  server.close((err) => {
6143
6183
  if (err) console.error("Error closing server:", err);
@@ -6191,7 +6231,8 @@ var init_createAppCore = __esm({
6191
6231
  "helmet",
6192
6232
  "bodyLimit",
6193
6233
  "logger",
6194
- "http2"
6234
+ "http2",
6235
+ "response"
6195
6236
  ]);
6196
6237
  }
6197
6238
  });
@@ -6287,7 +6328,7 @@ var init_devCommand = __esm({
6287
6328
 
6288
6329
  // src/cli/compileBuildRoutes.ts
6289
6330
  import path17 from "path";
6290
- import fs12 from "fs";
6331
+ import fs14 from "fs";
6291
6332
  import fg3 from "fast-glob";
6292
6333
  async function compileBuildRoutes(options) {
6293
6334
  const { rootDir, dist, files, logLevel = "silent" } = options;
@@ -6301,7 +6342,7 @@ async function compileBuildRoutes(options) {
6301
6342
  return { compiledFiles: [] };
6302
6343
  }
6303
6344
  const absDist = path17.resolve(rootDir, dist);
6304
- await fs12.promises.mkdir(absDist, { recursive: true });
6345
+ await fs14.promises.mkdir(absDist, { recursive: true });
6305
6346
  const plugins = buildAliasPlugins(rootDir);
6306
6347
  const esbuild = await import("esbuild");
6307
6348
  const outbase = path17.resolve(rootDir, APP_DIR3);
@@ -6336,7 +6377,7 @@ __export(buildCommand_exports, {
6336
6377
  buildCommand: () => buildCommand
6337
6378
  });
6338
6379
  import path18 from "path";
6339
- import fs13 from "fs";
6380
+ import fs15 from "fs";
6340
6381
  async function buildCommand(options) {
6341
6382
  const rootDir = options?.rootDir ?? process.cwd();
6342
6383
  const outdir = options?.dist ?? DEFAULT_DIST2;
@@ -6399,7 +6440,7 @@ loadEnv(process.cwd());
6399
6440
  const app = await createProdApp(${createProdAppArgs});
6400
6441
  await app.listen();
6401
6442
  `;
6402
- await fs13.promises.writeFile(mainPath, mainContent, "utf-8");
6443
+ await fs15.promises.writeFile(mainPath, mainContent, "utf-8");
6403
6444
  console.log(` Written to ${mainPath}`);
6404
6445
  console.log("\nfaapi build completed");
6405
6446
  }