@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/cli/index.js +381 -250
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +287 -95
- package/dist/index.js +336 -219
- package/dist/index.js.map +1 -1
- package/dist/testing.js +334 -221
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
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
|
|
2562
|
+
return state.enabled;
|
|
2442
2563
|
}
|
|
2443
|
-
var devDistDir;
|
|
2444
2564
|
function getDevDist() {
|
|
2445
|
-
return
|
|
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
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
controller.close();
|
|
3298
|
+
let reader;
|
|
3299
|
+
let errored = false;
|
|
3300
|
+
const releaseReader = () => {
|
|
3301
|
+
if (reader) {
|
|
3302
|
+
try {
|
|
3230
3303
|
reader.releaseLock();
|
|
3231
|
-
|
|
3304
|
+
} catch {
|
|
3232
3305
|
}
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|