@faapi/faapi 3.0.0 → 3.2.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/testing.js CHANGED
@@ -103,6 +103,144 @@ function createSseWriter() {
103
103
  return writer;
104
104
  }
105
105
 
106
+ // src/errors/FaapiError.ts
107
+ var FaapiError = class extends Error {
108
+ constructor(code, message, statusCode) {
109
+ super(message);
110
+ this.code = code;
111
+ this.statusCode = statusCode;
112
+ this.name = "FaapiError";
113
+ }
114
+ code;
115
+ statusCode;
116
+ };
117
+
118
+ // src/errors/httpErrors.ts
119
+ function deriveStatusCode(issues) {
120
+ const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
121
+ return has400 ? 400 : 422;
122
+ }
123
+ var ValidationError = class extends FaapiError {
124
+ constructor(message, issues) {
125
+ super("VALIDATION_ERROR", message, deriveStatusCode(issues));
126
+ this.issues = issues;
127
+ this.name = "ValidationError";
128
+ }
129
+ issues;
130
+ };
131
+ var RouteNotFoundError = class extends FaapiError {
132
+ constructor(path11) {
133
+ super("ROUTE_NOT_FOUND", `Route not found: ${path11}`, 404);
134
+ this.name = "RouteNotFoundError";
135
+ }
136
+ };
137
+ var MethodNotAllowedError = class extends FaapiError {
138
+ constructor(method, path11, allowedMethods) {
139
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path11}`, 405);
140
+ this.allowedMethods = allowedMethods;
141
+ this.name = "MethodNotAllowedError";
142
+ }
143
+ allowedMethods;
144
+ };
145
+ var InternalError = class extends FaapiError {
146
+ constructor(message) {
147
+ super("INTERNAL_ERROR", message, 500);
148
+ this.name = "InternalError";
149
+ }
150
+ };
151
+ var PayloadTooLargeError = class extends FaapiError {
152
+ constructor(maxSize) {
153
+ super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
154
+ this.name = "PayloadTooLargeError";
155
+ }
156
+ };
157
+
158
+ // src/response/responseFormatter.ts
159
+ function defaultOk(data) {
160
+ return { data };
161
+ }
162
+ function defaultFail(e) {
163
+ const error = { message: e.message };
164
+ if (e.code !== void 0) error.code = e.code;
165
+ return { error };
166
+ }
167
+ function getResponseConfig(config) {
168
+ return config?.response;
169
+ }
170
+ function resolveOkFn(config) {
171
+ return getResponseConfig(config)?.ok ?? defaultOk;
172
+ }
173
+ function resolveFailFn(config) {
174
+ return getResponseConfig(config)?.fail ?? defaultFail;
175
+ }
176
+ function jsonOk(body, status = 200, extraHeaders) {
177
+ return jsonRaw(body, status, extraHeaders);
178
+ }
179
+ function jsonRaw(body, status, extraHeaders) {
180
+ const headers = new Headers({ "Content-Type": "application/json" });
181
+ if (extraHeaders) {
182
+ const extra = new Headers(extraHeaders);
183
+ extra.forEach((value, key) => headers.set(key, value));
184
+ }
185
+ return new Response(JSON.stringify(body), { status, headers });
186
+ }
187
+ function wrapOkResult(result, config) {
188
+ if (result instanceof Response) return result;
189
+ return resolveOkFn(config)(result);
190
+ }
191
+ function formatFailResponse(options, config) {
192
+ const failFn = resolveFailFn(config);
193
+ const body = failFn({
194
+ status: options.status,
195
+ code: options.code,
196
+ message: options.message
197
+ });
198
+ return jsonOk(body, options.status ?? 500);
199
+ }
200
+ function formatErrorResponse(error, config) {
201
+ const failFn = resolveFailFn(config);
202
+ if (error instanceof ValidationError) {
203
+ const body2 = failFn({
204
+ status: error.statusCode,
205
+ code: error.code,
206
+ message: error.message
207
+ });
208
+ const bodyObj = typeof body2 === "object" && body2 !== null ? body2 : { error: body2 };
209
+ const errorObj = bodyObj.error ?? bodyObj;
210
+ if (errorObj) {
211
+ errorObj.issues = error.issues;
212
+ }
213
+ return jsonOk(bodyObj, error.statusCode);
214
+ }
215
+ if (error instanceof MethodNotAllowedError) {
216
+ const body2 = failFn({
217
+ status: error.statusCode,
218
+ code: error.code,
219
+ message: error.message
220
+ });
221
+ return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
222
+ }
223
+ if (error instanceof PayloadTooLargeError) {
224
+ const body2 = failFn({
225
+ status: error.statusCode,
226
+ code: error.code,
227
+ message: error.message
228
+ });
229
+ return jsonOk(body2, error.statusCode);
230
+ }
231
+ if (error instanceof FaapiError) {
232
+ const body2 = failFn({
233
+ status: error.statusCode,
234
+ code: error.code,
235
+ message: error.message
236
+ });
237
+ return jsonOk(body2, error.statusCode);
238
+ }
239
+ const message = error instanceof Error ? error.message : "An unknown error occurred";
240
+ const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
241
+ return jsonOk(body, 500);
242
+ }
243
+
106
244
  // src/runtime/createContext.ts
107
245
  function parseCookies(cookieHeader) {
108
246
  const cookies = /* @__PURE__ */ new Map();
@@ -163,11 +301,7 @@ function createContext(request, params, config = {}, ip = "") {
163
301
  });
164
302
  },
165
303
  json(data, status) {
166
- const headers = { "Content-Type": "application/json" };
167
- return new Response(JSON.stringify(data), {
168
- status: status ?? 200,
169
- headers
170
- });
304
+ return jsonOk(data, status ?? 200);
171
305
  },
172
306
  html(html, status) {
173
307
  const headers = { "Content-Type": "text/html; charset=utf-8" };
@@ -203,18 +337,22 @@ function createContext(request, params, config = {}, ip = "") {
203
337
  /**
204
338
  * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
205
339
  *
340
+ * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),
341
+ * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。
342
+ *
206
343
  * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
207
344
  * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
208
345
  */
209
346
  ok(data) {
210
- const responseConfig = config.response;
211
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
212
- const body = okFn(data);
213
- return ctx.json(body);
347
+ const body = wrapOkResult(data, config);
348
+ return jsonOk(body, 200);
214
349
  },
215
350
  /**
216
351
  * 返回错误响应(对象形式参数,status 和 code 均可省略)
217
352
  *
353
+ * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),
354
+ * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。
355
+ *
218
356
  * - status 省略时 HTTP 状态码默认 500
219
357
  * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
220
358
  * - status 和 code 独立无关联
@@ -222,18 +360,7 @@ function createContext(request, params, config = {}, ip = "") {
222
360
  * body 用 config.response.fail(或默认实现)包装。
223
361
  */
224
362
  fail(options) {
225
- const responseConfig = config.response;
226
- const failFn = responseConfig?.fail ?? ((e) => {
227
- const error = { message: e.message };
228
- if (e.code !== void 0) error.code = e.code;
229
- return { error };
230
- });
231
- const body = failFn({
232
- status: options.status,
233
- code: options.code,
234
- message: options.message
235
- });
236
- return ctx.json(body, options.status ?? 500);
363
+ return formatFailResponse(options, config);
237
364
  }
238
365
  };
239
366
  const extend = config?.extendContext;
@@ -466,7 +593,9 @@ function queryToObject(params) {
466
593
  // src/injection/agentRegistry.ts
467
594
  var registry = /* @__PURE__ */ new Map();
468
595
  function listAgents() {
469
- return Array.from(registry.values());
596
+ const merged = /* @__PURE__ */ new Map();
597
+ for (const agent of registry.values()) merged.set(agent.name, agent);
598
+ return Array.from(merged.values());
470
599
  }
471
600
 
472
601
  // src/injection/agentHandle.ts
@@ -540,10 +669,7 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
540
669
 
541
670
  // src/runtime/invokeHandler.ts
542
671
  function wrapResult(result, ctx) {
543
- if (result instanceof Response) return result;
544
- const responseConfig = ctx.config.response;
545
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
546
- return okFn(result);
672
+ return wrapOkResult(result, ctx.config);
547
673
  }
548
674
  function mergeMeta(response, meta) {
549
675
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
@@ -726,37 +852,42 @@ function setCachedMiddlewares(absPath, bundle) {
726
852
  middlewareCache.set(absPath, bundle);
727
853
  }
728
854
  async function loadMiddlewaresFile(filePath) {
855
+ let module;
729
856
  try {
730
- const module = await importWithCacheBust(filePath);
731
- const middlewares = module.default ?? module.middlewares ?? [];
732
- if (!Array.isArray(middlewares)) {
733
- console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
734
- return { middlewares: [], injectors: {} };
735
- }
736
- const validMiddlewares = middlewares.filter((m) => {
737
- if (typeof m !== "function") {
738
- console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
739
- return false;
740
- }
741
- return true;
742
- });
743
- const injectors = module.injectors ?? {};
744
- if (typeof injectors !== "object" || injectors === null) {
745
- console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
746
- return { middlewares: validMiddlewares, injectors: {} };
747
- }
748
- const validInjectors = {};
749
- for (const [name, injector] of Object.entries(injectors)) {
750
- if (typeof injector !== "function") {
751
- console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
752
- continue;
753
- }
754
- validInjectors[name] = injector;
755
- }
756
- return { middlewares: validMiddlewares, injectors: validInjectors };
757
- } catch {
857
+ module = await importWithCacheBust(filePath);
858
+ } catch (err) {
859
+ console.error(
860
+ `[faapi] Failed to load middlewares from ${filePath}:`,
861
+ err instanceof Error ? err.stack ?? err.message : err
862
+ );
863
+ return { middlewares: [], injectors: {} };
864
+ }
865
+ const middlewares = module.default ?? module.middlewares ?? [];
866
+ if (!Array.isArray(middlewares)) {
867
+ console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
758
868
  return { middlewares: [], injectors: {} };
759
869
  }
870
+ const validMiddlewares = middlewares.filter((m) => {
871
+ if (typeof m !== "function") {
872
+ console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
873
+ return false;
874
+ }
875
+ return true;
876
+ });
877
+ const injectors = module.injectors ?? {};
878
+ if (typeof injectors !== "object" || injectors === null) {
879
+ console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
880
+ return { middlewares: validMiddlewares, injectors: {} };
881
+ }
882
+ const validInjectors = {};
883
+ for (const [name, injector] of Object.entries(injectors)) {
884
+ if (typeof injector !== "function") {
885
+ console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
886
+ continue;
887
+ }
888
+ validInjectors[name] = injector;
889
+ }
890
+ return { middlewares: validMiddlewares, injectors: validInjectors };
760
891
  }
761
892
  async function loadMergedMiddlewares(middlewarePaths) {
762
893
  if (middlewarePaths.length === 0) return void 0;
@@ -2072,52 +2203,6 @@ async function writeSchemaFile(outputPath, source) {
2072
2203
  await fs2.writeFile(outputPath, source, "utf-8");
2073
2204
  }
2074
2205
 
2075
- // src/errors/FaapiError.ts
2076
- var FaapiError = class extends Error {
2077
- constructor(code, message, statusCode) {
2078
- super(message);
2079
- this.code = code;
2080
- this.statusCode = statusCode;
2081
- this.name = "FaapiError";
2082
- }
2083
- code;
2084
- statusCode;
2085
- };
2086
-
2087
- // src/errors/httpErrors.ts
2088
- function deriveStatusCode(issues) {
2089
- const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
2090
- return has400 ? 400 : 422;
2091
- }
2092
- var ValidationError = class extends FaapiError {
2093
- constructor(message, issues) {
2094
- super("VALIDATION_ERROR", message, deriveStatusCode(issues));
2095
- this.issues = issues;
2096
- this.name = "ValidationError";
2097
- }
2098
- issues;
2099
- };
2100
- var RouteNotFoundError = class extends FaapiError {
2101
- constructor(path11) {
2102
- super("ROUTE_NOT_FOUND", `Route not found: ${path11}`, 404);
2103
- this.name = "RouteNotFoundError";
2104
- }
2105
- };
2106
- var MethodNotAllowedError = class extends FaapiError {
2107
- constructor(method, path11, allowedMethods) {
2108
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path11}`, 405);
2109
- this.allowedMethods = allowedMethods;
2110
- this.name = "MethodNotAllowedError";
2111
- }
2112
- allowedMethods;
2113
- };
2114
- var InternalError = class extends FaapiError {
2115
- constructor(message) {
2116
- super("INTERNAL_ERROR", message, 500);
2117
- this.name = "InternalError";
2118
- }
2119
- };
2120
-
2121
2206
  // src/cli/compileOnDemand.ts
2122
2207
  import path7 from "path";
2123
2208
  import fs6 from "fs";
@@ -2371,9 +2456,25 @@ function isProductFresh(sourceAbsPath, productAbsPath) {
2371
2456
  return false;
2372
2457
  }
2373
2458
  }
2374
- var compiledFiles = /* @__PURE__ */ new Set();
2459
+ function createDevOnDemandState() {
2460
+ return {
2461
+ enabled: false,
2462
+ distDir: void 0,
2463
+ compiledFiles: /* @__PURE__ */ new Set(),
2464
+ generatedSchemas: /* @__PURE__ */ new Set(),
2465
+ inFlightCompilations: /* @__PURE__ */ new Map(),
2466
+ inFlightSchemaGenerations: /* @__PURE__ */ new Map()
2467
+ };
2468
+ }
2469
+ var state = createDevOnDemandState();
2375
2470
  async function ensureCompiled(sourceAbsPath, rootDir, dist) {
2376
- if (compiledFiles.has(sourceAbsPath)) {
2471
+ const inFlight = state.inFlightCompilations.get(sourceAbsPath);
2472
+ if (inFlight) {
2473
+ await inFlight.catch(() => {
2474
+ });
2475
+ return false;
2476
+ }
2477
+ if (state.compiledFiles.has(sourceAbsPath)) {
2377
2478
  return false;
2378
2479
  }
2379
2480
  if (!fs6.existsSync(sourceAbsPath)) {
@@ -2381,17 +2482,25 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
2381
2482
  }
2382
2483
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
2383
2484
  if (productPath && isProductFresh(sourceAbsPath, productPath)) {
2384
- compiledFiles.add(sourceAbsPath);
2485
+ state.compiledFiles.add(sourceAbsPath);
2385
2486
  return false;
2386
2487
  }
2387
- await compileDevRoutes({
2388
- rootDir,
2389
- dist,
2390
- files: [sourceAbsPath],
2391
- logLevel: "silent"
2392
- });
2393
- compiledFiles.add(sourceAbsPath);
2394
- return true;
2488
+ const compilePromise = (async () => {
2489
+ await compileDevRoutes({
2490
+ rootDir,
2491
+ dist,
2492
+ files: [sourceAbsPath],
2493
+ logLevel: "silent"
2494
+ });
2495
+ state.compiledFiles.add(sourceAbsPath);
2496
+ })();
2497
+ state.inFlightCompilations.set(sourceAbsPath, compilePromise);
2498
+ try {
2499
+ await compilePromise;
2500
+ return true;
2501
+ } finally {
2502
+ state.inFlightCompilations.delete(sourceAbsPath);
2503
+ }
2395
2504
  }
2396
2505
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
2397
2506
  const rel = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
@@ -2400,9 +2509,14 @@ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
2400
2509
  const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
2401
2510
  return path7.resolve(rootDir, dist, jsRel);
2402
2511
  }
2403
- var generatedSchemas = /* @__PURE__ */ new Set();
2404
2512
  async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
2405
- if (generatedSchemas.has(schemaPath)) {
2513
+ const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
2514
+ if (inFlight) {
2515
+ await inFlight.catch(() => {
2516
+ });
2517
+ return false;
2518
+ }
2519
+ if (state.generatedSchemas.has(schemaPath)) {
2406
2520
  return false;
2407
2521
  }
2408
2522
  const prodAbsPath = path7.resolve(rootDir, routeFilePath);
@@ -2411,7 +2525,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2411
2525
  return false;
2412
2526
  }
2413
2527
  if (isProductFresh(sourceAbsPath, schemaPath)) {
2414
- generatedSchemas.add(schemaPath);
2528
+ state.generatedSchemas.add(schemaPath);
2415
2529
  return false;
2416
2530
  }
2417
2531
  const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
@@ -2420,9 +2534,17 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2420
2534
  }
2421
2535
  const sourceRelPath = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2422
2536
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
2423
- await generateSchemaFiles(sourceRoutes, rootDir, dist);
2424
- generatedSchemas.add(schemaPath);
2425
- return true;
2537
+ const generatePromise = (async () => {
2538
+ await generateSchemaFiles(sourceRoutes, rootDir, dist);
2539
+ state.generatedSchemas.add(schemaPath);
2540
+ })();
2541
+ state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
2542
+ try {
2543
+ await generatePromise;
2544
+ return true;
2545
+ } finally {
2546
+ state.inFlightSchemaGenerations.delete(schemaPath);
2547
+ }
2426
2548
  }
2427
2549
  function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
2428
2550
  const rel = path7.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
@@ -2436,13 +2558,11 @@ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
2436
2558
  if (fs6.existsSync(tsAbs)) return tsAbs;
2437
2559
  return path7.resolve(rootDir, srcRel);
2438
2560
  }
2439
- var devOnDemandEnabled = false;
2440
2561
  function isDevOnDemandEnabled() {
2441
- return devOnDemandEnabled;
2562
+ return state.enabled;
2442
2563
  }
2443
- var devDistDir;
2444
2564
  function getDevDist() {
2445
- return devDistDir;
2565
+ return state.distDir;
2446
2566
  }
2447
2567
 
2448
2568
  // src/validator/validateInput.ts
@@ -2965,52 +3085,6 @@ import fs8 from "fs";
2965
3085
  import { WebSocketServer, WebSocket } from "ws";
2966
3086
  import path8 from "path";
2967
3087
 
2968
- // src/errors/formatErrorResponse.ts
2969
- function formatErrorResponse(error) {
2970
- if (error instanceof ValidationError) {
2971
- const body2 = {
2972
- code: error.code,
2973
- message: error.message,
2974
- issues: error.issues
2975
- };
2976
- return new Response(JSON.stringify({ error: body2 }), {
2977
- status: error.statusCode,
2978
- headers: { "Content-Type": "application/json" }
2979
- });
2980
- }
2981
- if (error instanceof MethodNotAllowedError) {
2982
- const body2 = {
2983
- code: error.code,
2984
- message: error.message
2985
- };
2986
- return new Response(JSON.stringify({ error: body2 }), {
2987
- status: error.statusCode,
2988
- headers: {
2989
- "Content-Type": "application/json",
2990
- Allow: error.allowedMethods.join(", ")
2991
- }
2992
- });
2993
- }
2994
- if (error instanceof FaapiError) {
2995
- const body2 = {
2996
- code: error.code,
2997
- message: error.message
2998
- };
2999
- return new Response(JSON.stringify({ error: body2 }), {
3000
- status: error.statusCode,
3001
- headers: { "Content-Type": "application/json" }
3002
- });
3003
- }
3004
- const body = {
3005
- code: "INTERNAL_ERROR",
3006
- message: error instanceof Error ? error.message : "An unknown error occurred"
3007
- };
3008
- return new Response(JSON.stringify({ error: body }), {
3009
- status: 500,
3010
- headers: { "Content-Type": "application/json" }
3011
- });
3012
- }
3013
-
3014
3088
  // src/server/serverUtils.ts
3015
3089
  function nodeHttpToWebHeaders(req) {
3016
3090
  const headers = new Headers();
@@ -3024,9 +3098,9 @@ function nodeHttpToWebHeaders(req) {
3024
3098
  }
3025
3099
  return headers;
3026
3100
  }
3027
- function buildErrorResponse(err) {
3101
+ function buildErrorResponse(err, config) {
3028
3102
  try {
3029
- return formatErrorResponse(err);
3103
+ return formatErrorResponse(err, config);
3030
3104
  } catch {
3031
3105
  return new Response(
3032
3106
  JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
@@ -3221,25 +3295,51 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
3221
3295
  }
3222
3296
  function limitStreamSize(stream, maxSize) {
3223
3297
  let totalSize = 0;
3224
- const reader = stream.getReader();
3225
- return new ReadableStream({
3226
- async pull(controller) {
3227
- const { done, value } = await reader.read();
3228
- if (done) {
3229
- controller.close();
3298
+ let reader;
3299
+ let errored = false;
3300
+ const releaseReader = () => {
3301
+ if (reader) {
3302
+ try {
3230
3303
  reader.releaseLock();
3231
- return;
3304
+ } catch {
3232
3305
  }
3233
- totalSize += value.byteLength;
3234
- if (totalSize > maxSize) {
3235
- controller.error(new Error(`\u8BF7\u6C42\u4F53\u8D85\u8FC7\u5927\u5C0F\u9650\u5236 ${maxSize} \u5B57\u8282`));
3236
- reader.releaseLock();
3237
- return;
3306
+ reader = void 0;
3307
+ }
3308
+ };
3309
+ const failStream = (controller, err) => {
3310
+ if (errored) return;
3311
+ errored = true;
3312
+ controller.error(err instanceof Error ? err : new Error(String(err)));
3313
+ releaseReader();
3314
+ };
3315
+ return new ReadableStream({
3316
+ async pull(controller) {
3317
+ if (!reader) reader = stream.getReader();
3318
+ try {
3319
+ const { done, value } = await reader.read();
3320
+ if (done) {
3321
+ controller.close();
3322
+ releaseReader();
3323
+ return;
3324
+ }
3325
+ totalSize += value.byteLength;
3326
+ if (totalSize > maxSize) {
3327
+ failStream(controller, new PayloadTooLargeError(maxSize));
3328
+ return;
3329
+ }
3330
+ controller.enqueue(value);
3331
+ } catch (err) {
3332
+ failStream(controller, err);
3238
3333
  }
3239
- controller.enqueue(value);
3240
3334
  },
3241
3335
  cancel(reason) {
3242
- reader.cancel(reason);
3336
+ if (reader) {
3337
+ try {
3338
+ reader.cancel(reason);
3339
+ } catch {
3340
+ }
3341
+ releaseReader();
3342
+ }
3243
3343
  }
3244
3344
  });
3245
3345
  }
@@ -3320,21 +3420,27 @@ function createServer(options) {
3320
3420
  }
3321
3421
  return { server, routesRef };
3322
3422
  }
3323
- async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
3423
+ function prepareRequest(req, config, bodyLimit) {
3324
3424
  const request = toWebRequest(req, bodyLimit);
3325
3425
  const method = request.method.toUpperCase();
3326
3426
  const urlPath = new URL(request.url).pathname;
3327
3427
  const ctx = createContext(request, {}, config, getClientIp(req));
3328
3428
  const meta = ctx.meta;
3329
- const routePipeline = async () => {
3330
- const match = matchRoute(routes, method, urlPath);
3331
- if (!match) {
3332
- const allowedMethods = findAllowedMethods(routes, urlPath);
3333
- if (allowedMethods.length > 0) {
3334
- throw new MethodNotAllowedError(method, urlPath, allowedMethods);
3335
- }
3336
- throw new RouteNotFoundError(urlPath);
3337
- }
3429
+ return { request, ctx, meta, method, urlPath };
3430
+ }
3431
+ function resolveRouteOrThrow(routes, method, urlPath) {
3432
+ const match = matchRoute(routes, method, urlPath);
3433
+ if (match) return match;
3434
+ const allowedMethods = findAllowedMethods(routes, urlPath);
3435
+ if (allowedMethods.length > 0) {
3436
+ throw new MethodNotAllowedError(method, urlPath, allowedMethods);
3437
+ }
3438
+ throw new RouteNotFoundError(urlPath);
3439
+ }
3440
+ function createRoutePipeline(opts) {
3441
+ const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
3442
+ return async () => {
3443
+ const match = resolveRouteOrThrow(routes, method, urlPath);
3338
3444
  ctx.params = match.params;
3339
3445
  const { route } = match;
3340
3446
  const absoluteFilePath = path9.resolve(rootDir, route.filePath);
@@ -3364,37 +3470,44 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
3364
3470
  }
3365
3471
  }
3366
3472
  const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
3367
- const response = await invokeHandler(
3368
- routeModule.handler,
3369
- ctx,
3370
- body,
3371
- route.middlewares,
3372
- mergedInjectors
3373
- );
3374
- return response;
3473
+ return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
3375
3474
  };
3376
- try {
3377
- let response;
3378
- const outerMiddlewares = [];
3379
- if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
3380
- if (globalMiddlewares && globalMiddlewares.length > 0) {
3381
- outerMiddlewares.push(...globalMiddlewares);
3382
- }
3383
- if (outerMiddlewares.length > 0) {
3384
- response = await compose(outerMiddlewares, ctx, routePipeline);
3385
- } else {
3386
- response = await routePipeline();
3475
+ }
3476
+ async function sendSuccessResponse(response, res) {
3477
+ await sendNodeResponse(response, res);
3478
+ }
3479
+ async function sendErrorResponse(err, meta, res, onError, ctx) {
3480
+ await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
3481
+ if (onError) {
3482
+ try {
3483
+ await onError(err, ctx);
3484
+ } catch {
3387
3485
  }
3388
- await sendNodeResponse(response, res);
3486
+ }
3487
+ }
3488
+ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
3489
+ const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
3490
+ const routePipeline = createRoutePipeline({
3491
+ routes,
3492
+ method,
3493
+ urlPath,
3494
+ ctx,
3495
+ request,
3496
+ rootDir,
3497
+ dist,
3498
+ globalMiddlewares,
3499
+ globalInjectors
3500
+ });
3501
+ const outerMiddlewares = [];
3502
+ if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
3503
+ if (globalMiddlewares && globalMiddlewares.length > 0) {
3504
+ outerMiddlewares.push(...globalMiddlewares);
3505
+ }
3506
+ try {
3507
+ const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
3508
+ await sendSuccessResponse(response, res);
3389
3509
  } catch (err) {
3390
- const errorResponse = buildErrorResponse(err);
3391
- await sendNodeResponse(mergeMeta(errorResponse, meta), res);
3392
- if (onError) {
3393
- try {
3394
- await onError(err, ctx);
3395
- } catch {
3396
- }
3397
- }
3510
+ await sendErrorResponse(err, meta, res, onError, ctx);
3398
3511
  }
3399
3512
  }
3400
3513