@faapi/faapi 3.0.0 → 3.1.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 +390 -254
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +265 -95
- package/dist/index.js +341 -223
- package/dist/index.js.map +1 -1
- package/dist/testing.js +342 -222
- 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;
|
|
@@ -463,12 +590,21 @@ function queryToObject(params) {
|
|
|
463
590
|
return result;
|
|
464
591
|
}
|
|
465
592
|
|
|
466
|
-
// src/injection/
|
|
593
|
+
// src/injection/skillRegistry.ts
|
|
467
594
|
var registry = /* @__PURE__ */ new Map();
|
|
468
|
-
function
|
|
595
|
+
function listSkills() {
|
|
469
596
|
return Array.from(registry.values());
|
|
470
597
|
}
|
|
471
598
|
|
|
599
|
+
// src/injection/agentRegistry.ts
|
|
600
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
601
|
+
function listAgents() {
|
|
602
|
+
const merged = /* @__PURE__ */ new Map();
|
|
603
|
+
for (const agent of registry2.values()) merged.set(agent.name, agent);
|
|
604
|
+
for (const skill of listSkills()) merged.set(skill.name, skill);
|
|
605
|
+
return Array.from(merged.values());
|
|
606
|
+
}
|
|
607
|
+
|
|
472
608
|
// src/injection/agentHandle.ts
|
|
473
609
|
var currentFactory = null;
|
|
474
610
|
function getAgentHandle(ctx) {
|
|
@@ -540,10 +676,7 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
|
|
|
540
676
|
|
|
541
677
|
// src/runtime/invokeHandler.ts
|
|
542
678
|
function wrapResult(result, ctx) {
|
|
543
|
-
|
|
544
|
-
const responseConfig = ctx.config.response;
|
|
545
|
-
const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
|
|
546
|
-
return okFn(result);
|
|
679
|
+
return wrapOkResult(result, ctx.config);
|
|
547
680
|
}
|
|
548
681
|
function mergeMeta(response, meta) {
|
|
549
682
|
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
@@ -726,37 +859,42 @@ function setCachedMiddlewares(absPath, bundle) {
|
|
|
726
859
|
middlewareCache.set(absPath, bundle);
|
|
727
860
|
}
|
|
728
861
|
async function loadMiddlewaresFile(filePath) {
|
|
862
|
+
let module;
|
|
729
863
|
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 {
|
|
864
|
+
module = await importWithCacheBust(filePath);
|
|
865
|
+
} catch (err) {
|
|
866
|
+
console.error(
|
|
867
|
+
`[faapi] Failed to load middlewares from ${filePath}:`,
|
|
868
|
+
err instanceof Error ? err.stack ?? err.message : err
|
|
869
|
+
);
|
|
870
|
+
return { middlewares: [], injectors: {} };
|
|
871
|
+
}
|
|
872
|
+
const middlewares = module.default ?? module.middlewares ?? [];
|
|
873
|
+
if (!Array.isArray(middlewares)) {
|
|
874
|
+
console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
758
875
|
return { middlewares: [], injectors: {} };
|
|
759
876
|
}
|
|
877
|
+
const validMiddlewares = middlewares.filter((m) => {
|
|
878
|
+
if (typeof m !== "function") {
|
|
879
|
+
console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
return true;
|
|
883
|
+
});
|
|
884
|
+
const injectors = module.injectors ?? {};
|
|
885
|
+
if (typeof injectors !== "object" || injectors === null) {
|
|
886
|
+
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
887
|
+
return { middlewares: validMiddlewares, injectors: {} };
|
|
888
|
+
}
|
|
889
|
+
const validInjectors = {};
|
|
890
|
+
for (const [name, injector] of Object.entries(injectors)) {
|
|
891
|
+
if (typeof injector !== "function") {
|
|
892
|
+
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
validInjectors[name] = injector;
|
|
896
|
+
}
|
|
897
|
+
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
760
898
|
}
|
|
761
899
|
async function loadMergedMiddlewares(middlewarePaths) {
|
|
762
900
|
if (middlewarePaths.length === 0) return void 0;
|
|
@@ -2072,52 +2210,6 @@ async function writeSchemaFile(outputPath, source) {
|
|
|
2072
2210
|
await fs2.writeFile(outputPath, source, "utf-8");
|
|
2073
2211
|
}
|
|
2074
2212
|
|
|
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
2213
|
// src/cli/compileOnDemand.ts
|
|
2122
2214
|
import path7 from "path";
|
|
2123
2215
|
import fs6 from "fs";
|
|
@@ -2371,9 +2463,25 @@ function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
|
2371
2463
|
return false;
|
|
2372
2464
|
}
|
|
2373
2465
|
}
|
|
2374
|
-
|
|
2466
|
+
function createDevOnDemandState() {
|
|
2467
|
+
return {
|
|
2468
|
+
enabled: false,
|
|
2469
|
+
distDir: void 0,
|
|
2470
|
+
compiledFiles: /* @__PURE__ */ new Set(),
|
|
2471
|
+
generatedSchemas: /* @__PURE__ */ new Set(),
|
|
2472
|
+
inFlightCompilations: /* @__PURE__ */ new Map(),
|
|
2473
|
+
inFlightSchemaGenerations: /* @__PURE__ */ new Map()
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
var state = createDevOnDemandState();
|
|
2375
2477
|
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
2376
|
-
|
|
2478
|
+
const inFlight = state.inFlightCompilations.get(sourceAbsPath);
|
|
2479
|
+
if (inFlight) {
|
|
2480
|
+
await inFlight.catch(() => {
|
|
2481
|
+
});
|
|
2482
|
+
return false;
|
|
2483
|
+
}
|
|
2484
|
+
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
2377
2485
|
return false;
|
|
2378
2486
|
}
|
|
2379
2487
|
if (!fs6.existsSync(sourceAbsPath)) {
|
|
@@ -2381,17 +2489,25 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
|
2381
2489
|
}
|
|
2382
2490
|
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
2383
2491
|
if (productPath && isProductFresh(sourceAbsPath, productPath)) {
|
|
2384
|
-
compiledFiles.add(sourceAbsPath);
|
|
2492
|
+
state.compiledFiles.add(sourceAbsPath);
|
|
2385
2493
|
return false;
|
|
2386
2494
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2495
|
+
const compilePromise = (async () => {
|
|
2496
|
+
await compileDevRoutes({
|
|
2497
|
+
rootDir,
|
|
2498
|
+
dist,
|
|
2499
|
+
files: [sourceAbsPath],
|
|
2500
|
+
logLevel: "silent"
|
|
2501
|
+
});
|
|
2502
|
+
state.compiledFiles.add(sourceAbsPath);
|
|
2503
|
+
})();
|
|
2504
|
+
state.inFlightCompilations.set(sourceAbsPath, compilePromise);
|
|
2505
|
+
try {
|
|
2506
|
+
await compilePromise;
|
|
2507
|
+
return true;
|
|
2508
|
+
} finally {
|
|
2509
|
+
state.inFlightCompilations.delete(sourceAbsPath);
|
|
2510
|
+
}
|
|
2395
2511
|
}
|
|
2396
2512
|
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2397
2513
|
const rel = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
@@ -2400,9 +2516,14 @@ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
|
2400
2516
|
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2401
2517
|
return path7.resolve(rootDir, dist, jsRel);
|
|
2402
2518
|
}
|
|
2403
|
-
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
2404
2519
|
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
2405
|
-
|
|
2520
|
+
const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
|
|
2521
|
+
if (inFlight) {
|
|
2522
|
+
await inFlight.catch(() => {
|
|
2523
|
+
});
|
|
2524
|
+
return false;
|
|
2525
|
+
}
|
|
2526
|
+
if (state.generatedSchemas.has(schemaPath)) {
|
|
2406
2527
|
return false;
|
|
2407
2528
|
}
|
|
2408
2529
|
const prodAbsPath = path7.resolve(rootDir, routeFilePath);
|
|
@@ -2411,7 +2532,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
2411
2532
|
return false;
|
|
2412
2533
|
}
|
|
2413
2534
|
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
2414
|
-
generatedSchemas.add(schemaPath);
|
|
2535
|
+
state.generatedSchemas.add(schemaPath);
|
|
2415
2536
|
return false;
|
|
2416
2537
|
}
|
|
2417
2538
|
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
@@ -2420,9 +2541,17 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
2420
2541
|
}
|
|
2421
2542
|
const sourceRelPath = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2422
2543
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2544
|
+
const generatePromise = (async () => {
|
|
2545
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
2546
|
+
state.generatedSchemas.add(schemaPath);
|
|
2547
|
+
})();
|
|
2548
|
+
state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
|
|
2549
|
+
try {
|
|
2550
|
+
await generatePromise;
|
|
2551
|
+
return true;
|
|
2552
|
+
} finally {
|
|
2553
|
+
state.inFlightSchemaGenerations.delete(schemaPath);
|
|
2554
|
+
}
|
|
2426
2555
|
}
|
|
2427
2556
|
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2428
2557
|
const rel = path7.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
@@ -2436,13 +2565,11 @@ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
|
2436
2565
|
if (fs6.existsSync(tsAbs)) return tsAbs;
|
|
2437
2566
|
return path7.resolve(rootDir, srcRel);
|
|
2438
2567
|
}
|
|
2439
|
-
var devOnDemandEnabled = false;
|
|
2440
2568
|
function isDevOnDemandEnabled() {
|
|
2441
|
-
return
|
|
2569
|
+
return state.enabled;
|
|
2442
2570
|
}
|
|
2443
|
-
var devDistDir;
|
|
2444
2571
|
function getDevDist() {
|
|
2445
|
-
return
|
|
2572
|
+
return state.distDir;
|
|
2446
2573
|
}
|
|
2447
2574
|
|
|
2448
2575
|
// src/validator/validateInput.ts
|
|
@@ -2965,52 +3092,6 @@ import fs8 from "fs";
|
|
|
2965
3092
|
import { WebSocketServer, WebSocket } from "ws";
|
|
2966
3093
|
import path8 from "path";
|
|
2967
3094
|
|
|
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
3095
|
// src/server/serverUtils.ts
|
|
3015
3096
|
function nodeHttpToWebHeaders(req) {
|
|
3016
3097
|
const headers = new Headers();
|
|
@@ -3024,9 +3105,9 @@ function nodeHttpToWebHeaders(req) {
|
|
|
3024
3105
|
}
|
|
3025
3106
|
return headers;
|
|
3026
3107
|
}
|
|
3027
|
-
function buildErrorResponse(err) {
|
|
3108
|
+
function buildErrorResponse(err, config) {
|
|
3028
3109
|
try {
|
|
3029
|
-
return formatErrorResponse(err);
|
|
3110
|
+
return formatErrorResponse(err, config);
|
|
3030
3111
|
} catch {
|
|
3031
3112
|
return new Response(
|
|
3032
3113
|
JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
|
|
@@ -3221,25 +3302,51 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
3221
3302
|
}
|
|
3222
3303
|
function limitStreamSize(stream, maxSize) {
|
|
3223
3304
|
let totalSize = 0;
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
controller.close();
|
|
3305
|
+
let reader;
|
|
3306
|
+
let errored = false;
|
|
3307
|
+
const releaseReader = () => {
|
|
3308
|
+
if (reader) {
|
|
3309
|
+
try {
|
|
3230
3310
|
reader.releaseLock();
|
|
3231
|
-
|
|
3311
|
+
} catch {
|
|
3232
3312
|
}
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3313
|
+
reader = void 0;
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
3316
|
+
const failStream = (controller, err) => {
|
|
3317
|
+
if (errored) return;
|
|
3318
|
+
errored = true;
|
|
3319
|
+
controller.error(err instanceof Error ? err : new Error(String(err)));
|
|
3320
|
+
releaseReader();
|
|
3321
|
+
};
|
|
3322
|
+
return new ReadableStream({
|
|
3323
|
+
async pull(controller) {
|
|
3324
|
+
if (!reader) reader = stream.getReader();
|
|
3325
|
+
try {
|
|
3326
|
+
const { done, value } = await reader.read();
|
|
3327
|
+
if (done) {
|
|
3328
|
+
controller.close();
|
|
3329
|
+
releaseReader();
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
totalSize += value.byteLength;
|
|
3333
|
+
if (totalSize > maxSize) {
|
|
3334
|
+
failStream(controller, new PayloadTooLargeError(maxSize));
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
controller.enqueue(value);
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
failStream(controller, err);
|
|
3238
3340
|
}
|
|
3239
|
-
controller.enqueue(value);
|
|
3240
3341
|
},
|
|
3241
3342
|
cancel(reason) {
|
|
3242
|
-
reader
|
|
3343
|
+
if (reader) {
|
|
3344
|
+
try {
|
|
3345
|
+
reader.cancel(reason);
|
|
3346
|
+
} catch {
|
|
3347
|
+
}
|
|
3348
|
+
releaseReader();
|
|
3349
|
+
}
|
|
3243
3350
|
}
|
|
3244
3351
|
});
|
|
3245
3352
|
}
|
|
@@ -3320,21 +3427,27 @@ function createServer(options) {
|
|
|
3320
3427
|
}
|
|
3321
3428
|
return { server, routesRef };
|
|
3322
3429
|
}
|
|
3323
|
-
|
|
3430
|
+
function prepareRequest(req, config, bodyLimit) {
|
|
3324
3431
|
const request = toWebRequest(req, bodyLimit);
|
|
3325
3432
|
const method = request.method.toUpperCase();
|
|
3326
3433
|
const urlPath = new URL(request.url).pathname;
|
|
3327
3434
|
const ctx = createContext(request, {}, config, getClientIp(req));
|
|
3328
3435
|
const meta = ctx.meta;
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3436
|
+
return { request, ctx, meta, method, urlPath };
|
|
3437
|
+
}
|
|
3438
|
+
function resolveRouteOrThrow(routes, method, urlPath) {
|
|
3439
|
+
const match = matchRoute(routes, method, urlPath);
|
|
3440
|
+
if (match) return match;
|
|
3441
|
+
const allowedMethods = findAllowedMethods(routes, urlPath);
|
|
3442
|
+
if (allowedMethods.length > 0) {
|
|
3443
|
+
throw new MethodNotAllowedError(method, urlPath, allowedMethods);
|
|
3444
|
+
}
|
|
3445
|
+
throw new RouteNotFoundError(urlPath);
|
|
3446
|
+
}
|
|
3447
|
+
function createRoutePipeline(opts) {
|
|
3448
|
+
const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
3449
|
+
return async () => {
|
|
3450
|
+
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
3338
3451
|
ctx.params = match.params;
|
|
3339
3452
|
const { route } = match;
|
|
3340
3453
|
const absoluteFilePath = path9.resolve(rootDir, route.filePath);
|
|
@@ -3364,37 +3477,44 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3364
3477
|
}
|
|
3365
3478
|
}
|
|
3366
3479
|
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;
|
|
3480
|
+
return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
|
|
3375
3481
|
};
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
}
|
|
3386
|
-
response = await routePipeline();
|
|
3482
|
+
}
|
|
3483
|
+
async function sendSuccessResponse(response, res) {
|
|
3484
|
+
await sendNodeResponse(response, res);
|
|
3485
|
+
}
|
|
3486
|
+
async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
3487
|
+
await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
|
|
3488
|
+
if (onError) {
|
|
3489
|
+
try {
|
|
3490
|
+
await onError(err, ctx);
|
|
3491
|
+
} catch {
|
|
3387
3492
|
}
|
|
3388
|
-
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
|
|
3496
|
+
const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
|
|
3497
|
+
const routePipeline = createRoutePipeline({
|
|
3498
|
+
routes,
|
|
3499
|
+
method,
|
|
3500
|
+
urlPath,
|
|
3501
|
+
ctx,
|
|
3502
|
+
request,
|
|
3503
|
+
rootDir,
|
|
3504
|
+
dist,
|
|
3505
|
+
globalMiddlewares,
|
|
3506
|
+
globalInjectors
|
|
3507
|
+
});
|
|
3508
|
+
const outerMiddlewares = [];
|
|
3509
|
+
if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
|
|
3510
|
+
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
3511
|
+
outerMiddlewares.push(...globalMiddlewares);
|
|
3512
|
+
}
|
|
3513
|
+
try {
|
|
3514
|
+
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
3515
|
+
await sendSuccessResponse(response, res);
|
|
3389
3516
|
} catch (err) {
|
|
3390
|
-
|
|
3391
|
-
await sendNodeResponse(mergeMeta(errorResponse, meta), res);
|
|
3392
|
-
if (onError) {
|
|
3393
|
-
try {
|
|
3394
|
-
await onError(err, ctx);
|
|
3395
|
-
} catch {
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3517
|
+
await sendErrorResponse(err, meta, res, onError, ctx);
|
|
3398
3518
|
}
|
|
3399
3519
|
}
|
|
3400
3520
|
|