@faapi/faapi 6.17.0 → 6.18.1
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 +1718 -1523
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.js +1100 -941
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-C7yHYxPg.d.ts → routeTypes-BAhPIzcG.d.ts} +26 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +458 -271
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// src/response/bufferedBody.ts
|
|
2
|
+
var bufferedBodyResponses = /* @__PURE__ */ new WeakSet();
|
|
3
|
+
function markBufferedBody(response) {
|
|
4
|
+
bufferedBodyResponses.add(response);
|
|
5
|
+
}
|
|
6
|
+
function hasBufferedBody(response) {
|
|
7
|
+
return bufferedBodyResponses.has(response);
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
// src/utils/isPlainObject.ts
|
|
2
11
|
function isPlainObject(value) {
|
|
3
12
|
if (value === null || typeof value !== "object") {
|
|
@@ -12,8 +21,50 @@ function isPlainObject(value) {
|
|
|
12
21
|
|
|
13
22
|
// src/utils/stringifyJson.ts
|
|
14
23
|
function stringifyJson(value) {
|
|
24
|
+
if (!needsConversion(value, /* @__PURE__ */ new Set())) {
|
|
25
|
+
return JSON.stringify(value);
|
|
26
|
+
}
|
|
15
27
|
return JSON.stringify(convertValue(value, /* @__PURE__ */ new Set()));
|
|
16
28
|
}
|
|
29
|
+
function needsConversion(value, ancestors) {
|
|
30
|
+
if (value === null) return false;
|
|
31
|
+
const type = typeof value;
|
|
32
|
+
if (type === "bigint") return true;
|
|
33
|
+
if (type === "number") return !Number.isFinite(value);
|
|
34
|
+
if (type !== "object") return false;
|
|
35
|
+
const obj = value;
|
|
36
|
+
if (ancestors.has(obj)) {
|
|
37
|
+
throw new TypeError("[faapi] Converting circular structure to JSON");
|
|
38
|
+
}
|
|
39
|
+
if (obj instanceof Date || obj instanceof Map || obj instanceof Set || obj instanceof RegExp) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (typeof obj.toJSON === "function") return true;
|
|
43
|
+
if (Array.isArray(obj)) {
|
|
44
|
+
ancestors.add(obj);
|
|
45
|
+
for (let i = 0; i < obj.length; i++) {
|
|
46
|
+
if (needsConversion(obj[i], ancestors)) {
|
|
47
|
+
ancestors.delete(obj);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
ancestors.delete(obj);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (isPlainObject(obj)) {
|
|
55
|
+
ancestors.add(obj);
|
|
56
|
+
for (const key in obj) {
|
|
57
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
|
|
58
|
+
if (needsConversion(obj[key], ancestors)) {
|
|
59
|
+
ancestors.delete(obj);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
ancestors.delete(obj);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
17
68
|
function convertValue(value, ancestors) {
|
|
18
69
|
if (value === null) return value;
|
|
19
70
|
const type = typeof value;
|
|
@@ -108,16 +159,30 @@ function createSseWriter() {
|
|
|
108
159
|
let controller = null;
|
|
109
160
|
let closed = false;
|
|
110
161
|
let aborted = false;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
162
|
+
let drainWaiters = [];
|
|
163
|
+
const resolveDrainWaiters = () => {
|
|
164
|
+
const waiters = drainWaiters;
|
|
165
|
+
drainWaiters = [];
|
|
166
|
+
for (const resolve of waiters) resolve();
|
|
167
|
+
};
|
|
168
|
+
const stream = new ReadableStream(
|
|
169
|
+
{
|
|
170
|
+
start(c) {
|
|
171
|
+
controller = c;
|
|
172
|
+
},
|
|
173
|
+
// 消费者排空缓冲后再要数据 → 背压解除,唤醒 waitForDrain 等待方
|
|
174
|
+
pull() {
|
|
175
|
+
resolveDrainWaiters();
|
|
176
|
+
},
|
|
177
|
+
cancel() {
|
|
178
|
+
aborted = true;
|
|
179
|
+
closed = true;
|
|
180
|
+
controller = null;
|
|
181
|
+
resolveDrainWaiters();
|
|
182
|
+
}
|
|
114
183
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
closed = true;
|
|
118
|
-
controller = null;
|
|
119
|
-
}
|
|
120
|
-
});
|
|
184
|
+
{ highWaterMark: 16 }
|
|
185
|
+
);
|
|
121
186
|
const response = new Response(stream, {
|
|
122
187
|
status: 200,
|
|
123
188
|
headers: {
|
|
@@ -152,6 +217,7 @@ function createSseWriter() {
|
|
|
152
217
|
close() {
|
|
153
218
|
if (closed) return;
|
|
154
219
|
closed = true;
|
|
220
|
+
resolveDrainWaiters();
|
|
155
221
|
if (controller) {
|
|
156
222
|
try {
|
|
157
223
|
controller.close();
|
|
@@ -163,6 +229,16 @@ function createSseWriter() {
|
|
|
163
229
|
get closed() {
|
|
164
230
|
return closed;
|
|
165
231
|
},
|
|
232
|
+
get desiredSize() {
|
|
233
|
+
return controller ? controller.desiredSize : null;
|
|
234
|
+
},
|
|
235
|
+
waitForDrain() {
|
|
236
|
+
if (closed || !controller) return Promise.resolve();
|
|
237
|
+
if ((controller.desiredSize ?? 0) > 0) return Promise.resolve();
|
|
238
|
+
return new Promise((resolve) => {
|
|
239
|
+
drainWaiters.push(resolve);
|
|
240
|
+
});
|
|
241
|
+
},
|
|
166
242
|
get aborted() {
|
|
167
243
|
return aborted;
|
|
168
244
|
},
|
|
@@ -199,14 +275,14 @@ var ValidationError = class extends FaapiError {
|
|
|
199
275
|
issues;
|
|
200
276
|
};
|
|
201
277
|
var RouteNotFoundError = class extends FaapiError {
|
|
202
|
-
constructor(
|
|
203
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
278
|
+
constructor(path16) {
|
|
279
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path16}`, 404);
|
|
204
280
|
this.name = "RouteNotFoundError";
|
|
205
281
|
}
|
|
206
282
|
};
|
|
207
283
|
var MethodNotAllowedError = class extends FaapiError {
|
|
208
|
-
constructor(method,
|
|
209
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
284
|
+
constructor(method, path16, allowedMethods) {
|
|
285
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path16}`, 405);
|
|
210
286
|
this.allowedMethods = allowedMethods;
|
|
211
287
|
this.name = "MethodNotAllowedError";
|
|
212
288
|
}
|
|
@@ -252,7 +328,9 @@ function jsonRaw(body, status, extraHeaders) {
|
|
|
252
328
|
const extra = new Headers(extraHeaders);
|
|
253
329
|
extra.forEach((value, key) => headers.set(key, value));
|
|
254
330
|
}
|
|
255
|
-
|
|
331
|
+
const response = new Response(stringifyJson(body), { status, headers });
|
|
332
|
+
markBufferedBody(response);
|
|
333
|
+
return response;
|
|
256
334
|
}
|
|
257
335
|
function wrapOkResult(result, config) {
|
|
258
336
|
if (result instanceof Response) return result;
|
|
@@ -269,12 +347,9 @@ function formatFailResponse(options, config) {
|
|
|
269
347
|
}
|
|
270
348
|
function formatErrorResponse(error, config) {
|
|
271
349
|
const failFn = resolveFailFn(config);
|
|
350
|
+
const buildFailBody = (e) => failFn({ status: e.statusCode, code: e.code, message: e.message });
|
|
272
351
|
if (error instanceof ValidationError) {
|
|
273
|
-
const body2 =
|
|
274
|
-
status: error.statusCode,
|
|
275
|
-
code: error.code,
|
|
276
|
-
message: error.message
|
|
277
|
-
});
|
|
352
|
+
const body2 = buildFailBody(error);
|
|
278
353
|
const bodyObj = typeof body2 === "object" && body2 !== null ? { ...body2 } : { error: body2 };
|
|
279
354
|
const existingError = bodyObj.error;
|
|
280
355
|
if (existingError && typeof existingError === "object") {
|
|
@@ -287,28 +362,15 @@ function formatErrorResponse(error, config) {
|
|
|
287
362
|
return jsonOk(bodyObj, error.statusCode);
|
|
288
363
|
}
|
|
289
364
|
if (error instanceof MethodNotAllowedError) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
code: error.code,
|
|
293
|
-
message: error.message
|
|
365
|
+
return jsonOk(buildFailBody(error), error.statusCode, {
|
|
366
|
+
Allow: error.allowedMethods.join(", ")
|
|
294
367
|
});
|
|
295
|
-
return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
|
|
296
368
|
}
|
|
297
369
|
if (error instanceof PayloadTooLargeError) {
|
|
298
|
-
|
|
299
|
-
status: error.statusCode,
|
|
300
|
-
code: error.code,
|
|
301
|
-
message: error.message
|
|
302
|
-
});
|
|
303
|
-
return jsonOk(body2, error.statusCode);
|
|
370
|
+
return jsonOk(buildFailBody(error), error.statusCode);
|
|
304
371
|
}
|
|
305
372
|
if (error instanceof FaapiError) {
|
|
306
|
-
|
|
307
|
-
status: error.statusCode,
|
|
308
|
-
code: error.code,
|
|
309
|
-
message: error.message
|
|
310
|
-
});
|
|
311
|
-
return jsonOk(body2, error.statusCode);
|
|
373
|
+
return jsonOk(buildFailBody(error), error.statusCode);
|
|
312
374
|
}
|
|
313
375
|
const message = error instanceof Error ? error.message : "An unknown error occurred";
|
|
314
376
|
const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
|
|
@@ -513,10 +575,12 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
513
575
|
},
|
|
514
576
|
html(html, status) {
|
|
515
577
|
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
516
|
-
|
|
578
|
+
const response = new Response(html, {
|
|
517
579
|
status: status ?? 200,
|
|
518
580
|
headers
|
|
519
581
|
});
|
|
582
|
+
markBufferedBody(response);
|
|
583
|
+
return response;
|
|
520
584
|
},
|
|
521
585
|
getCookie(name) {
|
|
522
586
|
return parsedCookies.get(name);
|
|
@@ -584,7 +648,7 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
584
648
|
function createTestContext(options) {
|
|
585
649
|
const {
|
|
586
650
|
method = "GET",
|
|
587
|
-
path:
|
|
651
|
+
path: path16,
|
|
588
652
|
query,
|
|
589
653
|
headers,
|
|
590
654
|
params = {},
|
|
@@ -592,7 +656,7 @@ function createTestContext(options) {
|
|
|
592
656
|
ip = "",
|
|
593
657
|
registries
|
|
594
658
|
} = options;
|
|
595
|
-
const url = new URL(`http://localhost${
|
|
659
|
+
const url = new URL(`http://localhost${path16}`);
|
|
596
660
|
if (query) {
|
|
597
661
|
for (const [key, value] of Object.entries(query)) {
|
|
598
662
|
if (Array.isArray(value)) {
|
|
@@ -676,51 +740,67 @@ async function toResponse(value, meta) {
|
|
|
676
740
|
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
677
741
|
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
678
742
|
applyMeta(headers2);
|
|
679
|
-
return
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
743
|
+
return markBuffered(
|
|
744
|
+
new Response(value, {
|
|
745
|
+
status: meta?.status ?? 200,
|
|
746
|
+
headers: headers2
|
|
747
|
+
})
|
|
748
|
+
);
|
|
683
749
|
}
|
|
684
750
|
if (value instanceof Uint8Array) {
|
|
685
751
|
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
686
752
|
applyMeta(headers2);
|
|
687
|
-
return
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
753
|
+
return markBuffered(
|
|
754
|
+
new Response(value, {
|
|
755
|
+
status: meta?.status ?? 200,
|
|
756
|
+
headers: headers2
|
|
757
|
+
})
|
|
758
|
+
);
|
|
691
759
|
}
|
|
692
760
|
if (isPlainObject(value) || Array.isArray(value)) {
|
|
693
761
|
const body2 = stringifyJson(value);
|
|
694
762
|
const headers2 = new Headers({ "Content-Type": "application/json" });
|
|
695
763
|
applyMeta(headers2);
|
|
696
|
-
return
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
764
|
+
return markBuffered(
|
|
765
|
+
new Response(body2, {
|
|
766
|
+
status: meta?.status ?? 200,
|
|
767
|
+
headers: headers2
|
|
768
|
+
})
|
|
769
|
+
);
|
|
700
770
|
}
|
|
701
771
|
if (typeof value === "string") {
|
|
702
772
|
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
703
773
|
applyMeta(headers2);
|
|
704
|
-
return
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
774
|
+
return markBuffered(
|
|
775
|
+
new Response(value, {
|
|
776
|
+
status: meta?.status ?? 200,
|
|
777
|
+
headers: headers2
|
|
778
|
+
})
|
|
779
|
+
);
|
|
708
780
|
}
|
|
709
781
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
710
782
|
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
711
783
|
applyMeta(headers2);
|
|
712
|
-
return
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
784
|
+
return markBuffered(
|
|
785
|
+
new Response(String(value), {
|
|
786
|
+
status: meta?.status ?? 200,
|
|
787
|
+
headers: headers2
|
|
788
|
+
})
|
|
789
|
+
);
|
|
716
790
|
}
|
|
717
791
|
const body = stringifyJson(value);
|
|
718
792
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
719
793
|
applyMeta(headers);
|
|
720
|
-
return
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
794
|
+
return markBuffered(
|
|
795
|
+
new Response(body, {
|
|
796
|
+
status: meta?.status ?? 200,
|
|
797
|
+
headers
|
|
798
|
+
})
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
function markBuffered(response) {
|
|
802
|
+
markBufferedBody(response);
|
|
803
|
+
return response;
|
|
724
804
|
}
|
|
725
805
|
|
|
726
806
|
// src/injection/resolveInjection.ts
|
|
@@ -825,7 +905,10 @@ function extractParamName(param, names) {
|
|
|
825
905
|
}
|
|
826
906
|
|
|
827
907
|
// src/utils/queryToObject.ts
|
|
908
|
+
var queryCache = /* @__PURE__ */ new WeakMap();
|
|
828
909
|
function queryToObject(params) {
|
|
910
|
+
const cached = queryCache.get(params);
|
|
911
|
+
if (cached) return cached;
|
|
829
912
|
const result = {};
|
|
830
913
|
for (const [key, value] of params) {
|
|
831
914
|
const existing = result[key];
|
|
@@ -837,6 +920,7 @@ function queryToObject(params) {
|
|
|
837
920
|
result[key] = [existing, value];
|
|
838
921
|
}
|
|
839
922
|
}
|
|
923
|
+
queryCache.set(params, result);
|
|
840
924
|
return result;
|
|
841
925
|
}
|
|
842
926
|
|
|
@@ -1157,18 +1241,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
1157
1241
|
sseWriter.close();
|
|
1158
1242
|
}
|
|
1159
1243
|
};
|
|
1160
|
-
|
|
1161
|
-
try {
|
|
1162
|
-
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
1163
|
-
const sseResponse = pickSseAndAutoClose();
|
|
1164
|
-
if (sseResponse) return sseResponse;
|
|
1165
|
-
return toResponse(wrapResult(result, ctx), meta);
|
|
1166
|
-
} catch (err) {
|
|
1167
|
-
autoCloseSseOnError();
|
|
1168
|
-
throw err;
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
const finalHandler = async () => {
|
|
1244
|
+
const executeAndFormat = async () => {
|
|
1172
1245
|
try {
|
|
1173
1246
|
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
1174
1247
|
const sseResponse = pickSseAndAutoClose();
|
|
@@ -1179,27 +1252,31 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
1179
1252
|
throw err;
|
|
1180
1253
|
}
|
|
1181
1254
|
};
|
|
1255
|
+
if (!middlewares || middlewares.length === 0) {
|
|
1256
|
+
return executeAndFormat();
|
|
1257
|
+
}
|
|
1258
|
+
const finalHandler = executeAndFormat;
|
|
1182
1259
|
return await compose(middlewares, ctx, finalHandler);
|
|
1183
1260
|
}
|
|
1184
1261
|
|
|
1185
1262
|
// src/testServer.ts
|
|
1186
|
-
import
|
|
1263
|
+
import path15 from "path";
|
|
1187
1264
|
import os from "os";
|
|
1188
|
-
import
|
|
1265
|
+
import fs12 from "fs/promises";
|
|
1189
1266
|
|
|
1190
1267
|
// src/router/scanRoutes.ts
|
|
1191
1268
|
import fg from "fast-glob";
|
|
1192
1269
|
import path2 from "path";
|
|
1193
|
-
import
|
|
1270
|
+
import fs3 from "fs";
|
|
1194
1271
|
|
|
1195
1272
|
// src/router/constants.ts
|
|
1196
1273
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
1197
1274
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
1198
1275
|
|
|
1199
1276
|
// src/utils/normalizePath.ts
|
|
1200
|
-
function normalizePath(
|
|
1201
|
-
if (!
|
|
1202
|
-
let result =
|
|
1277
|
+
function normalizePath(path16) {
|
|
1278
|
+
if (!path16) return "";
|
|
1279
|
+
let result = path16.replace(/\\/g, "/");
|
|
1203
1280
|
result = result.replace(/\/+/g, "/");
|
|
1204
1281
|
result = result.replace(/\/+$/, "");
|
|
1205
1282
|
if (result && !result.startsWith("/")) {
|
|
@@ -1246,6 +1323,7 @@ function filePathToUrlPath(filePath) {
|
|
|
1246
1323
|
}
|
|
1247
1324
|
|
|
1248
1325
|
// src/utils/importWithCacheBust.ts
|
|
1326
|
+
import fs from "fs";
|
|
1249
1327
|
import { pathToFileURL } from "url";
|
|
1250
1328
|
var loadTs;
|
|
1251
1329
|
function getVitestImportActual() {
|
|
@@ -1253,13 +1331,26 @@ function getVitestImportActual() {
|
|
|
1253
1331
|
if (typeof vi?.importActual !== "function") return void 0;
|
|
1254
1332
|
return vi.importActual.bind(vi);
|
|
1255
1333
|
}
|
|
1334
|
+
var bustCache = /* @__PURE__ */ new Map();
|
|
1335
|
+
var bustCounter = 0;
|
|
1256
1336
|
async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
1257
1337
|
const importActual = getVitestImportActual();
|
|
1258
1338
|
if (importActual) {
|
|
1259
1339
|
if (bustViteCache) {
|
|
1340
|
+
let mtimeMs = 0;
|
|
1341
|
+
try {
|
|
1342
|
+
mtimeMs = fs.statSync(filePath).mtimeMs;
|
|
1343
|
+
} catch {
|
|
1344
|
+
}
|
|
1345
|
+
const cached = bustCache.get(filePath);
|
|
1346
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
1347
|
+
return cached.module;
|
|
1348
|
+
}
|
|
1260
1349
|
let url2 = pathToFileURL(filePath).href;
|
|
1261
|
-
url2 += `?t=${Date.now()}`;
|
|
1262
|
-
|
|
1350
|
+
url2 += `?t=${Date.now()}-${++bustCounter}`;
|
|
1351
|
+
const module = await import(url2);
|
|
1352
|
+
bustCache.set(filePath, { mtimeMs, module });
|
|
1353
|
+
return module;
|
|
1263
1354
|
}
|
|
1264
1355
|
return await importActual(filePath);
|
|
1265
1356
|
}
|
|
@@ -1347,7 +1438,7 @@ async function loadMergedMiddlewares(middlewarePaths) {
|
|
|
1347
1438
|
|
|
1348
1439
|
// src/utils/prodPaths.ts
|
|
1349
1440
|
import path from "path";
|
|
1350
|
-
import
|
|
1441
|
+
import fs2 from "fs";
|
|
1351
1442
|
var APP_DIR = "src";
|
|
1352
1443
|
function toProdFilePath(filePath, dist) {
|
|
1353
1444
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -1365,7 +1456,7 @@ function toProdExtension(filePath) {
|
|
|
1365
1456
|
}
|
|
1366
1457
|
function toRealPath(p) {
|
|
1367
1458
|
try {
|
|
1368
|
-
return
|
|
1459
|
+
return fs2.realpathSync(p);
|
|
1369
1460
|
} catch {
|
|
1370
1461
|
return p;
|
|
1371
1462
|
}
|
|
@@ -1400,7 +1491,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
|
1400
1491
|
const mwJsPath = path2.join(currentDir, "middlewares.js");
|
|
1401
1492
|
const absTsPath = path2.resolve(rootDir, mwTsPath);
|
|
1402
1493
|
const absJsPath = path2.resolve(rootDir, mwJsPath);
|
|
1403
|
-
const absMwPath =
|
|
1494
|
+
const absMwPath = fs3.existsSync(absTsPath) ? absTsPath : fs3.existsSync(absJsPath) ? absJsPath : null;
|
|
1404
1495
|
if (absMwPath) {
|
|
1405
1496
|
const relMwPath = path2.relative(rootDir, absMwPath);
|
|
1406
1497
|
const prodAbsPath = path2.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
@@ -1410,7 +1501,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
|
1410
1501
|
for (const ext of [".ts", ".js"]) {
|
|
1411
1502
|
const mwPath = path2.join(currentDir, `middlewares${ext}`);
|
|
1412
1503
|
const absMwPath = path2.resolve(rootDir, mwPath);
|
|
1413
|
-
if (
|
|
1504
|
+
if (fs3.existsSync(absMwPath)) {
|
|
1414
1505
|
paths.push(absMwPath);
|
|
1415
1506
|
break;
|
|
1416
1507
|
}
|
|
@@ -1449,7 +1540,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
1449
1540
|
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
1450
1541
|
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
1451
1542
|
}
|
|
1452
|
-
const source = await
|
|
1543
|
+
const source = await fs3.promises.readFile(absPath, "utf8").catch(() => "");
|
|
1453
1544
|
const exportNames = extractExportsFromSource(source);
|
|
1454
1545
|
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
1455
1546
|
for (const method of methods) {
|
|
@@ -1502,34 +1593,24 @@ function sortRoutes(routes) {
|
|
|
1502
1593
|
}
|
|
1503
1594
|
|
|
1504
1595
|
// src/cli/generateSchemaFiles.ts
|
|
1505
|
-
import
|
|
1506
|
-
|
|
1507
|
-
// src/utils/atomicWrite.ts
|
|
1508
|
-
import path3 from "path";
|
|
1509
|
-
import fs3 from "fs";
|
|
1510
|
-
async function atomicWriteFile(outputPath, content) {
|
|
1511
|
-
await fs3.promises.mkdir(path3.dirname(outputPath), { recursive: true });
|
|
1512
|
-
const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1513
|
-
await fs3.promises.writeFile(tmp, content, "utf-8");
|
|
1514
|
-
await fs3.promises.rename(tmp, outputPath);
|
|
1515
|
-
}
|
|
1596
|
+
import path7 from "path";
|
|
1516
1597
|
|
|
1517
1598
|
// src/ast/createProgram.ts
|
|
1518
1599
|
import ts2 from "typescript";
|
|
1519
1600
|
import fs4 from "fs";
|
|
1520
|
-
import
|
|
1601
|
+
import path3 from "path";
|
|
1521
1602
|
var programCache = /* @__PURE__ */ new Map();
|
|
1522
1603
|
var tsConfigCache = /* @__PURE__ */ new Map();
|
|
1523
1604
|
function findTsConfig(filePath) {
|
|
1524
|
-
let dir =
|
|
1525
|
-
const root =
|
|
1605
|
+
let dir = path3.dirname(filePath);
|
|
1606
|
+
const root = path3.parse(dir).root;
|
|
1526
1607
|
while (true) {
|
|
1527
|
-
const candidate =
|
|
1608
|
+
const candidate = path3.join(dir, "tsconfig.json");
|
|
1528
1609
|
if (fs4.existsSync(candidate)) {
|
|
1529
1610
|
return candidate;
|
|
1530
1611
|
}
|
|
1531
1612
|
if (dir === root) return null;
|
|
1532
|
-
const parent =
|
|
1613
|
+
const parent = path3.dirname(dir);
|
|
1533
1614
|
if (parent === dir) return null;
|
|
1534
1615
|
dir = parent;
|
|
1535
1616
|
}
|
|
@@ -1545,7 +1626,7 @@ function parseTsConfig(tsconfigPath) {
|
|
|
1545
1626
|
return result;
|
|
1546
1627
|
}
|
|
1547
1628
|
const config = configFile.config ?? {};
|
|
1548
|
-
const basePath =
|
|
1629
|
+
const basePath = path3.dirname(tsconfigPath);
|
|
1549
1630
|
const parsed = ts2.parseJsonConfigFileContent(
|
|
1550
1631
|
config,
|
|
1551
1632
|
ts2.sys,
|
|
@@ -2545,11 +2626,11 @@ function extractSchema(typeNode, sourceFile) {
|
|
|
2545
2626
|
}
|
|
2546
2627
|
|
|
2547
2628
|
// src/cli/collectRouteSchemaSources.ts
|
|
2548
|
-
import
|
|
2629
|
+
import path4 from "path";
|
|
2549
2630
|
function collectRouteSchemaSources(routes, rootDir) {
|
|
2550
2631
|
const methodsByFile = /* @__PURE__ */ new Map();
|
|
2551
2632
|
for (const route of routes) {
|
|
2552
|
-
const filePath = rootDir ?
|
|
2633
|
+
const filePath = rootDir ? path4.resolve(rootDir, route.filePath) : route.filePath;
|
|
2553
2634
|
let entry = methodsByFile.get(filePath);
|
|
2554
2635
|
if (!entry) {
|
|
2555
2636
|
entry = { urlPath: route.urlPath, methods: /* @__PURE__ */ new Set() };
|
|
@@ -2893,6 +2974,63 @@ function generateZodSchemaSourceParts(typeInfo, resolveType, exportName, coerce
|
|
|
2893
2974
|
return { namedTypeDeclarations, entryDeclaration };
|
|
2894
2975
|
}
|
|
2895
2976
|
|
|
2977
|
+
// src/cli/generateZodArtifacts.ts
|
|
2978
|
+
import path6 from "path";
|
|
2979
|
+
import { existsSync } from "fs";
|
|
2980
|
+
|
|
2981
|
+
// src/utils/atomicWrite.ts
|
|
2982
|
+
import path5 from "path";
|
|
2983
|
+
import fs5 from "fs";
|
|
2984
|
+
async function atomicWriteFile(outputPath, content) {
|
|
2985
|
+
await fs5.promises.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
2986
|
+
const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2987
|
+
await fs5.promises.writeFile(tmp, content, "utf-8");
|
|
2988
|
+
await fs5.promises.rename(tmp, outputPath);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// src/cli/generateZodArtifacts.ts
|
|
2992
|
+
async function generateZodArtifacts(sources, options) {
|
|
2993
|
+
const { generateFileSource, resolversByFile, rootDir, dist } = options;
|
|
2994
|
+
if (sources.length === 0) return;
|
|
2995
|
+
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
2996
|
+
for (const source of sources) {
|
|
2997
|
+
let list = sourcesByFile.get(source.filePath);
|
|
2998
|
+
if (!list) {
|
|
2999
|
+
list = [];
|
|
3000
|
+
sourcesByFile.set(source.filePath, list);
|
|
3001
|
+
}
|
|
3002
|
+
list.push(source);
|
|
3003
|
+
}
|
|
3004
|
+
const fileEntries = [];
|
|
3005
|
+
for (const [filePath, fileSources] of sourcesByFile) {
|
|
3006
|
+
const relFile = path6.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
3007
|
+
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
3008
|
+
const resolver = resolversByFile.get(filePath);
|
|
3009
|
+
let relForDir = relFile;
|
|
3010
|
+
if (relForDir.startsWith("src/")) {
|
|
3011
|
+
relForDir = relForDir.slice(4);
|
|
3012
|
+
}
|
|
3013
|
+
const dirIdx = relForDir.lastIndexOf("/");
|
|
3014
|
+
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
3015
|
+
const source = generateFileSource(
|
|
3016
|
+
fileSources,
|
|
3017
|
+
(name) => resolver?.resolve(name)?.runtimeType,
|
|
3018
|
+
getHelpersImportPath(zodRelDir)
|
|
3019
|
+
);
|
|
3020
|
+
fileEntries.push({ outputPath, source });
|
|
3021
|
+
}
|
|
3022
|
+
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
3023
|
+
if (usesCoerceHelpers(allSourceCode)) {
|
|
3024
|
+
const helpersPath = path6.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
3025
|
+
if (!existsSync(helpersPath)) {
|
|
3026
|
+
await atomicWriteFile(helpersPath, generateHelpersFileSource());
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
await Promise.all(
|
|
3030
|
+
fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
|
|
2896
3034
|
// src/cli/generateSchemaFiles.ts
|
|
2897
3035
|
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
2898
3036
|
let rel = sourceFile.replace(/\\/g, "/");
|
|
@@ -2901,7 +3039,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
2901
3039
|
}
|
|
2902
3040
|
const idx = rel.lastIndexOf("/");
|
|
2903
3041
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2904
|
-
return
|
|
3042
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
2905
3043
|
}
|
|
2906
3044
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
2907
3045
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -2912,7 +3050,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
2912
3050
|
}
|
|
2913
3051
|
const idx = rel.lastIndexOf("/");
|
|
2914
3052
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2915
|
-
return
|
|
3053
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
2916
3054
|
}
|
|
2917
3055
|
function getHelpersImportPath(relDir) {
|
|
2918
3056
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -2960,60 +3098,27 @@ function generateSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
|
2960
3098
|
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
2961
3099
|
if (routes.length === 0) return;
|
|
2962
3100
|
const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
}
|
|
2970
|
-
list.push(source);
|
|
2971
|
-
}
|
|
2972
|
-
const fileEntries = [];
|
|
2973
|
-
for (const [filePath, fileSources] of sourcesByFile) {
|
|
2974
|
-
const relFile = path6.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2975
|
-
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
2976
|
-
let relForDir = relFile;
|
|
2977
|
-
if (relForDir.startsWith("src/")) {
|
|
2978
|
-
relForDir = relForDir.slice(4);
|
|
2979
|
-
}
|
|
2980
|
-
const dirIdx = relForDir.lastIndexOf("/");
|
|
2981
|
-
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
2982
|
-
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
2983
|
-
const resolver = resolversByFile.get(filePath);
|
|
2984
|
-
const source = generateSchemaFileSource(
|
|
2985
|
-
fileSources,
|
|
2986
|
-
(name) => resolver?.resolve(name)?.runtimeType,
|
|
2987
|
-
helpersImportPath
|
|
2988
|
-
);
|
|
2989
|
-
fileEntries.push({ outputPath, source });
|
|
2990
|
-
}
|
|
2991
|
-
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2992
|
-
if (usesCoerceHelpers(allSourceCode)) {
|
|
2993
|
-
const helpersPath = path6.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
2994
|
-
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
2995
|
-
}
|
|
2996
|
-
await Promise.all(
|
|
2997
|
-
fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source))
|
|
2998
|
-
);
|
|
2999
|
-
}
|
|
3000
|
-
async function writeSchemaFile(outputPath, source) {
|
|
3001
|
-
await atomicWriteFile(outputPath, source);
|
|
3101
|
+
await generateZodArtifacts(sources, {
|
|
3102
|
+
generateFileSource: generateSchemaFileSource,
|
|
3103
|
+
resolversByFile,
|
|
3104
|
+
rootDir,
|
|
3105
|
+
dist
|
|
3106
|
+
});
|
|
3002
3107
|
}
|
|
3003
3108
|
|
|
3004
3109
|
// src/cli/compileOnDemand.ts
|
|
3005
|
-
import
|
|
3006
|
-
import
|
|
3110
|
+
import path12 from "path";
|
|
3111
|
+
import fs10 from "fs";
|
|
3007
3112
|
|
|
3008
3113
|
// src/cli/compileSourceFiles.ts
|
|
3009
|
-
import
|
|
3010
|
-
import
|
|
3114
|
+
import path10 from "path";
|
|
3115
|
+
import fs8 from "fs";
|
|
3011
3116
|
import fg2 from "fast-glob";
|
|
3012
3117
|
|
|
3013
3118
|
// src/cli/aliasPlugin.ts
|
|
3014
3119
|
import ts7 from "typescript";
|
|
3015
|
-
import
|
|
3016
|
-
import
|
|
3120
|
+
import path9 from "path";
|
|
3121
|
+
import fs7 from "fs";
|
|
3017
3122
|
|
|
3018
3123
|
// src/utils/resolveAlias.ts
|
|
3019
3124
|
function resolveAlias(specifier, config) {
|
|
@@ -3040,15 +3145,15 @@ function resolveAlias(specifier, config) {
|
|
|
3040
3145
|
|
|
3041
3146
|
// src/utils/readTsconfig.ts
|
|
3042
3147
|
import ts6 from "typescript";
|
|
3043
|
-
import
|
|
3044
|
-
import
|
|
3148
|
+
import path8 from "path";
|
|
3149
|
+
import fs6 from "fs";
|
|
3045
3150
|
var tsconfigCache = /* @__PURE__ */ new Map();
|
|
3046
3151
|
function readTsconfig(rootDir) {
|
|
3047
|
-
const tsconfigPath =
|
|
3048
|
-
if (!
|
|
3152
|
+
const tsconfigPath = path8.resolve(rootDir, "tsconfig.json");
|
|
3153
|
+
if (!fs6.existsSync(tsconfigPath)) return null;
|
|
3049
3154
|
let mtimeMs;
|
|
3050
3155
|
try {
|
|
3051
|
-
mtimeMs =
|
|
3156
|
+
mtimeMs = fs6.statSync(tsconfigPath).mtimeMs;
|
|
3052
3157
|
} catch {
|
|
3053
3158
|
return null;
|
|
3054
3159
|
}
|
|
@@ -3064,7 +3169,7 @@ function readTsconfig(rootDir) {
|
|
|
3064
3169
|
const config = rawPaths ? (() => {
|
|
3065
3170
|
const paths = {};
|
|
3066
3171
|
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
3067
|
-
paths[pattern] = targets.map((t) =>
|
|
3172
|
+
paths[pattern] = targets.map((t) => path8.resolve(baseUrl, t));
|
|
3068
3173
|
}
|
|
3069
3174
|
return { baseUrl, paths };
|
|
3070
3175
|
})() : null;
|
|
@@ -3074,26 +3179,26 @@ function readTsconfig(rootDir) {
|
|
|
3074
3179
|
|
|
3075
3180
|
// src/cli/aliasPlugin.ts
|
|
3076
3181
|
function toProdImportPath(sourceFile, importer) {
|
|
3077
|
-
const importerDir =
|
|
3078
|
-
let rel =
|
|
3079
|
-
rel = rel.split(
|
|
3182
|
+
const importerDir = path9.dirname(importer);
|
|
3183
|
+
let rel = path9.relative(importerDir, sourceFile);
|
|
3184
|
+
rel = rel.split(path9.sep).join("/");
|
|
3080
3185
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3081
3186
|
return toProdExtension(rel);
|
|
3082
3187
|
}
|
|
3083
3188
|
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
3084
|
-
const appDirAbs = toRealPath(
|
|
3189
|
+
const appDirAbs = toRealPath(path9.resolve(rootDir, APP_DIR));
|
|
3085
3190
|
const sourceReal = toRealPath(sourceFile);
|
|
3086
|
-
let rel =
|
|
3087
|
-
rel = rel.split(
|
|
3191
|
+
let rel = path9.relative(appDirAbs, sourceReal);
|
|
3192
|
+
rel = rel.split(path9.sep).join("/");
|
|
3088
3193
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3089
3194
|
return toProdExtension(rel);
|
|
3090
3195
|
}
|
|
3091
3196
|
function toProdImportFromImporter(importer, rootDir, relFromDist) {
|
|
3092
|
-
const importerRel =
|
|
3093
|
-
const importerProdDir =
|
|
3197
|
+
const importerRel = path9.relative(toRealPath(path9.resolve(rootDir)), toRealPath(importer)).split(path9.sep).join("/");
|
|
3198
|
+
const importerProdDir = path9.posix.dirname(toProdExtension(importerRel));
|
|
3094
3199
|
if (importerProdDir === ".") return relFromDist;
|
|
3095
3200
|
const target = relFromDist.replace(/^\.\//, "");
|
|
3096
|
-
let rel =
|
|
3201
|
+
let rel = path9.posix.relative(importerProdDir, target);
|
|
3097
3202
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3098
3203
|
return rel;
|
|
3099
3204
|
}
|
|
@@ -3108,41 +3213,41 @@ var INDEX_EXTS = [
|
|
|
3108
3213
|
"/index.cjs"
|
|
3109
3214
|
];
|
|
3110
3215
|
function resolveRelativeSpecifier(importer, specifier) {
|
|
3111
|
-
const importerDir =
|
|
3112
|
-
const base =
|
|
3216
|
+
const importerDir = path9.dirname(importer);
|
|
3217
|
+
const base = path9.resolve(importerDir, specifier);
|
|
3113
3218
|
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
3114
|
-
if (
|
|
3219
|
+
if (fs7.existsSync(base)) return base;
|
|
3115
3220
|
if (base.endsWith(".js")) {
|
|
3116
3221
|
for (const ext of [".ts", ".tsx", ".jsx"]) {
|
|
3117
3222
|
const file = base.slice(0, -3) + ext;
|
|
3118
|
-
if (
|
|
3223
|
+
if (fs7.existsSync(file)) return file;
|
|
3119
3224
|
}
|
|
3120
3225
|
}
|
|
3121
3226
|
return null;
|
|
3122
3227
|
}
|
|
3123
3228
|
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
3124
|
-
return
|
|
3229
|
+
return fs7.existsSync(base) ? base : null;
|
|
3125
3230
|
}
|
|
3126
3231
|
for (const ext of SOURCE_EXTS) {
|
|
3127
3232
|
const file = base + ext;
|
|
3128
|
-
if (
|
|
3233
|
+
if (fs7.existsSync(file)) return file;
|
|
3129
3234
|
}
|
|
3130
3235
|
for (const indexExt of INDEX_EXTS) {
|
|
3131
3236
|
const file = base + indexExt;
|
|
3132
|
-
if (
|
|
3237
|
+
if (fs7.existsSync(file)) return file;
|
|
3133
3238
|
}
|
|
3134
3239
|
return null;
|
|
3135
3240
|
}
|
|
3136
3241
|
function createAliasPlugin(config, options) {
|
|
3137
|
-
const appDirAbs = options?.rootDir ? toRealPath(
|
|
3242
|
+
const appDirAbs = options?.rootDir ? toRealPath(path9.resolve(options.rootDir, APP_DIR)) : null;
|
|
3138
3243
|
const probeCandidateFile = (candidate) => {
|
|
3139
3244
|
for (const ext of SOURCE_EXTS) {
|
|
3140
3245
|
const file = candidate + ext;
|
|
3141
|
-
if (
|
|
3246
|
+
if (fs7.existsSync(file)) return file;
|
|
3142
3247
|
}
|
|
3143
3248
|
for (const indexExt of INDEX_EXTS) {
|
|
3144
3249
|
const file = candidate + indexExt;
|
|
3145
|
-
if (
|
|
3250
|
+
if (fs7.existsSync(file)) return file;
|
|
3146
3251
|
}
|
|
3147
3252
|
return null;
|
|
3148
3253
|
};
|
|
@@ -3152,7 +3257,7 @@ function createAliasPlugin(config, options) {
|
|
|
3152
3257
|
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
3153
3258
|
let source;
|
|
3154
3259
|
try {
|
|
3155
|
-
source =
|
|
3260
|
+
source = fs7.readFileSync(args.path, "utf8");
|
|
3156
3261
|
} catch {
|
|
3157
3262
|
return void 0;
|
|
3158
3263
|
}
|
|
@@ -3277,11 +3382,11 @@ async function compileSourceFiles(options) {
|
|
|
3277
3382
|
if (entryPoints.length === 0) {
|
|
3278
3383
|
return { compiledFiles: [] };
|
|
3279
3384
|
}
|
|
3280
|
-
const absDist =
|
|
3281
|
-
await
|
|
3385
|
+
const absDist = path10.resolve(rootDir, dist);
|
|
3386
|
+
await fs8.promises.mkdir(absDist, { recursive: true });
|
|
3282
3387
|
const plugins = buildAliasPlugins(rootDir);
|
|
3283
3388
|
const esbuild = await import("esbuild");
|
|
3284
|
-
const outbase =
|
|
3389
|
+
const outbase = path10.resolve(rootDir, APP_DIR);
|
|
3285
3390
|
const result = await esbuild.build({
|
|
3286
3391
|
entryPoints,
|
|
3287
3392
|
outdir: absDist,
|
|
@@ -3301,10 +3406,10 @@ async function compileSourceFiles(options) {
|
|
|
3301
3406
|
if (atomicWrite && result.outputFiles) {
|
|
3302
3407
|
await Promise.all(
|
|
3303
3408
|
result.outputFiles.map(async (file) => {
|
|
3304
|
-
await
|
|
3409
|
+
await fs8.promises.mkdir(path10.dirname(file.path), { recursive: true });
|
|
3305
3410
|
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
3306
|
-
await
|
|
3307
|
-
await
|
|
3411
|
+
await fs8.promises.writeFile(tmp, file.contents);
|
|
3412
|
+
await fs8.promises.rename(tmp, file.path);
|
|
3308
3413
|
})
|
|
3309
3414
|
);
|
|
3310
3415
|
}
|
|
@@ -3317,8 +3422,8 @@ async function compileDevRoutes(options) {
|
|
|
3317
3422
|
}
|
|
3318
3423
|
|
|
3319
3424
|
// src/cli/collectImports.ts
|
|
3320
|
-
import
|
|
3321
|
-
import
|
|
3425
|
+
import path11 from "path";
|
|
3426
|
+
import fs9 from "fs";
|
|
3322
3427
|
var SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
3323
3428
|
function extractImportSpecifiers(source) {
|
|
3324
3429
|
const specifiers = [];
|
|
@@ -3330,10 +3435,10 @@ function extractImportSpecifiers(source) {
|
|
|
3330
3435
|
return specifiers;
|
|
3331
3436
|
}
|
|
3332
3437
|
function resolveSpecifierFromDir(dir, specifier) {
|
|
3333
|
-
return resolveRelativeSpecifier(
|
|
3438
|
+
return resolveRelativeSpecifier(path11.join(dir, "__faapi_probe__.ts"), specifier);
|
|
3334
3439
|
}
|
|
3335
3440
|
async function collectRelativeImports(entryFiles, rootDir) {
|
|
3336
|
-
const appDirAbs = toRealPath(
|
|
3441
|
+
const appDirAbs = toRealPath(path11.resolve(rootDir, "src"));
|
|
3337
3442
|
const tsconfig = readTsconfig(rootDir);
|
|
3338
3443
|
const visited = /* @__PURE__ */ new Set();
|
|
3339
3444
|
const insideFiles = /* @__PURE__ */ new Set();
|
|
@@ -3343,7 +3448,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
3343
3448
|
visited.add(filePath);
|
|
3344
3449
|
let source;
|
|
3345
3450
|
try {
|
|
3346
|
-
source = await
|
|
3451
|
+
source = await fs9.promises.readFile(filePath, "utf8");
|
|
3347
3452
|
} catch {
|
|
3348
3453
|
return;
|
|
3349
3454
|
}
|
|
@@ -3365,7 +3470,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
3365
3470
|
resolved = null;
|
|
3366
3471
|
}
|
|
3367
3472
|
if (!resolved) continue;
|
|
3368
|
-
if (!isInsideDir(toRealPath(resolved), toRealPath(
|
|
3473
|
+
if (!isInsideDir(toRealPath(resolved), toRealPath(path11.resolve(rootDir)))) continue;
|
|
3369
3474
|
if (isInsideDir(toRealPath(resolved), appDirAbs)) {
|
|
3370
3475
|
insideFiles.add(resolved);
|
|
3371
3476
|
} else {
|
|
@@ -3386,8 +3491,8 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
3386
3491
|
// src/cli/compileOnDemand.ts
|
|
3387
3492
|
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
3388
3493
|
try {
|
|
3389
|
-
const srcStat =
|
|
3390
|
-
const prodStat =
|
|
3494
|
+
const srcStat = fs10.statSync(sourceAbsPath);
|
|
3495
|
+
const prodStat = fs10.statSync(productAbsPath);
|
|
3391
3496
|
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
3392
3497
|
} catch {
|
|
3393
3498
|
return false;
|
|
@@ -3407,14 +3512,13 @@ var state = createDevOnDemandState();
|
|
|
3407
3512
|
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
3408
3513
|
const inFlight2 = state.inFlightCompilations.get(sourceAbsPath);
|
|
3409
3514
|
if (inFlight2) {
|
|
3410
|
-
await inFlight2
|
|
3411
|
-
});
|
|
3515
|
+
await inFlight2;
|
|
3412
3516
|
return false;
|
|
3413
3517
|
}
|
|
3414
3518
|
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
3415
3519
|
return false;
|
|
3416
3520
|
}
|
|
3417
|
-
if (!
|
|
3521
|
+
if (!fs10.existsSync(sourceAbsPath)) {
|
|
3418
3522
|
return false;
|
|
3419
3523
|
}
|
|
3420
3524
|
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
@@ -3464,25 +3568,24 @@ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
|
|
|
3464
3568
|
}
|
|
3465
3569
|
}
|
|
3466
3570
|
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
3467
|
-
const rel =
|
|
3571
|
+
const rel = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3468
3572
|
if (!rel.startsWith("src/")) return null;
|
|
3469
3573
|
const relWithoutSrc = rel.slice(4);
|
|
3470
3574
|
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
3471
|
-
return
|
|
3575
|
+
return path12.resolve(rootDir, dist, jsRel);
|
|
3472
3576
|
}
|
|
3473
3577
|
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
3474
3578
|
const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
|
|
3475
3579
|
if (inFlight2) {
|
|
3476
|
-
await inFlight2
|
|
3477
|
-
});
|
|
3580
|
+
await inFlight2;
|
|
3478
3581
|
return false;
|
|
3479
3582
|
}
|
|
3480
3583
|
if (state.generatedSchemas.has(schemaPath)) {
|
|
3481
3584
|
return false;
|
|
3482
3585
|
}
|
|
3483
|
-
const prodAbsPath =
|
|
3586
|
+
const prodAbsPath = path12.resolve(rootDir, routeFilePath);
|
|
3484
3587
|
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
3485
|
-
if (!
|
|
3588
|
+
if (!fs10.existsSync(sourceAbsPath)) {
|
|
3486
3589
|
return false;
|
|
3487
3590
|
}
|
|
3488
3591
|
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
@@ -3493,7 +3596,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
3493
3596
|
if (fileRoutes.length === 0) {
|
|
3494
3597
|
return false;
|
|
3495
3598
|
}
|
|
3496
|
-
const sourceRelPath =
|
|
3599
|
+
const sourceRelPath = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3497
3600
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3498
3601
|
const generatePromise = (async () => {
|
|
3499
3602
|
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
@@ -3511,19 +3614,19 @@ var sourcePathCache = /* @__PURE__ */ new Map();
|
|
|
3511
3614
|
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
3512
3615
|
const cached = sourcePathCache.get(prodAbsPath);
|
|
3513
3616
|
if (cached) return cached;
|
|
3514
|
-
const rel =
|
|
3617
|
+
const rel = path12.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
3515
3618
|
let relWithoutDist = rel;
|
|
3516
3619
|
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
3517
3620
|
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
3518
3621
|
}
|
|
3519
3622
|
const srcRel = `src/${relWithoutDist}`;
|
|
3520
3623
|
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
3521
|
-
const tsAbs =
|
|
3624
|
+
const tsAbs = path12.resolve(rootDir, tsRel);
|
|
3522
3625
|
let result;
|
|
3523
|
-
if (
|
|
3626
|
+
if (fs10.existsSync(tsAbs)) {
|
|
3524
3627
|
result = tsAbs;
|
|
3525
3628
|
} else {
|
|
3526
|
-
result =
|
|
3629
|
+
result = path12.resolve(rootDir, srcRel);
|
|
3527
3630
|
}
|
|
3528
3631
|
sourcePathCache.set(prodAbsPath, result);
|
|
3529
3632
|
return result;
|
|
@@ -3576,9 +3679,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
3576
3679
|
function mapZodIssues(error) {
|
|
3577
3680
|
return error.issues.map((issue) => {
|
|
3578
3681
|
const code = mapZodCode(issue);
|
|
3579
|
-
const
|
|
3682
|
+
const path16 = issue.path.map(String).join(".") || "";
|
|
3580
3683
|
return {
|
|
3581
|
-
path:
|
|
3684
|
+
path: path16,
|
|
3582
3685
|
code,
|
|
3583
3686
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
3584
3687
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -3628,7 +3731,7 @@ import {
|
|
|
3628
3731
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
3629
3732
|
import { readFileSync } from "fs";
|
|
3630
3733
|
import { Readable as Readable2 } from "stream";
|
|
3631
|
-
import
|
|
3734
|
+
import path14 from "path";
|
|
3632
3735
|
|
|
3633
3736
|
// src/router/matchRoute.ts
|
|
3634
3737
|
var httpIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -3670,65 +3773,72 @@ function getWsIndex(routes) {
|
|
|
3670
3773
|
wsIndexCache.set(routes, index);
|
|
3671
3774
|
return index;
|
|
3672
3775
|
}
|
|
3673
|
-
function matchRoute(routes, method,
|
|
3776
|
+
function matchRoute(routes, method, path16) {
|
|
3674
3777
|
const index = getHttpIndex(routes);
|
|
3675
3778
|
const upper = method.toUpperCase();
|
|
3676
|
-
const hit = matchByMethod(index, upper,
|
|
3779
|
+
const hit = matchByMethod(index, upper, path16);
|
|
3677
3780
|
if (hit) return hit;
|
|
3678
3781
|
if (upper === "HEAD") {
|
|
3679
|
-
return matchByMethod(index, "GET",
|
|
3782
|
+
return matchByMethod(index, "GET", path16);
|
|
3680
3783
|
}
|
|
3681
3784
|
return null;
|
|
3682
3785
|
}
|
|
3683
|
-
function matchByMethod(index, method,
|
|
3684
|
-
const staticHit = index.static.get(`${method}|${
|
|
3786
|
+
function matchByMethod(index, method, path16) {
|
|
3787
|
+
const staticHit = index.static.get(`${method}|${path16}`);
|
|
3685
3788
|
if (staticHit) {
|
|
3686
3789
|
return { route: staticHit, params: {} };
|
|
3687
3790
|
}
|
|
3791
|
+
if (index.dynamics.length === 0) {
|
|
3792
|
+
return null;
|
|
3793
|
+
}
|
|
3794
|
+
const pathSegments = path16.split("/").filter(Boolean);
|
|
3688
3795
|
for (const entry of index.dynamics) {
|
|
3689
3796
|
const route = entry.route;
|
|
3690
3797
|
if (route.method !== method) {
|
|
3691
3798
|
continue;
|
|
3692
3799
|
}
|
|
3693
|
-
const params = matchSegments(entry.segments,
|
|
3800
|
+
const params = matchSegments(entry.segments, pathSegments, route.paramNames, route.isCatchAll);
|
|
3694
3801
|
if (params !== null) {
|
|
3695
3802
|
return { route, params };
|
|
3696
3803
|
}
|
|
3697
3804
|
}
|
|
3698
3805
|
return null;
|
|
3699
3806
|
}
|
|
3700
|
-
function matchWsRoute(wsRoutes,
|
|
3807
|
+
function matchWsRoute(wsRoutes, path16) {
|
|
3701
3808
|
const index = getWsIndex(wsRoutes);
|
|
3702
|
-
const staticHit = index.static.get(
|
|
3809
|
+
const staticHit = index.static.get(path16);
|
|
3703
3810
|
if (staticHit) {
|
|
3704
3811
|
return { route: staticHit, params: {} };
|
|
3705
3812
|
}
|
|
3706
3813
|
for (const route of index.dynamics) {
|
|
3707
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3814
|
+
const params = matchDynamicPath(route.urlPath, path16, route.paramNames, route.isCatchAll);
|
|
3708
3815
|
if (params !== null) {
|
|
3709
3816
|
return { route, params };
|
|
3710
3817
|
}
|
|
3711
3818
|
}
|
|
3712
3819
|
return null;
|
|
3713
3820
|
}
|
|
3714
|
-
function findAllowedMethods(routes,
|
|
3821
|
+
function findAllowedMethods(routes, path16) {
|
|
3715
3822
|
const index = getHttpIndex(routes);
|
|
3716
3823
|
const methods = /* @__PURE__ */ new Set();
|
|
3717
|
-
const staticMethods = index.methodsByStaticPath.get(
|
|
3824
|
+
const staticMethods = index.methodsByStaticPath.get(path16);
|
|
3718
3825
|
if (staticMethods) {
|
|
3719
3826
|
for (const method of staticMethods) {
|
|
3720
3827
|
methods.add(method);
|
|
3721
3828
|
}
|
|
3722
3829
|
}
|
|
3723
|
-
|
|
3724
|
-
const
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3830
|
+
if (index.dynamics.length > 0) {
|
|
3831
|
+
const pathSegments = path16.split("/").filter(Boolean);
|
|
3832
|
+
for (const entry of index.dynamics) {
|
|
3833
|
+
const params = matchSegments(
|
|
3834
|
+
entry.segments,
|
|
3835
|
+
pathSegments,
|
|
3836
|
+
entry.route.paramNames,
|
|
3837
|
+
entry.route.isCatchAll
|
|
3838
|
+
);
|
|
3839
|
+
if (params !== null) {
|
|
3840
|
+
methods.add(entry.route.method);
|
|
3841
|
+
}
|
|
3732
3842
|
}
|
|
3733
3843
|
}
|
|
3734
3844
|
if (methods.has("GET")) {
|
|
@@ -3736,11 +3846,15 @@ function findAllowedMethods(routes, path15) {
|
|
|
3736
3846
|
}
|
|
3737
3847
|
return Array.from(methods);
|
|
3738
3848
|
}
|
|
3739
|
-
function matchDynamicPath(pattern,
|
|
3740
|
-
return matchSegments(
|
|
3849
|
+
function matchDynamicPath(pattern, path16, paramNames, isCatchAll) {
|
|
3850
|
+
return matchSegments(
|
|
3851
|
+
pattern.split("/").filter(Boolean),
|
|
3852
|
+
path16.split("/").filter(Boolean),
|
|
3853
|
+
paramNames,
|
|
3854
|
+
isCatchAll
|
|
3855
|
+
);
|
|
3741
3856
|
}
|
|
3742
|
-
function matchSegments(patternSegments,
|
|
3743
|
-
const pathSegments = path15.split("/").filter(Boolean);
|
|
3857
|
+
function matchSegments(patternSegments, pathSegments, paramNames, isCatchAll) {
|
|
3744
3858
|
if (isCatchAll) {
|
|
3745
3859
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
3746
3860
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -3956,6 +4070,20 @@ async function sendNodeResponse(response, res) {
|
|
|
3956
4070
|
}
|
|
3957
4071
|
}
|
|
3958
4072
|
if (response.body) {
|
|
4073
|
+
if (hasBufferedBody(response)) {
|
|
4074
|
+
const buffer = await readBufferedBody(response.body);
|
|
4075
|
+
await new Promise((resolve) => {
|
|
4076
|
+
res.on("close", () => {
|
|
4077
|
+
if (!res.writableEnded) res.destroy();
|
|
4078
|
+
});
|
|
4079
|
+
if (res.destroyed) {
|
|
4080
|
+
resolve();
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
res.end(buffer, () => resolve());
|
|
4084
|
+
});
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
3959
4087
|
const nodeStream = Readable.fromWeb(response.body);
|
|
3960
4088
|
await new Promise((resolve, reject) => {
|
|
3961
4089
|
nodeStream.on("error", reject);
|
|
@@ -3974,6 +4102,25 @@ async function sendNodeResponse(response, res) {
|
|
|
3974
4102
|
}
|
|
3975
4103
|
res.end();
|
|
3976
4104
|
}
|
|
4105
|
+
async function readBufferedBody(body) {
|
|
4106
|
+
const reader = body.getReader();
|
|
4107
|
+
const chunks = [];
|
|
4108
|
+
try {
|
|
4109
|
+
for (; ; ) {
|
|
4110
|
+
const { done, value } = await reader.read();
|
|
4111
|
+
if (done) break;
|
|
4112
|
+
chunks.push(value);
|
|
4113
|
+
}
|
|
4114
|
+
} finally {
|
|
4115
|
+
try {
|
|
4116
|
+
reader.releaseLock();
|
|
4117
|
+
} catch {
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
if (chunks.length === 0) return new Uint8Array(0);
|
|
4121
|
+
if (chunks.length === 1) return chunks[0];
|
|
4122
|
+
return Buffer.concat(chunks);
|
|
4123
|
+
}
|
|
3977
4124
|
|
|
3978
4125
|
// src/utils/getClientIp.ts
|
|
3979
4126
|
function getClientIp(req, trustedProxy = false) {
|
|
@@ -3994,6 +4141,20 @@ function getClientIp(req, trustedProxy = false) {
|
|
|
3994
4141
|
return "";
|
|
3995
4142
|
}
|
|
3996
4143
|
|
|
4144
|
+
// src/middleware/mergeVary.ts
|
|
4145
|
+
function mergeVary(meta, value) {
|
|
4146
|
+
const existing = meta.headers["Vary"] ?? meta.headers["vary"];
|
|
4147
|
+
if (!existing) {
|
|
4148
|
+
meta.headers["Vary"] = value;
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
const segments = existing.split(",").map((s) => s.trim().toLowerCase());
|
|
4152
|
+
if (!segments.includes(value.toLowerCase())) {
|
|
4153
|
+
const key = meta.headers["Vary"] !== void 0 ? "Vary" : "vary";
|
|
4154
|
+
meta.headers[key] = `${existing}, ${value}`;
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
|
|
3997
4158
|
// src/middleware/cors.ts
|
|
3998
4159
|
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
3999
4160
|
function cors(options = {}) {
|
|
@@ -4006,6 +4167,7 @@ function cors(options = {}) {
|
|
|
4006
4167
|
maxAge
|
|
4007
4168
|
} = options;
|
|
4008
4169
|
return async (ctx, next) => {
|
|
4170
|
+
const meta = ctx.meta;
|
|
4009
4171
|
const reqOrigin = ctx.headers.get("origin");
|
|
4010
4172
|
if (!reqOrigin) {
|
|
4011
4173
|
await next();
|
|
@@ -4020,14 +4182,7 @@ function cors(options = {}) {
|
|
|
4020
4182
|
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
4021
4183
|
}
|
|
4022
4184
|
if (origin === true || Array.isArray(origin)) {
|
|
4023
|
-
|
|
4024
|
-
if (existingVary) {
|
|
4025
|
-
if (!existingVary.toLowerCase().includes("origin")) {
|
|
4026
|
-
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
4027
|
-
}
|
|
4028
|
-
} else {
|
|
4029
|
-
ctx.setHeader("Vary", "Origin");
|
|
4030
|
-
}
|
|
4185
|
+
mergeVary(meta, "Origin");
|
|
4031
4186
|
}
|
|
4032
4187
|
if (!allowOrigin) {
|
|
4033
4188
|
await next();
|
|
@@ -4178,17 +4333,6 @@ async function compressBody(encoding, body) {
|
|
|
4178
4333
|
return deflateAsync(buf);
|
|
4179
4334
|
}
|
|
4180
4335
|
}
|
|
4181
|
-
function mergeVary(meta, value) {
|
|
4182
|
-
const existing = meta.headers["Vary"] ?? meta.headers["vary"];
|
|
4183
|
-
if (!existing) {
|
|
4184
|
-
meta.headers["Vary"] = value;
|
|
4185
|
-
return;
|
|
4186
|
-
}
|
|
4187
|
-
if (!existing.toLowerCase().includes(value.toLowerCase())) {
|
|
4188
|
-
const key = meta.headers["Vary"] !== void 0 ? "Vary" : "vary";
|
|
4189
|
-
meta.headers[key] = `${existing}, ${value}`;
|
|
4190
|
-
}
|
|
4191
|
-
}
|
|
4192
4336
|
function compression(options = {}) {
|
|
4193
4337
|
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
4194
4338
|
return async (ctx, next) => {
|
|
@@ -4338,9 +4482,9 @@ function logger(options = {}) {
|
|
|
4338
4482
|
}
|
|
4339
4483
|
|
|
4340
4484
|
// src/server/handleWsUpgrade.ts
|
|
4341
|
-
import
|
|
4485
|
+
import fs11 from "fs";
|
|
4342
4486
|
import { WebSocketServer, WebSocket } from "ws";
|
|
4343
|
-
import
|
|
4487
|
+
import path13 from "path";
|
|
4344
4488
|
|
|
4345
4489
|
// src/server/serverUtils.ts
|
|
4346
4490
|
function nodeHttpToWebHeaders(req) {
|
|
@@ -4359,13 +4503,15 @@ function buildErrorResponse(err, config) {
|
|
|
4359
4503
|
try {
|
|
4360
4504
|
return formatErrorResponse(err, config);
|
|
4361
4505
|
} catch {
|
|
4362
|
-
|
|
4506
|
+
const fallback = new Response(
|
|
4363
4507
|
JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
|
|
4364
4508
|
{
|
|
4365
4509
|
status: 500,
|
|
4366
4510
|
headers: { "Content-Type": "application/json" }
|
|
4367
4511
|
}
|
|
4368
4512
|
);
|
|
4513
|
+
markBufferedBody(fallback);
|
|
4514
|
+
return fallback;
|
|
4369
4515
|
}
|
|
4370
4516
|
}
|
|
4371
4517
|
|
|
@@ -4396,7 +4542,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
4396
4542
|
const dist = getDevDist();
|
|
4397
4543
|
if (dist) {
|
|
4398
4544
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
4399
|
-
if (sourcePath &&
|
|
4545
|
+
if (sourcePath && fs11.existsSync(sourcePath)) {
|
|
4400
4546
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
4401
4547
|
}
|
|
4402
4548
|
}
|
|
@@ -4411,27 +4557,47 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
4411
4557
|
function bindEvents(rawSocket, handlers) {
|
|
4412
4558
|
if (!handlers) return;
|
|
4413
4559
|
const ws = wrapWsSocket(rawSocket);
|
|
4560
|
+
const guard = (event, fn) => {
|
|
4561
|
+
try {
|
|
4562
|
+
fn();
|
|
4563
|
+
} catch (err) {
|
|
4564
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4565
|
+
if (handlers.onError) {
|
|
4566
|
+
try {
|
|
4567
|
+
handlers.onError(ws, error);
|
|
4568
|
+
} catch (hookErr) {
|
|
4569
|
+
console.error(`[faapi] WS onError \u56DE\u8C03\u629B\u9519 (after ${event}):`, hookErr);
|
|
4570
|
+
}
|
|
4571
|
+
} else {
|
|
4572
|
+
console.error(`[faapi] WS ${event} \u56DE\u8C03\u629B\u9519:`, error);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
};
|
|
4414
4576
|
if (handlers.onOpen) {
|
|
4415
4577
|
if (rawSocket.readyState === WebSocket.OPEN) {
|
|
4416
|
-
handlers.onOpen(ws);
|
|
4578
|
+
guard("onOpen", () => handlers.onOpen(ws));
|
|
4417
4579
|
} else {
|
|
4418
|
-
rawSocket.once("open", () => handlers.onOpen(ws));
|
|
4580
|
+
rawSocket.once("open", () => guard("onOpen", () => handlers.onOpen(ws)));
|
|
4419
4581
|
}
|
|
4420
4582
|
}
|
|
4421
4583
|
if (handlers.onMessage) {
|
|
4422
4584
|
rawSocket.on("message", (data, isBinary) => {
|
|
4423
4585
|
const buf = toWsBuffer(data);
|
|
4424
|
-
handlers.onMessage(ws, isBinary ? buf : buf.toString("utf8"));
|
|
4586
|
+
guard("onMessage", () => handlers.onMessage(ws, isBinary ? buf : buf.toString("utf8")));
|
|
4425
4587
|
});
|
|
4426
4588
|
}
|
|
4427
4589
|
if (handlers.onClose) {
|
|
4428
4590
|
rawSocket.on("close", (code, reason) => {
|
|
4429
|
-
handlers.onClose(ws, code, reason.toString("utf8"));
|
|
4591
|
+
guard("onClose", () => handlers.onClose(ws, code, reason.toString("utf8")));
|
|
4430
4592
|
});
|
|
4431
4593
|
}
|
|
4432
4594
|
if (handlers.onError) {
|
|
4433
4595
|
rawSocket.on("error", (err) => {
|
|
4434
|
-
|
|
4596
|
+
try {
|
|
4597
|
+
handlers.onError(ws, err);
|
|
4598
|
+
} catch (hookErr) {
|
|
4599
|
+
console.error("[faapi] WS onError \u56DE\u8C03\u629B\u9519 (after onError):", hookErr);
|
|
4600
|
+
}
|
|
4435
4601
|
});
|
|
4436
4602
|
}
|
|
4437
4603
|
}
|
|
@@ -4510,7 +4676,7 @@ function attachWebSocket(options) {
|
|
|
4510
4676
|
const finalHandler = async () => {
|
|
4511
4677
|
let handlers;
|
|
4512
4678
|
try {
|
|
4513
|
-
const absoluteFilePath =
|
|
4679
|
+
const absoluteFilePath = path13.resolve(rootDir, route.filePath);
|
|
4514
4680
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
4515
4681
|
} catch (err) {
|
|
4516
4682
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -4715,9 +4881,17 @@ function createServer(options) {
|
|
|
4715
4881
|
bodyLimit,
|
|
4716
4882
|
trustedProxy,
|
|
4717
4883
|
registries
|
|
4718
|
-
).catch(() => {
|
|
4719
|
-
|
|
4720
|
-
|
|
4884
|
+
).catch((err) => {
|
|
4885
|
+
console.error("[faapi] Request pipeline failed after error response attempt:", err);
|
|
4886
|
+
try {
|
|
4887
|
+
if (!res.headersSent) {
|
|
4888
|
+
res.statusCode = 500;
|
|
4889
|
+
res.end();
|
|
4890
|
+
} else if (!res.writableEnded) {
|
|
4891
|
+
res.end();
|
|
4892
|
+
}
|
|
4893
|
+
} catch {
|
|
4894
|
+
}
|
|
4721
4895
|
});
|
|
4722
4896
|
});
|
|
4723
4897
|
attachWebSocket({
|
|
@@ -4760,13 +4934,14 @@ function getRoutePaths(route, rootDir, dist) {
|
|
|
4760
4934
|
let cached = routePathCache.get(route);
|
|
4761
4935
|
if (!cached) {
|
|
4762
4936
|
cached = {
|
|
4763
|
-
absFilePath:
|
|
4937
|
+
absFilePath: path14.resolve(rootDir, route.filePath),
|
|
4764
4938
|
schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
|
|
4765
4939
|
};
|
|
4766
4940
|
routePathCache.set(route, cached);
|
|
4767
4941
|
}
|
|
4768
4942
|
return cached;
|
|
4769
4943
|
}
|
|
4944
|
+
var mergedInjectorsCache = /* @__PURE__ */ new WeakMap();
|
|
4770
4945
|
function createRoutePipeline(opts) {
|
|
4771
4946
|
const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
4772
4947
|
return async () => {
|
|
@@ -4799,7 +4974,15 @@ function createRoutePipeline(opts) {
|
|
|
4799
4974
|
route.injectors = {};
|
|
4800
4975
|
}
|
|
4801
4976
|
}
|
|
4802
|
-
|
|
4977
|
+
let mergedInjectors = route.injectors;
|
|
4978
|
+
if (globalInjectors) {
|
|
4979
|
+
let merged = mergedInjectorsCache.get(route);
|
|
4980
|
+
if (!merged) {
|
|
4981
|
+
merged = { ...globalInjectors, ...route.injectors };
|
|
4982
|
+
mergedInjectorsCache.set(route, merged);
|
|
4983
|
+
}
|
|
4984
|
+
mergedInjectors = merged;
|
|
4985
|
+
}
|
|
4803
4986
|
return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
|
|
4804
4987
|
};
|
|
4805
4988
|
}
|
|
@@ -4849,6 +5032,10 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
|
|
|
4849
5032
|
await sendSuccessResponse(response, res);
|
|
4850
5033
|
} catch (err) {
|
|
4851
5034
|
if (res.destroyed || res.writableEnded) return;
|
|
5035
|
+
if (err instanceof PayloadTooLargeError) {
|
|
5036
|
+
res.setHeader("Connection", "close");
|
|
5037
|
+
res.once("finish", () => req.destroy());
|
|
5038
|
+
}
|
|
4852
5039
|
await sendErrorResponse(err, meta, res, onError, ctx);
|
|
4853
5040
|
}
|
|
4854
5041
|
}
|
|
@@ -4871,7 +5058,7 @@ async function createTestServer(options) {
|
|
|
4871
5058
|
} = options;
|
|
4872
5059
|
const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
|
|
4873
5060
|
const sorted = sortRoutes(routes);
|
|
4874
|
-
const schemaDist = dist ?
|
|
5061
|
+
const schemaDist = dist ? path15.isAbsolute(dist) ? dist : path15.resolve(rootDir, dist) : await fs12.mkdtemp(path15.join(os.tmpdir(), "faapi-test-schema-"));
|
|
4875
5062
|
await generateSchemaFiles(sorted, rootDir, schemaDist);
|
|
4876
5063
|
const { server } = createServer({
|
|
4877
5064
|
routes: sorted,
|
|
@@ -4903,7 +5090,7 @@ async function createTestServer(options) {
|
|
|
4903
5090
|
await new Promise((resolve) => {
|
|
4904
5091
|
server.close(() => resolve());
|
|
4905
5092
|
});
|
|
4906
|
-
await
|
|
5093
|
+
await fs12.rm(schemaDist, { recursive: true, force: true }).catch(() => {
|
|
4907
5094
|
});
|
|
4908
5095
|
invalidateSchemaCache();
|
|
4909
5096
|
}
|