@faapi/faapi 1.1.1 → 1.2.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 +3603 -3434
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1249 -762
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
1
11
|
// src/ast/createProgram.ts
|
|
2
12
|
import ts from "typescript";
|
|
3
|
-
var programCache = /* @__PURE__ */ new Map();
|
|
4
13
|
function invalidateProgramCache() {
|
|
5
14
|
programCache.clear();
|
|
6
15
|
}
|
|
@@ -20,22 +29,16 @@ function createProgram(filePath) {
|
|
|
20
29
|
programCache.set(filePath, program);
|
|
21
30
|
return program;
|
|
22
31
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
var programCache;
|
|
33
|
+
var init_createProgram = __esm({
|
|
34
|
+
"src/ast/createProgram.ts"() {
|
|
35
|
+
"use strict";
|
|
36
|
+
programCache = /* @__PURE__ */ new Map();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
26
39
|
|
|
27
40
|
// src/ast/resolveTypeNode.ts
|
|
28
41
|
import ts2 from "typescript";
|
|
29
|
-
var SchemaExtractionError = class extends Error {
|
|
30
|
-
constructor(typeText, reason, options) {
|
|
31
|
-
super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
|
|
32
|
-
this.typeText = typeText;
|
|
33
|
-
this.reason = reason;
|
|
34
|
-
this.name = "SchemaExtractionError";
|
|
35
|
-
}
|
|
36
|
-
typeText;
|
|
37
|
-
reason;
|
|
38
|
-
};
|
|
39
42
|
function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
40
43
|
const kind = typeNode.kind;
|
|
41
44
|
switch (kind) {
|
|
@@ -415,26 +418,6 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
|
|
|
415
418
|
}
|
|
416
419
|
return { kind: "object", properties };
|
|
417
420
|
}
|
|
418
|
-
var NUMBER_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
|
|
419
|
-
"max",
|
|
420
|
-
"min",
|
|
421
|
-
"int",
|
|
422
|
-
"positive",
|
|
423
|
-
"negative",
|
|
424
|
-
"nonnegative",
|
|
425
|
-
"nonpositive"
|
|
426
|
-
]);
|
|
427
|
-
var LENGTH_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
|
|
428
|
-
"maxLength",
|
|
429
|
-
"minLength",
|
|
430
|
-
"length"
|
|
431
|
-
]);
|
|
432
|
-
var STRING_FORMAT_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
|
|
433
|
-
"regex",
|
|
434
|
-
"email",
|
|
435
|
-
"url",
|
|
436
|
-
"uuid"
|
|
437
|
-
]);
|
|
438
421
|
function extractConstraintsFromJsDoc(node, fieldName) {
|
|
439
422
|
const jsDocs = ts2.getJSDocCommentsAndTags(node).filter((entry) => ts2.isJSDoc(entry));
|
|
440
423
|
if (jsDocs.length === 0) return [];
|
|
@@ -551,8 +534,45 @@ function validateConstraints(constraints, type, fieldName) {
|
|
|
551
534
|
}
|
|
552
535
|
}
|
|
553
536
|
}
|
|
537
|
+
var SchemaExtractionError, NUMBER_CONSTRAINT_KINDS, LENGTH_CONSTRAINT_KINDS, STRING_FORMAT_CONSTRAINT_KINDS;
|
|
538
|
+
var init_resolveTypeNode = __esm({
|
|
539
|
+
"src/ast/resolveTypeNode.ts"() {
|
|
540
|
+
"use strict";
|
|
541
|
+
SchemaExtractionError = class extends Error {
|
|
542
|
+
constructor(typeText, reason, options) {
|
|
543
|
+
super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
|
|
544
|
+
this.typeText = typeText;
|
|
545
|
+
this.reason = reason;
|
|
546
|
+
this.name = "SchemaExtractionError";
|
|
547
|
+
}
|
|
548
|
+
typeText;
|
|
549
|
+
reason;
|
|
550
|
+
};
|
|
551
|
+
NUMBER_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
|
|
552
|
+
"max",
|
|
553
|
+
"min",
|
|
554
|
+
"int",
|
|
555
|
+
"positive",
|
|
556
|
+
"negative",
|
|
557
|
+
"nonnegative",
|
|
558
|
+
"nonpositive"
|
|
559
|
+
]);
|
|
560
|
+
LENGTH_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
|
|
561
|
+
"maxLength",
|
|
562
|
+
"minLength",
|
|
563
|
+
"length"
|
|
564
|
+
]);
|
|
565
|
+
STRING_FORMAT_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
|
|
566
|
+
"regex",
|
|
567
|
+
"email",
|
|
568
|
+
"url",
|
|
569
|
+
"uuid"
|
|
570
|
+
]);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
554
573
|
|
|
555
574
|
// src/ast/extractHandlerTypes.ts
|
|
575
|
+
import ts3 from "typescript";
|
|
556
576
|
function extractTypeInfo(program, filePath, typeName) {
|
|
557
577
|
const sourceFile = program.getSourceFile(filePath);
|
|
558
578
|
if (!sourceFile) return null;
|
|
@@ -648,6 +668,12 @@ function withFileContext(filePath, typeName, fn) {
|
|
|
648
668
|
throw err;
|
|
649
669
|
}
|
|
650
670
|
}
|
|
671
|
+
var init_extractHandlerTypes = __esm({
|
|
672
|
+
"src/ast/extractHandlerTypes.ts"() {
|
|
673
|
+
"use strict";
|
|
674
|
+
init_resolveTypeNode();
|
|
675
|
+
}
|
|
676
|
+
});
|
|
651
677
|
|
|
652
678
|
// src/runtime/inputType.ts
|
|
653
679
|
function getInputTypeForMethod(method) {
|
|
@@ -661,32 +687,24 @@ function hasBody(method) {
|
|
|
661
687
|
const upper = method.toUpperCase();
|
|
662
688
|
return upper === "POST" || upper === "PUT" || upper === "PATCH" || upper === "DELETE";
|
|
663
689
|
}
|
|
690
|
+
var init_inputType = __esm({
|
|
691
|
+
"src/runtime/inputType.ts"() {
|
|
692
|
+
"use strict";
|
|
693
|
+
}
|
|
694
|
+
});
|
|
664
695
|
|
|
665
696
|
// src/validator/schemaName.ts
|
|
666
697
|
function getSchemaName(method, inputType) {
|
|
667
698
|
return `${method.toUpperCase()}${inputType.charAt(0).toUpperCase() + inputType.slice(1)}`;
|
|
668
699
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
700
|
+
var init_schemaName = __esm({
|
|
701
|
+
"src/validator/schemaName.ts"() {
|
|
702
|
+
"use strict";
|
|
703
|
+
}
|
|
704
|
+
});
|
|
672
705
|
|
|
673
706
|
// src/injection/resolveInjection.ts
|
|
674
707
|
import ts4 from "typescript";
|
|
675
|
-
var PARAM_TYPE_MAP = {
|
|
676
|
-
query: "query",
|
|
677
|
-
body: "body",
|
|
678
|
-
form: "form",
|
|
679
|
-
headers: "headers",
|
|
680
|
-
params: "params",
|
|
681
|
-
context: "context",
|
|
682
|
-
ctx: "context",
|
|
683
|
-
// 别名
|
|
684
|
-
cookies: "cookies",
|
|
685
|
-
ip: "ip",
|
|
686
|
-
ua: "ua",
|
|
687
|
-
files: "files",
|
|
688
|
-
fields: "fields"
|
|
689
|
-
};
|
|
690
708
|
function resolveInjection(fn) {
|
|
691
709
|
const fnStr = fn.toString();
|
|
692
710
|
const params = extractParamsWithAst(fnStr);
|
|
@@ -755,8 +773,30 @@ function extractParamName(param, names) {
|
|
|
755
773
|
return;
|
|
756
774
|
}
|
|
757
775
|
}
|
|
776
|
+
var PARAM_TYPE_MAP;
|
|
777
|
+
var init_resolveInjection = __esm({
|
|
778
|
+
"src/injection/resolveInjection.ts"() {
|
|
779
|
+
"use strict";
|
|
780
|
+
PARAM_TYPE_MAP = {
|
|
781
|
+
query: "query",
|
|
782
|
+
body: "body",
|
|
783
|
+
form: "form",
|
|
784
|
+
headers: "headers",
|
|
785
|
+
params: "params",
|
|
786
|
+
context: "context",
|
|
787
|
+
ctx: "context",
|
|
788
|
+
// 别名
|
|
789
|
+
cookies: "cookies",
|
|
790
|
+
ip: "ip",
|
|
791
|
+
ua: "ua",
|
|
792
|
+
files: "files",
|
|
793
|
+
fields: "fields"
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
});
|
|
758
797
|
|
|
759
798
|
// src/injection/analyzeInjection.ts
|
|
799
|
+
import ts5 from "typescript";
|
|
760
800
|
function analyzeInjection(code, functionName) {
|
|
761
801
|
const sourceFile = ts5.createSourceFile("temp.ts", code, ts5.ScriptTarget.Latest, true);
|
|
762
802
|
const params = [];
|
|
@@ -799,6 +839,12 @@ function extractSchema(typeNode, sourceFile) {
|
|
|
799
839
|
}
|
|
800
840
|
return schema;
|
|
801
841
|
}
|
|
842
|
+
var init_analyzeInjection = __esm({
|
|
843
|
+
"src/injection/analyzeInjection.ts"() {
|
|
844
|
+
"use strict";
|
|
845
|
+
init_resolveInjection();
|
|
846
|
+
}
|
|
847
|
+
});
|
|
802
848
|
|
|
803
849
|
// src/cli/collectRouteSchemaSources.ts
|
|
804
850
|
import path from "path";
|
|
@@ -848,192 +894,632 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
848
894
|
}
|
|
849
895
|
return { sources, allTypesByFile, mergedAllTypes };
|
|
850
896
|
}
|
|
897
|
+
var init_collectRouteSchemaSources = __esm({
|
|
898
|
+
"src/cli/collectRouteSchemaSources.ts"() {
|
|
899
|
+
"use strict";
|
|
900
|
+
init_createProgram();
|
|
901
|
+
init_extractHandlerTypes();
|
|
902
|
+
init_inputType();
|
|
903
|
+
init_schemaName();
|
|
904
|
+
init_analyzeInjection();
|
|
905
|
+
}
|
|
906
|
+
});
|
|
851
907
|
|
|
852
|
-
// src/
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (!reqOrigin) {
|
|
866
|
-
await next();
|
|
908
|
+
// src/ast/generateZodSchema.ts
|
|
909
|
+
function collectNamedTypes(type, ctx) {
|
|
910
|
+
switch (type.kind) {
|
|
911
|
+
case "string":
|
|
912
|
+
case "number":
|
|
913
|
+
case "boolean":
|
|
914
|
+
case "bigint":
|
|
915
|
+
case "null":
|
|
916
|
+
case "undefined":
|
|
917
|
+
case "any":
|
|
918
|
+
case "unknown":
|
|
919
|
+
case "literal":
|
|
920
|
+
case "date":
|
|
867
921
|
return;
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if (origin === true) {
|
|
871
|
-
allowOrigin = reqOrigin;
|
|
872
|
-
} else if (typeof origin === "string") {
|
|
873
|
-
allowOrigin = reqOrigin === origin ? origin : null;
|
|
874
|
-
} else if (Array.isArray(origin)) {
|
|
875
|
-
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
876
|
-
}
|
|
877
|
-
if (!allowOrigin) {
|
|
878
|
-
await next();
|
|
922
|
+
case "array":
|
|
923
|
+
collectNamedTypes(type.element, ctx);
|
|
879
924
|
return;
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const existingVary = ctx.headers.get("vary");
|
|
884
|
-
if (existingVary) {
|
|
885
|
-
if (!existingVary.toLowerCase().includes("origin")) {
|
|
886
|
-
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
887
|
-
}
|
|
888
|
-
} else {
|
|
889
|
-
ctx.setHeader("Vary", "Origin");
|
|
925
|
+
case "tuple":
|
|
926
|
+
for (const el of type.elements) {
|
|
927
|
+
collectNamedTypes(el.type, ctx);
|
|
890
928
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
} else {
|
|
896
|
-
const requestHeaders = ctx.headers.get("access-control-request-headers");
|
|
897
|
-
if (requestHeaders) {
|
|
898
|
-
ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
929
|
+
return;
|
|
930
|
+
case "object":
|
|
931
|
+
for (const prop of type.properties) {
|
|
932
|
+
collectNamedTypes(prop.type, ctx);
|
|
899
933
|
}
|
|
934
|
+
return;
|
|
935
|
+
case "union":
|
|
936
|
+
for (const member of type.members) {
|
|
937
|
+
collectNamedTypes(member, ctx);
|
|
938
|
+
}
|
|
939
|
+
return;
|
|
940
|
+
case "record":
|
|
941
|
+
collectNamedTypes(type.key, ctx);
|
|
942
|
+
collectNamedTypes(type.value, ctx);
|
|
943
|
+
return;
|
|
944
|
+
case "map":
|
|
945
|
+
collectNamedTypes(type.key, ctx);
|
|
946
|
+
collectNamedTypes(type.value, ctx);
|
|
947
|
+
return;
|
|
948
|
+
case "set":
|
|
949
|
+
collectNamedTypes(type.element, ctx);
|
|
950
|
+
return;
|
|
951
|
+
case "ref": {
|
|
952
|
+
if (ctx.namedTypes.has(type.name)) return;
|
|
953
|
+
ctx.namedTypes.set(type.name, { kind: "any" });
|
|
954
|
+
const resolved = ctx.resolveType(type.name);
|
|
955
|
+
if (resolved) {
|
|
956
|
+
ctx.namedTypes.set(type.name, resolved);
|
|
957
|
+
collectNamedTypes(resolved, ctx);
|
|
958
|
+
}
|
|
959
|
+
return;
|
|
900
960
|
}
|
|
901
|
-
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
902
|
-
ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
903
|
-
}
|
|
904
|
-
if (credentials) {
|
|
905
|
-
ctx.setHeader("Access-Control-Allow-Credentials", "true");
|
|
906
|
-
}
|
|
907
|
-
if (maxAge !== void 0) {
|
|
908
|
-
ctx.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
909
|
-
}
|
|
910
|
-
if (ctx.method === "OPTIONS") {
|
|
911
|
-
return new Response(null, { status: 204 });
|
|
912
|
-
}
|
|
913
|
-
await next();
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
// src/middleware/logger.ts
|
|
918
|
-
function logger(options = {}) {
|
|
919
|
-
return async (ctx, next) => {
|
|
920
|
-
const log = options.log ?? console.log;
|
|
921
|
-
const start = Date.now();
|
|
922
|
-
try {
|
|
923
|
-
const response = await next();
|
|
924
|
-
const duration = Date.now() - start;
|
|
925
|
-
const entry = {
|
|
926
|
-
method: ctx.method,
|
|
927
|
-
path: ctx.path,
|
|
928
|
-
status: response.status,
|
|
929
|
-
durationMs: duration
|
|
930
|
-
};
|
|
931
|
-
log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);
|
|
932
|
-
return response;
|
|
933
|
-
} catch (err) {
|
|
934
|
-
const duration = Date.now() - start;
|
|
935
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
936
|
-
const status = err?.statusCode ?? 500;
|
|
937
|
-
const entry = {
|
|
938
|
-
method: ctx.method,
|
|
939
|
-
path: ctx.path,
|
|
940
|
-
status,
|
|
941
|
-
durationMs: duration,
|
|
942
|
-
error: message
|
|
943
|
-
};
|
|
944
|
-
log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
|
|
945
|
-
throw err;
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// src/middleware/helmet.ts
|
|
951
|
-
var DEFAULTS = {
|
|
952
|
-
contentSecurityPolicy: "default-src 'self'",
|
|
953
|
-
xFrameOptions: "SAMEORIGIN",
|
|
954
|
-
xContentTypeOptions: true,
|
|
955
|
-
referrerPolicy: "no-referrer",
|
|
956
|
-
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
|
957
|
-
xDnsPrefetchControl: true,
|
|
958
|
-
xDownloadOptions: true,
|
|
959
|
-
xPermittedCrossDomainPolicies: "none",
|
|
960
|
-
crossOriginOpenerPolicy: "same-origin",
|
|
961
|
-
crossOriginResourcePolicy: "same-origin",
|
|
962
|
-
crossOriginEmbedderPolicy: false,
|
|
963
|
-
originAgentCluster: true,
|
|
964
|
-
xPoweredBy: true
|
|
965
|
-
};
|
|
966
|
-
function helmet(options = {}) {
|
|
967
|
-
const opts = { ...DEFAULTS, ...options };
|
|
968
|
-
return async (ctx, next) => {
|
|
969
|
-
if (opts.contentSecurityPolicy !== false) {
|
|
970
|
-
ctx.setHeader("Content-Security-Policy", opts.contentSecurityPolicy);
|
|
971
|
-
}
|
|
972
|
-
if (opts.xFrameOptions !== false) {
|
|
973
|
-
ctx.setHeader("X-Frame-Options", opts.xFrameOptions);
|
|
974
|
-
}
|
|
975
|
-
if (opts.xContentTypeOptions) {
|
|
976
|
-
ctx.setHeader("X-Content-Type-Options", "nosniff");
|
|
977
|
-
}
|
|
978
|
-
if (opts.referrerPolicy !== false) {
|
|
979
|
-
ctx.setHeader("Referrer-Policy", opts.referrerPolicy);
|
|
980
|
-
}
|
|
981
|
-
if (opts.strictTransportSecurity !== false) {
|
|
982
|
-
ctx.setHeader("Strict-Transport-Security", opts.strictTransportSecurity);
|
|
983
|
-
}
|
|
984
|
-
if (opts.xDnsPrefetchControl) {
|
|
985
|
-
ctx.setHeader("X-DNS-Prefetch-Control", "off");
|
|
986
|
-
}
|
|
987
|
-
if (opts.xDownloadOptions) {
|
|
988
|
-
ctx.setHeader("X-Download-Options", "noopen");
|
|
989
|
-
}
|
|
990
|
-
if (opts.xPermittedCrossDomainPolicies !== false) {
|
|
991
|
-
ctx.setHeader("X-Permitted-Cross-Domain-Policies", opts.xPermittedCrossDomainPolicies);
|
|
992
|
-
}
|
|
993
|
-
if (opts.crossOriginOpenerPolicy !== false) {
|
|
994
|
-
ctx.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
|
|
995
|
-
}
|
|
996
|
-
if (opts.crossOriginResourcePolicy !== false) {
|
|
997
|
-
ctx.setHeader("Cross-Origin-Resource-Policy", opts.crossOriginResourcePolicy);
|
|
998
|
-
}
|
|
999
|
-
if (opts.crossOriginEmbedderPolicy !== false) {
|
|
1000
|
-
ctx.setHeader("Cross-Origin-Embedder-Policy", opts.crossOriginEmbedderPolicy);
|
|
1001
|
-
}
|
|
1002
|
-
if (opts.originAgentCluster) {
|
|
1003
|
-
ctx.setHeader("Origin-Agent-Cluster", "?1");
|
|
1004
|
-
}
|
|
1005
|
-
if (opts.xPoweredBy) {
|
|
1006
|
-
ctx.setHeader("X-Powered-By", "faapi");
|
|
1007
|
-
}
|
|
1008
|
-
return await next();
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// src/config/loadConfig.ts
|
|
1013
|
-
import path2 from "path";
|
|
1014
|
-
import fs from "fs";
|
|
1015
|
-
|
|
1016
|
-
// src/utils/importWithCacheBust.ts
|
|
1017
|
-
import { pathToFileURL } from "url";
|
|
1018
|
-
var loadTs;
|
|
1019
|
-
function setLoadTimestamp(ts6) {
|
|
1020
|
-
loadTs = ts6;
|
|
1021
|
-
}
|
|
1022
|
-
function getVitestImportActual() {
|
|
1023
|
-
const vi = globalThis.vi;
|
|
1024
|
-
if (typeof vi?.importActual !== "function") return void 0;
|
|
1025
|
-
return vi.importActual.bind(vi);
|
|
1026
|
-
}
|
|
1027
|
-
async function importWithCacheBust(filePath) {
|
|
1028
|
-
const importActual = getVitestImportActual();
|
|
1029
|
-
if (importActual) {
|
|
1030
|
-
return await importActual(filePath);
|
|
1031
961
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
962
|
+
}
|
|
963
|
+
function runtimeTypeToZodExpression(type, ctx, constraints) {
|
|
964
|
+
const expr = baseExpression(type, ctx);
|
|
965
|
+
const withConstraints = constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;
|
|
966
|
+
if (ctx.coerce && (type.kind === "number" || type.kind === "boolean")) {
|
|
967
|
+
return wrapCoercePreprocess(type.kind, withConstraints);
|
|
1035
968
|
}
|
|
1036
|
-
return
|
|
969
|
+
return withConstraints;
|
|
970
|
+
}
|
|
971
|
+
function applyConstraints(baseExpr, constraints, typeKind) {
|
|
972
|
+
const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join("");
|
|
973
|
+
return `${baseExpr}${suffix}`;
|
|
974
|
+
}
|
|
975
|
+
function constraintToZodChain(constraint, _typeKind) {
|
|
976
|
+
switch (constraint.kind) {
|
|
977
|
+
case "max":
|
|
978
|
+
return `.max(${constraint.value})`;
|
|
979
|
+
case "min":
|
|
980
|
+
return `.min(${constraint.value})`;
|
|
981
|
+
case "int":
|
|
982
|
+
return ".int()";
|
|
983
|
+
case "positive":
|
|
984
|
+
return ".positive()";
|
|
985
|
+
case "negative":
|
|
986
|
+
return ".negative()";
|
|
987
|
+
case "nonnegative":
|
|
988
|
+
return ".nonnegative()";
|
|
989
|
+
case "nonpositive":
|
|
990
|
+
return ".nonpositive()";
|
|
991
|
+
case "maxLength":
|
|
992
|
+
return `.max(${constraint.value})`;
|
|
993
|
+
case "minLength":
|
|
994
|
+
return `.min(${constraint.value})`;
|
|
995
|
+
case "length":
|
|
996
|
+
return `.length(${constraint.value})`;
|
|
997
|
+
case "regex": {
|
|
998
|
+
const flags = constraint.flags ?? "";
|
|
999
|
+
return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ""}))`;
|
|
1000
|
+
}
|
|
1001
|
+
case "email":
|
|
1002
|
+
return ".email()";
|
|
1003
|
+
case "url":
|
|
1004
|
+
return ".url()";
|
|
1005
|
+
case "uuid":
|
|
1006
|
+
return ".uuid()";
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function baseExpression(type, ctx) {
|
|
1010
|
+
switch (type.kind) {
|
|
1011
|
+
case "string":
|
|
1012
|
+
return "z.string()";
|
|
1013
|
+
case "number":
|
|
1014
|
+
return "z.number()";
|
|
1015
|
+
case "boolean":
|
|
1016
|
+
return "z.boolean()";
|
|
1017
|
+
case "bigint":
|
|
1018
|
+
return "z.never()";
|
|
1019
|
+
case "null":
|
|
1020
|
+
return "z.null()";
|
|
1021
|
+
case "undefined":
|
|
1022
|
+
return "z.undefined()";
|
|
1023
|
+
case "any":
|
|
1024
|
+
case "unknown":
|
|
1025
|
+
return "z.unknown()";
|
|
1026
|
+
case "literal":
|
|
1027
|
+
return `z.literal(${JSON.stringify(type.value)})`;
|
|
1028
|
+
case "array":
|
|
1029
|
+
return `z.array(${runtimeTypeToZodExpression(type.element, ctx)})`;
|
|
1030
|
+
case "tuple":
|
|
1031
|
+
return generateTupleExpression(type.elements, ctx);
|
|
1032
|
+
case "object":
|
|
1033
|
+
return generateObjectExpression(type.properties, ctx);
|
|
1034
|
+
case "union":
|
|
1035
|
+
return generateUnionExpression(type.members, ctx);
|
|
1036
|
+
case "date":
|
|
1037
|
+
return 'z.preprocess((v) => (typeof v === "string" ? new Date(v) : v), z.date())';
|
|
1038
|
+
case "record":
|
|
1039
|
+
return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;
|
|
1040
|
+
case "map":
|
|
1041
|
+
return `z.preprocess(coerceMap, z.map(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)}))`;
|
|
1042
|
+
case "set":
|
|
1043
|
+
return `z.preprocess(coerceSet, z.set(${runtimeTypeToZodExpression(type.element, ctx)}))`;
|
|
1044
|
+
case "ref":
|
|
1045
|
+
if (type.name === ctx.entryTypeName) {
|
|
1046
|
+
return `${ctx.entryExportName}Schema`;
|
|
1047
|
+
}
|
|
1048
|
+
return `${type.name}Schema`;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function generateHelpersFileSource() {
|
|
1052
|
+
return [
|
|
1053
|
+
"// faapi-helpers.js \u2014 faapi \u81EA\u52A8\u751F\u6210\u7684\u516C\u7528\u51FD\u6570\uFF08\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91\uFF09",
|
|
1054
|
+
COERCE_NUMBER_HELPER,
|
|
1055
|
+
COERCE_BOOLEAN_HELPER,
|
|
1056
|
+
COERCE_MAP_HELPER,
|
|
1057
|
+
COERCE_SET_HELPER,
|
|
1058
|
+
""
|
|
1059
|
+
].join("\n");
|
|
1060
|
+
}
|
|
1061
|
+
function usesCoerceHelpers(code) {
|
|
1062
|
+
return code.includes("coerceNumber") || code.includes("coerceBoolean") || code.includes("coerceMap") || code.includes("coerceSet");
|
|
1063
|
+
}
|
|
1064
|
+
function wrapCoercePreprocess(kind, inner) {
|
|
1065
|
+
if (kind === "number") {
|
|
1066
|
+
return `z.preprocess(coerceNumber, ${inner})`;
|
|
1067
|
+
}
|
|
1068
|
+
return `z.preprocess(coerceBoolean, ${inner})`;
|
|
1069
|
+
}
|
|
1070
|
+
function generateTupleExpression(elements, ctx) {
|
|
1071
|
+
const fixedExprs = [];
|
|
1072
|
+
const fixedOptional = [];
|
|
1073
|
+
let restExpression = "";
|
|
1074
|
+
let restStarted = false;
|
|
1075
|
+
for (const el of elements) {
|
|
1076
|
+
if (el.rest) {
|
|
1077
|
+
restExpression = runtimeTypeToZodExpression(el.type, ctx);
|
|
1078
|
+
restStarted = true;
|
|
1079
|
+
} else if (!restStarted) {
|
|
1080
|
+
fixedExprs.push(runtimeTypeToZodExpression(el.type, ctx));
|
|
1081
|
+
fixedOptional.push(el.optional);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (restExpression) {
|
|
1085
|
+
return `z.tuple([${fixedExprs.join(", ")}]).rest(${restExpression})`;
|
|
1086
|
+
}
|
|
1087
|
+
const hasOptional = fixedOptional.some((o) => o);
|
|
1088
|
+
if (!hasOptional) {
|
|
1089
|
+
return `z.tuple([${fixedExprs.join(", ")}])`;
|
|
1090
|
+
}
|
|
1091
|
+
const variants = [];
|
|
1092
|
+
for (let len = fixedExprs.length; len >= 0; len--) {
|
|
1093
|
+
const removed = fixedOptional.slice(len);
|
|
1094
|
+
if (removed.length > 0 && removed.some((o) => !o)) {
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
const subset = fixedExprs.slice(0, len);
|
|
1098
|
+
variants.push(`z.tuple([${subset.join(", ")}])`);
|
|
1099
|
+
}
|
|
1100
|
+
variants.reverse();
|
|
1101
|
+
if (variants.length === 1) {
|
|
1102
|
+
return variants[0];
|
|
1103
|
+
}
|
|
1104
|
+
return `z.union([${variants.join(", ")}])`;
|
|
1105
|
+
}
|
|
1106
|
+
function generateObjectExpression(properties, ctx) {
|
|
1107
|
+
const fields = properties.map((prop) => {
|
|
1108
|
+
const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);
|
|
1109
|
+
const finalExpr = prop.optional ? `${expr}.optional()` : expr;
|
|
1110
|
+
return `${JSON.stringify(prop.name)}: ${finalExpr}`;
|
|
1111
|
+
});
|
|
1112
|
+
return `z.object({ ${fields.join(", ")} })`;
|
|
1113
|
+
}
|
|
1114
|
+
function generateUnionExpression(members, ctx) {
|
|
1115
|
+
const hasNull = members.some((m) => m.kind === "null");
|
|
1116
|
+
const nonNull = members.filter((m) => m.kind !== "null");
|
|
1117
|
+
if (hasNull && nonNull.length === 1) {
|
|
1118
|
+
return `${runtimeTypeToZodExpression(nonNull[0], ctx)}.nullable()`;
|
|
1119
|
+
}
|
|
1120
|
+
if (hasNull) {
|
|
1121
|
+
const unionInner2 = nonNull.map((m) => runtimeTypeToZodExpression(m, ctx)).join(", ");
|
|
1122
|
+
return `z.union([${unionInner2}]).nullable()`;
|
|
1123
|
+
}
|
|
1124
|
+
const unionInner = members.map((m) => runtimeTypeToZodExpression(m, ctx)).join(", ");
|
|
1125
|
+
return `z.union([${unionInner}])`;
|
|
1126
|
+
}
|
|
1127
|
+
function generateNamedTypeDeclaration(name, type, ctx) {
|
|
1128
|
+
const expr = runtimeTypeToZodExpression(type, ctx);
|
|
1129
|
+
const hasRef = containsRef(type, /* @__PURE__ */ new Set([name]));
|
|
1130
|
+
if (hasRef) {
|
|
1131
|
+
return `const ${name}Schema = z.lazy(() => ${expr});`;
|
|
1132
|
+
}
|
|
1133
|
+
return `const ${name}Schema = ${expr};`;
|
|
1134
|
+
}
|
|
1135
|
+
function containsRef(type, visited) {
|
|
1136
|
+
switch (type.kind) {
|
|
1137
|
+
case "ref":
|
|
1138
|
+
return visited.has(type.name);
|
|
1139
|
+
case "array":
|
|
1140
|
+
return containsRef(type.element, visited);
|
|
1141
|
+
case "tuple":
|
|
1142
|
+
return type.elements.some((el) => containsRef(el.type, visited));
|
|
1143
|
+
case "object":
|
|
1144
|
+
return type.properties.some((prop) => containsRef(prop.type, visited));
|
|
1145
|
+
case "union":
|
|
1146
|
+
return type.members.some((m) => containsRef(m, visited));
|
|
1147
|
+
case "record":
|
|
1148
|
+
return containsRef(type.key, visited) || containsRef(type.value, visited);
|
|
1149
|
+
case "map":
|
|
1150
|
+
return containsRef(type.key, visited) || containsRef(type.value, visited);
|
|
1151
|
+
case "set":
|
|
1152
|
+
return containsRef(type.element, visited);
|
|
1153
|
+
default:
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
function generateZodSchemaSource(typeInfo, resolveType, exportName, coerce = false) {
|
|
1158
|
+
const ctx = new CodeGenContext(resolveType);
|
|
1159
|
+
const name = exportName ?? typeInfo.name;
|
|
1160
|
+
ctx.entryTypeName = typeInfo.name;
|
|
1161
|
+
ctx.entryExportName = name;
|
|
1162
|
+
ctx.coerce = coerce;
|
|
1163
|
+
collectNamedTypes(typeInfo.runtimeType, ctx);
|
|
1164
|
+
ctx.namedTypes.delete(typeInfo.name);
|
|
1165
|
+
const lines = [];
|
|
1166
|
+
lines.push("import { z } from 'zod';");
|
|
1167
|
+
lines.push("");
|
|
1168
|
+
for (const [n, type] of ctx.namedTypes) {
|
|
1169
|
+
lines.push(generateNamedTypeDeclaration(n, type, ctx));
|
|
1170
|
+
}
|
|
1171
|
+
if (ctx.namedTypes.size > 0) lines.push("");
|
|
1172
|
+
const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);
|
|
1173
|
+
const hasSelfRef = containsRef(typeInfo.runtimeType, /* @__PURE__ */ new Set([typeInfo.name]));
|
|
1174
|
+
if (hasSelfRef) {
|
|
1175
|
+
lines.push(`export const ${name}Schema = z.lazy(() => ${entryExpr});`);
|
|
1176
|
+
} else {
|
|
1177
|
+
lines.push(`export const ${name}Schema = ${entryExpr};`);
|
|
1178
|
+
}
|
|
1179
|
+
return lines.join("\n");
|
|
1180
|
+
}
|
|
1181
|
+
var CodeGenContext, COERCE_NUMBER_HELPER, COERCE_BOOLEAN_HELPER, COERCE_MAP_HELPER, COERCE_SET_HELPER, HELPERS_FILENAME;
|
|
1182
|
+
var init_generateZodSchema = __esm({
|
|
1183
|
+
"src/ast/generateZodSchema.ts"() {
|
|
1184
|
+
"use strict";
|
|
1185
|
+
CodeGenContext = class {
|
|
1186
|
+
/** 命名类型集合:name → RuntimeType */
|
|
1187
|
+
namedTypes = /* @__PURE__ */ new Map();
|
|
1188
|
+
/** 类型解析器(用于解析 ref 的实际类型) */
|
|
1189
|
+
resolveType;
|
|
1190
|
+
/** 入口类型原始名(typeInfo.name,用于识别入口类型的自引用) */
|
|
1191
|
+
entryTypeName = "";
|
|
1192
|
+
/** 入口类型导出名(exportName,自引用时用此名生成变量名) */
|
|
1193
|
+
entryExportName = "";
|
|
1194
|
+
/**
|
|
1195
|
+
* 是否生成 coerce 逻辑(query/params 场景,URL 来源均为 string)
|
|
1196
|
+
*
|
|
1197
|
+
* true 时为 number/boolean 字段包 z.preprocess,把合法的字符串转成对应类型。
|
|
1198
|
+
* 嵌套类型(array/object/tuple/union 等)的元素递归处理。
|
|
1199
|
+
*/
|
|
1200
|
+
coerce = false;
|
|
1201
|
+
constructor(resolveType) {
|
|
1202
|
+
this.resolveType = resolveType;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
COERCE_NUMBER_HELPER = 'export const coerceNumber = (v) => typeof v === "string" && v.trim() !== "" && !isNaN(Number(v)) ? Number(v) : v;';
|
|
1206
|
+
COERCE_BOOLEAN_HELPER = 'export const coerceBoolean = (v) => v === "true" || v === "1" ? true : v === "false" || v === "0" ? false : v;';
|
|
1207
|
+
COERCE_MAP_HELPER = 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === "object" ? new Map(Object.entries(v)) : v);';
|
|
1208
|
+
COERCE_SET_HELPER = "export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);";
|
|
1209
|
+
HELPERS_FILENAME = "faapi-helpers.js";
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
// src/cli/generateSchemaFiles.ts
|
|
1214
|
+
var generateSchemaFiles_exports = {};
|
|
1215
|
+
__export(generateSchemaFiles_exports, {
|
|
1216
|
+
generateSchemaFileSource: () => generateSchemaFileSource,
|
|
1217
|
+
generateSchemaFiles: () => generateSchemaFiles,
|
|
1218
|
+
getHelpersImportPath: () => getHelpersImportPath,
|
|
1219
|
+
getRuntimeSchemaPath: () => getRuntimeSchemaPath,
|
|
1220
|
+
getSchemaOutputPath: () => getSchemaOutputPath
|
|
1221
|
+
});
|
|
1222
|
+
import path5 from "path";
|
|
1223
|
+
import fs4 from "fs/promises";
|
|
1224
|
+
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1225
|
+
let rel = sourceFile.replace(/\\/g, "/");
|
|
1226
|
+
if (rel.startsWith("src/")) {
|
|
1227
|
+
rel = rel.slice(4);
|
|
1228
|
+
}
|
|
1229
|
+
const idx = rel.lastIndexOf("/");
|
|
1230
|
+
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1231
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
1232
|
+
}
|
|
1233
|
+
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
1234
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
1235
|
+
if (rel.startsWith("src/")) {
|
|
1236
|
+
rel = rel.slice(4);
|
|
1237
|
+
} else if (rel.startsWith(`${dist}/`)) {
|
|
1238
|
+
rel = rel.slice(dist.length + 1);
|
|
1239
|
+
}
|
|
1240
|
+
const idx = rel.lastIndexOf("/");
|
|
1241
|
+
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1242
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
1243
|
+
}
|
|
1244
|
+
function getHelpersImportPath(relDir) {
|
|
1245
|
+
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
1246
|
+
const depth = relDir.split("/").filter(Boolean).length;
|
|
1247
|
+
return `${"../".repeat(depth)}${HELPERS_FILENAME}`;
|
|
1248
|
+
}
|
|
1249
|
+
function generateSchemaFileSource(sources, allTypes, helpersImportPath) {
|
|
1250
|
+
const resolveType = (name) => allTypes.get(name)?.runtimeType;
|
|
1251
|
+
const lines = ["import { z } from 'zod';"];
|
|
1252
|
+
const schemaBlocks = [];
|
|
1253
|
+
for (const source of sources) {
|
|
1254
|
+
const { schemaName, typeInfo } = source;
|
|
1255
|
+
if (!typeInfo) {
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);
|
|
1259
|
+
const block = [`// ${schemaName}`];
|
|
1260
|
+
const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(
|
|
1261
|
+
/^import \{ z \} from 'zod';\s*\n\s*\n/,
|
|
1262
|
+
""
|
|
1263
|
+
);
|
|
1264
|
+
block.push(schemaCode);
|
|
1265
|
+
block.push("");
|
|
1266
|
+
schemaBlocks.push(block.join("\n"));
|
|
1267
|
+
}
|
|
1268
|
+
const allSchemaCode = schemaBlocks.join("\n");
|
|
1269
|
+
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
1270
|
+
lines.push(
|
|
1271
|
+
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
lines.push("");
|
|
1275
|
+
lines.push(...schemaBlocks);
|
|
1276
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
1277
|
+
}
|
|
1278
|
+
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
1279
|
+
if (routes.length === 0) return;
|
|
1280
|
+
const { sources, allTypesByFile } = collectRouteSchemaSources(routes, rootDir);
|
|
1281
|
+
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
1282
|
+
for (const source of sources) {
|
|
1283
|
+
let list = sourcesByFile.get(source.filePath);
|
|
1284
|
+
if (!list) {
|
|
1285
|
+
list = [];
|
|
1286
|
+
sourcesByFile.set(source.filePath, list);
|
|
1287
|
+
}
|
|
1288
|
+
list.push(source);
|
|
1289
|
+
}
|
|
1290
|
+
const fileEntries = [];
|
|
1291
|
+
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1292
|
+
const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1293
|
+
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1294
|
+
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
1295
|
+
let relForDir = relFile;
|
|
1296
|
+
if (relForDir.startsWith("src/")) {
|
|
1297
|
+
relForDir = relForDir.slice(4);
|
|
1298
|
+
}
|
|
1299
|
+
const dirIdx = relForDir.lastIndexOf("/");
|
|
1300
|
+
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
1301
|
+
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
1302
|
+
const source = generateSchemaFileSource(fileSources, allTypes, helpersImportPath);
|
|
1303
|
+
fileEntries.push({ outputPath, source });
|
|
1304
|
+
}
|
|
1305
|
+
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1306
|
+
if (usesCoerceHelpers(allSourceCode)) {
|
|
1307
|
+
const helpersPath = path5.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1308
|
+
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
1309
|
+
}
|
|
1310
|
+
await Promise.all(
|
|
1311
|
+
fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source))
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
async function writeSchemaFile(outputPath, source) {
|
|
1315
|
+
await fs4.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
1316
|
+
await fs4.writeFile(outputPath, source, "utf-8");
|
|
1317
|
+
}
|
|
1318
|
+
var init_generateSchemaFiles = __esm({
|
|
1319
|
+
"src/cli/generateSchemaFiles.ts"() {
|
|
1320
|
+
"use strict";
|
|
1321
|
+
init_collectRouteSchemaSources();
|
|
1322
|
+
init_generateZodSchema();
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
// src/index.ts
|
|
1327
|
+
init_createProgram();
|
|
1328
|
+
init_extractHandlerTypes();
|
|
1329
|
+
init_resolveTypeNode();
|
|
1330
|
+
init_inputType();
|
|
1331
|
+
init_collectRouteSchemaSources();
|
|
1332
|
+
|
|
1333
|
+
// src/middleware/cors.ts
|
|
1334
|
+
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
1335
|
+
function cors(options = {}) {
|
|
1336
|
+
const {
|
|
1337
|
+
origin = true,
|
|
1338
|
+
methods = DEFAULT_METHODS,
|
|
1339
|
+
allowedHeaders,
|
|
1340
|
+
exposeHeaders,
|
|
1341
|
+
credentials = false,
|
|
1342
|
+
maxAge
|
|
1343
|
+
} = options;
|
|
1344
|
+
return async (ctx, next) => {
|
|
1345
|
+
const reqOrigin = ctx.headers.get("origin");
|
|
1346
|
+
if (!reqOrigin) {
|
|
1347
|
+
await next();
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
let allowOrigin = null;
|
|
1351
|
+
if (origin === true) {
|
|
1352
|
+
allowOrigin = reqOrigin;
|
|
1353
|
+
} else if (typeof origin === "string") {
|
|
1354
|
+
allowOrigin = reqOrigin === origin ? origin : null;
|
|
1355
|
+
} else if (Array.isArray(origin)) {
|
|
1356
|
+
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
1357
|
+
}
|
|
1358
|
+
if (!allowOrigin) {
|
|
1359
|
+
await next();
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
1363
|
+
if (origin === true || Array.isArray(origin)) {
|
|
1364
|
+
const existingVary = ctx.headers.get("vary");
|
|
1365
|
+
if (existingVary) {
|
|
1366
|
+
if (!existingVary.toLowerCase().includes("origin")) {
|
|
1367
|
+
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
1368
|
+
}
|
|
1369
|
+
} else {
|
|
1370
|
+
ctx.setHeader("Vary", "Origin");
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
1374
|
+
if (allowedHeaders) {
|
|
1375
|
+
ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
1376
|
+
} else {
|
|
1377
|
+
const requestHeaders = ctx.headers.get("access-control-request-headers");
|
|
1378
|
+
if (requestHeaders) {
|
|
1379
|
+
ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
1383
|
+
ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
1384
|
+
}
|
|
1385
|
+
if (credentials) {
|
|
1386
|
+
ctx.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1387
|
+
}
|
|
1388
|
+
if (maxAge !== void 0) {
|
|
1389
|
+
ctx.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
1390
|
+
}
|
|
1391
|
+
if (ctx.method === "OPTIONS") {
|
|
1392
|
+
return new Response(null, { status: 204 });
|
|
1393
|
+
}
|
|
1394
|
+
await next();
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/middleware/logger.ts
|
|
1399
|
+
function logger(options = {}) {
|
|
1400
|
+
return async (ctx, next) => {
|
|
1401
|
+
const log = options.log ?? console.log;
|
|
1402
|
+
const start = Date.now();
|
|
1403
|
+
try {
|
|
1404
|
+
const response = await next();
|
|
1405
|
+
const duration = Date.now() - start;
|
|
1406
|
+
const entry = {
|
|
1407
|
+
method: ctx.method,
|
|
1408
|
+
path: ctx.path,
|
|
1409
|
+
status: response.status,
|
|
1410
|
+
durationMs: duration
|
|
1411
|
+
};
|
|
1412
|
+
log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);
|
|
1413
|
+
return response;
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
const duration = Date.now() - start;
|
|
1416
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1417
|
+
const status = err?.statusCode ?? 500;
|
|
1418
|
+
const entry = {
|
|
1419
|
+
method: ctx.method,
|
|
1420
|
+
path: ctx.path,
|
|
1421
|
+
status,
|
|
1422
|
+
durationMs: duration,
|
|
1423
|
+
error: message
|
|
1424
|
+
};
|
|
1425
|
+
log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
|
|
1426
|
+
throw err;
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/middleware/helmet.ts
|
|
1432
|
+
var DEFAULTS = {
|
|
1433
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
1434
|
+
xFrameOptions: "SAMEORIGIN",
|
|
1435
|
+
xContentTypeOptions: true,
|
|
1436
|
+
referrerPolicy: "no-referrer",
|
|
1437
|
+
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
|
1438
|
+
xDnsPrefetchControl: true,
|
|
1439
|
+
xDownloadOptions: true,
|
|
1440
|
+
xPermittedCrossDomainPolicies: "none",
|
|
1441
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
1442
|
+
crossOriginResourcePolicy: "same-origin",
|
|
1443
|
+
crossOriginEmbedderPolicy: false,
|
|
1444
|
+
originAgentCluster: true,
|
|
1445
|
+
xPoweredBy: true
|
|
1446
|
+
};
|
|
1447
|
+
function helmet(options = {}) {
|
|
1448
|
+
const opts = { ...DEFAULTS, ...options };
|
|
1449
|
+
return async (ctx, next) => {
|
|
1450
|
+
if (opts.contentSecurityPolicy !== false) {
|
|
1451
|
+
ctx.setHeader("Content-Security-Policy", opts.contentSecurityPolicy);
|
|
1452
|
+
}
|
|
1453
|
+
if (opts.xFrameOptions !== false) {
|
|
1454
|
+
ctx.setHeader("X-Frame-Options", opts.xFrameOptions);
|
|
1455
|
+
}
|
|
1456
|
+
if (opts.xContentTypeOptions) {
|
|
1457
|
+
ctx.setHeader("X-Content-Type-Options", "nosniff");
|
|
1458
|
+
}
|
|
1459
|
+
if (opts.referrerPolicy !== false) {
|
|
1460
|
+
ctx.setHeader("Referrer-Policy", opts.referrerPolicy);
|
|
1461
|
+
}
|
|
1462
|
+
if (opts.strictTransportSecurity !== false) {
|
|
1463
|
+
ctx.setHeader("Strict-Transport-Security", opts.strictTransportSecurity);
|
|
1464
|
+
}
|
|
1465
|
+
if (opts.xDnsPrefetchControl) {
|
|
1466
|
+
ctx.setHeader("X-DNS-Prefetch-Control", "off");
|
|
1467
|
+
}
|
|
1468
|
+
if (opts.xDownloadOptions) {
|
|
1469
|
+
ctx.setHeader("X-Download-Options", "noopen");
|
|
1470
|
+
}
|
|
1471
|
+
if (opts.xPermittedCrossDomainPolicies !== false) {
|
|
1472
|
+
ctx.setHeader("X-Permitted-Cross-Domain-Policies", opts.xPermittedCrossDomainPolicies);
|
|
1473
|
+
}
|
|
1474
|
+
if (opts.crossOriginOpenerPolicy !== false) {
|
|
1475
|
+
ctx.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
|
|
1476
|
+
}
|
|
1477
|
+
if (opts.crossOriginResourcePolicy !== false) {
|
|
1478
|
+
ctx.setHeader("Cross-Origin-Resource-Policy", opts.crossOriginResourcePolicy);
|
|
1479
|
+
}
|
|
1480
|
+
if (opts.crossOriginEmbedderPolicy !== false) {
|
|
1481
|
+
ctx.setHeader("Cross-Origin-Embedder-Policy", opts.crossOriginEmbedderPolicy);
|
|
1482
|
+
}
|
|
1483
|
+
if (opts.originAgentCluster) {
|
|
1484
|
+
ctx.setHeader("Origin-Agent-Cluster", "?1");
|
|
1485
|
+
}
|
|
1486
|
+
if (opts.xPoweredBy) {
|
|
1487
|
+
ctx.setHeader("X-Powered-By", "faapi");
|
|
1488
|
+
}
|
|
1489
|
+
return await next();
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/config/loadConfig.ts
|
|
1494
|
+
import path2 from "path";
|
|
1495
|
+
import fs from "fs";
|
|
1496
|
+
|
|
1497
|
+
// src/utils/importWithCacheBust.ts
|
|
1498
|
+
import { pathToFileURL } from "url";
|
|
1499
|
+
var loadTs;
|
|
1500
|
+
function setLoadTimestamp(ts7) {
|
|
1501
|
+
loadTs = ts7;
|
|
1502
|
+
}
|
|
1503
|
+
function getVitestImportActual() {
|
|
1504
|
+
const vi = globalThis.vi;
|
|
1505
|
+
if (typeof vi?.importActual !== "function") return void 0;
|
|
1506
|
+
return vi.importActual.bind(vi);
|
|
1507
|
+
}
|
|
1508
|
+
async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
1509
|
+
const importActual = getVitestImportActual();
|
|
1510
|
+
if (importActual) {
|
|
1511
|
+
if (bustViteCache) {
|
|
1512
|
+
let url2 = pathToFileURL(filePath).href;
|
|
1513
|
+
url2 += `?t=${Date.now()}`;
|
|
1514
|
+
return await import(url2);
|
|
1515
|
+
}
|
|
1516
|
+
return await importActual(filePath);
|
|
1517
|
+
}
|
|
1518
|
+
let url = pathToFileURL(filePath).href;
|
|
1519
|
+
if (loadTs !== void 0) {
|
|
1520
|
+
url += `?t=${loadTs}`;
|
|
1521
|
+
}
|
|
1522
|
+
return await import(url);
|
|
1037
1523
|
}
|
|
1038
1524
|
|
|
1039
1525
|
// src/config/loadConfig.ts
|
|
@@ -1160,14 +1646,14 @@ var ValidationError = class extends FaapiError {
|
|
|
1160
1646
|
issues;
|
|
1161
1647
|
};
|
|
1162
1648
|
var RouteNotFoundError = class extends FaapiError {
|
|
1163
|
-
constructor(
|
|
1164
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
1649
|
+
constructor(path15) {
|
|
1650
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path15}`, 404);
|
|
1165
1651
|
this.name = "RouteNotFoundError";
|
|
1166
1652
|
}
|
|
1167
1653
|
};
|
|
1168
1654
|
var MethodNotAllowedError = class extends FaapiError {
|
|
1169
|
-
constructor(method,
|
|
1170
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
1655
|
+
constructor(method, path15, allowedMethods) {
|
|
1656
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path15}`, 405);
|
|
1171
1657
|
this.allowedMethods = allowedMethods;
|
|
1172
1658
|
this.name = "MethodNotAllowedError";
|
|
1173
1659
|
}
|
|
@@ -1497,6 +1983,9 @@ async function toResponse(value, meta) {
|
|
|
1497
1983
|
});
|
|
1498
1984
|
}
|
|
1499
1985
|
|
|
1986
|
+
// src/injection/injectParams.ts
|
|
1987
|
+
init_resolveInjection();
|
|
1988
|
+
|
|
1500
1989
|
// src/utils/queryToObject.ts
|
|
1501
1990
|
function queryToObject(params) {
|
|
1502
1991
|
const result = {};
|
|
@@ -1648,9 +2137,9 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
1648
2137
|
}
|
|
1649
2138
|
|
|
1650
2139
|
// src/testServer.ts
|
|
1651
|
-
import
|
|
2140
|
+
import path12 from "path";
|
|
1652
2141
|
import os from "os";
|
|
1653
|
-
import
|
|
2142
|
+
import fs11 from "fs/promises";
|
|
1654
2143
|
|
|
1655
2144
|
// src/router/scanRoutes.ts
|
|
1656
2145
|
import fg from "fast-glob";
|
|
@@ -1660,14 +2149,11 @@ import fs3 from "fs";
|
|
|
1660
2149
|
// src/router/constants.ts
|
|
1661
2150
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
1662
2151
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
1663
|
-
function isHttpMethod(value) {
|
|
1664
|
-
return HTTP_METHOD_SET.has(value);
|
|
1665
|
-
}
|
|
1666
2152
|
|
|
1667
2153
|
// src/utils/normalizePath.ts
|
|
1668
|
-
function normalizePath(
|
|
1669
|
-
if (!
|
|
1670
|
-
let result =
|
|
2154
|
+
function normalizePath(path15) {
|
|
2155
|
+
if (!path15) return "";
|
|
2156
|
+
let result = path15.replace(/\\/g, "/");
|
|
1671
2157
|
result = result.replace(/\/+/g, "/");
|
|
1672
2158
|
result = result.replace(/\/+$/, "");
|
|
1673
2159
|
if (result && !result.startsWith("/")) {
|
|
@@ -1757,50 +2243,11 @@ async function loadMiddlewaresFile(filePath) {
|
|
|
1757
2243
|
return { middlewares: [], injectors: {} };
|
|
1758
2244
|
}
|
|
1759
2245
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
var APP_DIR = "src";
|
|
1763
|
-
function toProdAbsPath(sourceAbsPath, rootDir, dist) {
|
|
1764
|
-
let rel = path4.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1765
|
-
if (rel.startsWith(`${APP_DIR}/`)) {
|
|
1766
|
-
rel = rel.slice(APP_DIR.length + 1);
|
|
1767
|
-
}
|
|
1768
|
-
const prodRel = `${dist}/${rel.replace(/\.ts$/, ".js")}`;
|
|
1769
|
-
return path4.resolve(rootDir, prodRel);
|
|
1770
|
-
}
|
|
1771
|
-
async function findMergedMiddlewares(routeFilePath, rootDir, dist) {
|
|
1772
|
-
const routeDir = path4.dirname(routeFilePath);
|
|
1773
|
-
const resolvedRoot = path4.resolve(rootDir);
|
|
1774
|
-
const mwPaths = [];
|
|
1775
|
-
let currentDir = path4.resolve(rootDir, routeDir);
|
|
1776
|
-
while (true) {
|
|
1777
|
-
if (dist) {
|
|
1778
|
-
const mwPath = path4.join(currentDir, "middlewares.js");
|
|
1779
|
-
const absMwPath = path4.resolve(rootDir, mwPath);
|
|
1780
|
-
const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, dist);
|
|
1781
|
-
if (fs3.existsSync(prodAbsMwPath)) {
|
|
1782
|
-
mwPaths.push(prodAbsMwPath);
|
|
1783
|
-
}
|
|
1784
|
-
} else {
|
|
1785
|
-
for (const ext of [".ts", ".js"]) {
|
|
1786
|
-
const mwPath = path4.join(currentDir, `middlewares${ext}`);
|
|
1787
|
-
const absMwPath = path4.resolve(rootDir, mwPath);
|
|
1788
|
-
if (fs3.existsSync(absMwPath)) {
|
|
1789
|
-
mwPaths.push(absMwPath);
|
|
1790
|
-
break;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
if (currentDir === resolvedRoot) break;
|
|
1795
|
-
const parentDir = path4.dirname(currentDir);
|
|
1796
|
-
if (parentDir === currentDir) break;
|
|
1797
|
-
currentDir = parentDir;
|
|
1798
|
-
}
|
|
1799
|
-
if (mwPaths.length === 0) return void 0;
|
|
1800
|
-
mwPaths.reverse();
|
|
2246
|
+
async function loadMergedMiddlewares(middlewarePaths) {
|
|
2247
|
+
if (middlewarePaths.length === 0) return void 0;
|
|
1801
2248
|
const mergedMiddlewares = [];
|
|
1802
2249
|
const mergedInjectors = {};
|
|
1803
|
-
for (const absMwPath of
|
|
2250
|
+
for (const absMwPath of middlewarePaths) {
|
|
1804
2251
|
let bundle = getCachedMiddlewares(absMwPath);
|
|
1805
2252
|
if (bundle === void 0) {
|
|
1806
2253
|
bundle = await loadMiddlewaresFile(absMwPath);
|
|
@@ -1816,31 +2263,63 @@ async function findMergedMiddlewares(routeFilePath, rootDir, dist) {
|
|
|
1816
2263
|
}
|
|
1817
2264
|
return { middlewares: mergedMiddlewares, injectors: mergedInjectors };
|
|
1818
2265
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
2266
|
+
|
|
2267
|
+
// src/router/scanRoutes.ts
|
|
2268
|
+
var HTTP_OR_WS_EXPORT_RE = new RegExp(
|
|
2269
|
+
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|WS)\b`,
|
|
2270
|
+
"g"
|
|
2271
|
+
);
|
|
2272
|
+
function extractExportsFromSource(source) {
|
|
2273
|
+
const names = /* @__PURE__ */ new Set();
|
|
2274
|
+
let match;
|
|
2275
|
+
HTTP_OR_WS_EXPORT_RE.lastIndex = 0;
|
|
2276
|
+
while ((match = HTTP_OR_WS_EXPORT_RE.exec(source)) !== null) {
|
|
2277
|
+
names.add(match[1]);
|
|
2278
|
+
}
|
|
2279
|
+
return names;
|
|
2280
|
+
}
|
|
2281
|
+
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
2282
|
+
const routeDir = path4.dirname(routeFilePath);
|
|
2283
|
+
const resolvedRoot = path4.resolve(rootDir);
|
|
2284
|
+
const paths = [];
|
|
2285
|
+
let currentDir = path4.resolve(rootDir, routeDir);
|
|
2286
|
+
while (true) {
|
|
2287
|
+
if (dist) {
|
|
2288
|
+
const mwTsPath = path4.join(currentDir, "middlewares.ts");
|
|
2289
|
+
const mwJsPath = path4.join(currentDir, "middlewares.js");
|
|
2290
|
+
const absTsPath = path4.resolve(rootDir, mwTsPath);
|
|
2291
|
+
const absJsPath = path4.resolve(rootDir, mwJsPath);
|
|
2292
|
+
const absMwPath = fs3.existsSync(absTsPath) ? absTsPath : fs3.existsSync(absJsPath) ? absJsPath : null;
|
|
2293
|
+
if (absMwPath) {
|
|
2294
|
+
const relMwPath = path4.relative(rootDir, absMwPath);
|
|
2295
|
+
const prodAbsPath = path4.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
2296
|
+
paths.push(prodAbsPath);
|
|
2297
|
+
}
|
|
2298
|
+
} else {
|
|
2299
|
+
for (const ext of [".ts", ".js"]) {
|
|
2300
|
+
const mwPath = path4.join(currentDir, `middlewares${ext}`);
|
|
2301
|
+
const absMwPath = path4.resolve(rootDir, mwPath);
|
|
2302
|
+
if (fs3.existsSync(absMwPath)) {
|
|
2303
|
+
paths.push(absMwPath);
|
|
2304
|
+
break;
|
|
2305
|
+
}
|
|
1826
2306
|
}
|
|
1827
2307
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
return [];
|
|
2308
|
+
if (currentDir === resolvedRoot) break;
|
|
2309
|
+
const parentDir = path4.dirname(currentDir);
|
|
2310
|
+
if (parentDir === currentDir) break;
|
|
2311
|
+
currentDir = parentDir;
|
|
1833
2312
|
}
|
|
2313
|
+
paths.reverse();
|
|
2314
|
+
return paths;
|
|
1834
2315
|
}
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
} catch (err) {
|
|
1840
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1841
|
-
console.warn(`[faapi] \u52A0\u8F7D\u8DEF\u7531\u6587\u4EF6\u5931\u8D25\uFF08WS \u68C0\u6D4B\uFF09${absPath}: ${reason}`);
|
|
1842
|
-
return false;
|
|
2316
|
+
function toProdFilePath(filePath, dist) {
|
|
2317
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
2318
|
+
if (rel.startsWith("src/")) {
|
|
2319
|
+
rel = rel.slice(4);
|
|
1843
2320
|
}
|
|
2321
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
2322
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
1844
2323
|
}
|
|
1845
2324
|
async function scanRoutes(rootDir, patterns, dist) {
|
|
1846
2325
|
const files = await fg(patterns, {
|
|
@@ -1855,13 +2334,21 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
1855
2334
|
const fileName = normalizedFile.split("/").pop();
|
|
1856
2335
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
1857
2336
|
const absPath = path4.resolve(rootDir, normalizedFile);
|
|
1858
|
-
const importPath = dist ? toProdAbsPath(absPath, rootDir, dist) : absPath;
|
|
1859
2337
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
1860
2338
|
const paramNames = extractParamNames(urlPath);
|
|
1861
2339
|
const isDynamic = paramNames.length > 0;
|
|
1862
2340
|
const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
|
|
1863
|
-
|
|
1864
|
-
|
|
2341
|
+
let middlewarePaths;
|
|
2342
|
+
let middlewareBundle;
|
|
2343
|
+
if (dist) {
|
|
2344
|
+
middlewarePaths = collectMiddlewarePaths(normalizedFile, rootDir, dist);
|
|
2345
|
+
} else {
|
|
2346
|
+
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
2347
|
+
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
2348
|
+
}
|
|
2349
|
+
const source = await fs3.promises.readFile(absPath, "utf8").catch(() => "");
|
|
2350
|
+
const exportNames = extractExportsFromSource(source);
|
|
2351
|
+
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
1865
2352
|
for (const method of methods) {
|
|
1866
2353
|
routes.push({
|
|
1867
2354
|
method,
|
|
@@ -1870,18 +2357,19 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
1870
2357
|
paramNames,
|
|
1871
2358
|
isDynamic,
|
|
1872
2359
|
isCatchAll: isCatchAll || void 0,
|
|
2360
|
+
middlewarePaths,
|
|
1873
2361
|
middlewares: middlewareBundle?.middlewares,
|
|
1874
2362
|
injectors: middlewareBundle?.injectors
|
|
1875
2363
|
});
|
|
1876
2364
|
}
|
|
1877
|
-
|
|
1878
|
-
if (hasWs) {
|
|
2365
|
+
if (exportNames.has("WS")) {
|
|
1879
2366
|
wsRoutes.push({
|
|
1880
2367
|
urlPath,
|
|
1881
2368
|
filePath: normalizedFile,
|
|
1882
2369
|
paramNames,
|
|
1883
2370
|
isDynamic,
|
|
1884
2371
|
isCatchAll: isCatchAll || void 0,
|
|
2372
|
+
middlewarePaths,
|
|
1885
2373
|
middlewares: middlewareBundle?.middlewares,
|
|
1886
2374
|
injectors: middlewareBundle?.injectors
|
|
1887
2375
|
});
|
|
@@ -1910,403 +2398,357 @@ function sortRoutes(routes) {
|
|
|
1910
2398
|
});
|
|
1911
2399
|
}
|
|
1912
2400
|
|
|
1913
|
-
// src/
|
|
1914
|
-
|
|
1915
|
-
import fs4 from "fs/promises";
|
|
2401
|
+
// src/testServer.ts
|
|
2402
|
+
init_generateSchemaFiles();
|
|
1916
2403
|
|
|
1917
|
-
// src/
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
collectNamedTypes(type.element, ctx);
|
|
1953
|
-
return;
|
|
1954
|
-
case "tuple":
|
|
1955
|
-
for (const el of type.elements) {
|
|
1956
|
-
collectNamedTypes(el.type, ctx);
|
|
1957
|
-
}
|
|
1958
|
-
return;
|
|
1959
|
-
case "object":
|
|
1960
|
-
for (const prop of type.properties) {
|
|
1961
|
-
collectNamedTypes(prop.type, ctx);
|
|
1962
|
-
}
|
|
1963
|
-
return;
|
|
1964
|
-
case "union":
|
|
1965
|
-
for (const member of type.members) {
|
|
1966
|
-
collectNamedTypes(member, ctx);
|
|
1967
|
-
}
|
|
1968
|
-
return;
|
|
1969
|
-
case "record":
|
|
1970
|
-
collectNamedTypes(type.key, ctx);
|
|
1971
|
-
collectNamedTypes(type.value, ctx);
|
|
1972
|
-
return;
|
|
1973
|
-
case "map":
|
|
1974
|
-
collectNamedTypes(type.key, ctx);
|
|
1975
|
-
collectNamedTypes(type.value, ctx);
|
|
1976
|
-
return;
|
|
1977
|
-
case "set":
|
|
1978
|
-
collectNamedTypes(type.element, ctx);
|
|
1979
|
-
return;
|
|
1980
|
-
case "ref": {
|
|
1981
|
-
if (ctx.namedTypes.has(type.name)) return;
|
|
1982
|
-
ctx.namedTypes.set(type.name, { kind: "any" });
|
|
1983
|
-
const resolved = ctx.resolveType(type.name);
|
|
1984
|
-
if (resolved) {
|
|
1985
|
-
ctx.namedTypes.set(type.name, resolved);
|
|
1986
|
-
collectNamedTypes(resolved, ctx);
|
|
1987
|
-
}
|
|
1988
|
-
return;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
function runtimeTypeToZodExpression(type, ctx, constraints) {
|
|
1993
|
-
const expr = baseExpression(type, ctx);
|
|
1994
|
-
const withConstraints = constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;
|
|
1995
|
-
if (ctx.coerce && (type.kind === "number" || type.kind === "boolean")) {
|
|
1996
|
-
return wrapCoercePreprocess(type.kind, withConstraints);
|
|
1997
|
-
}
|
|
1998
|
-
return withConstraints;
|
|
1999
|
-
}
|
|
2000
|
-
function applyConstraints(baseExpr, constraints, typeKind) {
|
|
2001
|
-
const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join("");
|
|
2002
|
-
return `${baseExpr}${suffix}`;
|
|
2003
|
-
}
|
|
2004
|
-
function constraintToZodChain(constraint, _typeKind) {
|
|
2005
|
-
switch (constraint.kind) {
|
|
2006
|
-
case "max":
|
|
2007
|
-
return `.max(${constraint.value})`;
|
|
2008
|
-
case "min":
|
|
2009
|
-
return `.min(${constraint.value})`;
|
|
2010
|
-
case "int":
|
|
2011
|
-
return ".int()";
|
|
2012
|
-
case "positive":
|
|
2013
|
-
return ".positive()";
|
|
2014
|
-
case "negative":
|
|
2015
|
-
return ".negative()";
|
|
2016
|
-
case "nonnegative":
|
|
2017
|
-
return ".nonnegative()";
|
|
2018
|
-
case "nonpositive":
|
|
2019
|
-
return ".nonpositive()";
|
|
2020
|
-
case "maxLength":
|
|
2021
|
-
return `.max(${constraint.value})`;
|
|
2022
|
-
case "minLength":
|
|
2023
|
-
return `.min(${constraint.value})`;
|
|
2024
|
-
case "length":
|
|
2025
|
-
return `.length(${constraint.value})`;
|
|
2026
|
-
case "regex": {
|
|
2027
|
-
const flags = constraint.flags ?? "";
|
|
2028
|
-
return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ""}))`;
|
|
2029
|
-
}
|
|
2030
|
-
case "email":
|
|
2031
|
-
return ".email()";
|
|
2032
|
-
case "url":
|
|
2033
|
-
return ".url()";
|
|
2034
|
-
case "uuid":
|
|
2035
|
-
return ".uuid()";
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
function baseExpression(type, ctx) {
|
|
2039
|
-
switch (type.kind) {
|
|
2040
|
-
case "string":
|
|
2041
|
-
return "z.string()";
|
|
2042
|
-
case "number":
|
|
2043
|
-
return "z.number()";
|
|
2044
|
-
case "boolean":
|
|
2045
|
-
return "z.boolean()";
|
|
2046
|
-
case "bigint":
|
|
2047
|
-
return "z.never()";
|
|
2048
|
-
case "null":
|
|
2049
|
-
return "z.null()";
|
|
2050
|
-
case "undefined":
|
|
2051
|
-
return "z.undefined()";
|
|
2052
|
-
case "any":
|
|
2053
|
-
case "unknown":
|
|
2054
|
-
return "z.unknown()";
|
|
2055
|
-
case "literal":
|
|
2056
|
-
return `z.literal(${JSON.stringify(type.value)})`;
|
|
2057
|
-
case "array":
|
|
2058
|
-
return `z.array(${runtimeTypeToZodExpression(type.element, ctx)})`;
|
|
2059
|
-
case "tuple":
|
|
2060
|
-
return generateTupleExpression(type.elements, ctx);
|
|
2061
|
-
case "object":
|
|
2062
|
-
return generateObjectExpression(type.properties, ctx);
|
|
2063
|
-
case "union":
|
|
2064
|
-
return generateUnionExpression(type.members, ctx);
|
|
2065
|
-
case "date":
|
|
2066
|
-
return 'z.preprocess((v) => (typeof v === "string" ? new Date(v) : v), z.date())';
|
|
2067
|
-
case "record":
|
|
2068
|
-
return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;
|
|
2069
|
-
case "map":
|
|
2070
|
-
return `z.preprocess(coerceMap, z.map(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)}))`;
|
|
2071
|
-
case "set":
|
|
2072
|
-
return `z.preprocess(coerceSet, z.set(${runtimeTypeToZodExpression(type.element, ctx)}))`;
|
|
2073
|
-
case "ref":
|
|
2074
|
-
if (type.name === ctx.entryTypeName) {
|
|
2075
|
-
return `${ctx.entryExportName}Schema`;
|
|
2076
|
-
}
|
|
2077
|
-
return `${type.name}Schema`;
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
var COERCE_NUMBER_HELPER = 'export const coerceNumber = (v) => typeof v === "string" && v.trim() !== "" && !isNaN(Number(v)) ? Number(v) : v;';
|
|
2081
|
-
var COERCE_BOOLEAN_HELPER = 'export const coerceBoolean = (v) => v === "true" || v === "1" ? true : v === "false" || v === "0" ? false : v;';
|
|
2082
|
-
var COERCE_MAP_HELPER = 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === "object" ? new Map(Object.entries(v)) : v);';
|
|
2083
|
-
var COERCE_SET_HELPER = "export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);";
|
|
2084
|
-
var HELPERS_FILENAME = "faapi-helpers.js";
|
|
2085
|
-
function generateHelpersFileSource() {
|
|
2086
|
-
return [
|
|
2087
|
-
"// faapi-helpers.js \u2014 faapi \u81EA\u52A8\u751F\u6210\u7684\u516C\u7528\u51FD\u6570\uFF08\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91\uFF09",
|
|
2088
|
-
COERCE_NUMBER_HELPER,
|
|
2089
|
-
COERCE_BOOLEAN_HELPER,
|
|
2090
|
-
COERCE_MAP_HELPER,
|
|
2091
|
-
COERCE_SET_HELPER,
|
|
2092
|
-
""
|
|
2093
|
-
].join("\n");
|
|
2094
|
-
}
|
|
2095
|
-
function usesCoerceHelpers(code) {
|
|
2096
|
-
return code.includes("coerceNumber") || code.includes("coerceBoolean") || code.includes("coerceMap") || code.includes("coerceSet");
|
|
2097
|
-
}
|
|
2098
|
-
function wrapCoercePreprocess(kind, inner) {
|
|
2099
|
-
if (kind === "number") {
|
|
2100
|
-
return `z.preprocess(coerceNumber, ${inner})`;
|
|
2101
|
-
}
|
|
2102
|
-
return `z.preprocess(coerceBoolean, ${inner})`;
|
|
2103
|
-
}
|
|
2104
|
-
function generateTupleExpression(elements, ctx) {
|
|
2105
|
-
const fixedExprs = [];
|
|
2106
|
-
const fixedOptional = [];
|
|
2107
|
-
let restExpression = "";
|
|
2108
|
-
let restStarted = false;
|
|
2109
|
-
for (const el of elements) {
|
|
2110
|
-
if (el.rest) {
|
|
2111
|
-
restExpression = runtimeTypeToZodExpression(el.type, ctx);
|
|
2112
|
-
restStarted = true;
|
|
2113
|
-
} else if (!restStarted) {
|
|
2114
|
-
fixedExprs.push(runtimeTypeToZodExpression(el.type, ctx));
|
|
2115
|
-
fixedOptional.push(el.optional);
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
if (restExpression) {
|
|
2119
|
-
return `z.tuple([${fixedExprs.join(", ")}]).rest(${restExpression})`;
|
|
2120
|
-
}
|
|
2121
|
-
const hasOptional = fixedOptional.some((o) => o);
|
|
2122
|
-
if (!hasOptional) {
|
|
2123
|
-
return `z.tuple([${fixedExprs.join(", ")}])`;
|
|
2124
|
-
}
|
|
2125
|
-
const variants = [];
|
|
2126
|
-
for (let len = fixedExprs.length; len >= 0; len--) {
|
|
2127
|
-
const removed = fixedOptional.slice(len);
|
|
2128
|
-
if (removed.length > 0 && removed.some((o) => !o)) {
|
|
2129
|
-
break;
|
|
2130
|
-
}
|
|
2131
|
-
const subset = fixedExprs.slice(0, len);
|
|
2132
|
-
variants.push(`z.tuple([${subset.join(", ")}])`);
|
|
2133
|
-
}
|
|
2134
|
-
variants.reverse();
|
|
2135
|
-
if (variants.length === 1) {
|
|
2136
|
-
return variants[0];
|
|
2137
|
-
}
|
|
2138
|
-
return `z.union([${variants.join(", ")}])`;
|
|
2139
|
-
}
|
|
2140
|
-
function generateObjectExpression(properties, ctx) {
|
|
2141
|
-
const fields = properties.map((prop) => {
|
|
2142
|
-
const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);
|
|
2143
|
-
const finalExpr = prop.optional ? `${expr}.optional()` : expr;
|
|
2144
|
-
return `${JSON.stringify(prop.name)}: ${finalExpr}`;
|
|
2145
|
-
});
|
|
2146
|
-
return `z.object({ ${fields.join(", ")} })`;
|
|
2147
|
-
}
|
|
2148
|
-
function generateUnionExpression(members, ctx) {
|
|
2149
|
-
const hasNull = members.some((m) => m.kind === "null");
|
|
2150
|
-
const nonNull = members.filter((m) => m.kind !== "null");
|
|
2151
|
-
if (hasNull && nonNull.length === 1) {
|
|
2152
|
-
return `${runtimeTypeToZodExpression(nonNull[0], ctx)}.nullable()`;
|
|
2153
|
-
}
|
|
2154
|
-
if (hasNull) {
|
|
2155
|
-
const unionInner2 = nonNull.map((m) => runtimeTypeToZodExpression(m, ctx)).join(", ");
|
|
2156
|
-
return `z.union([${unionInner2}]).nullable()`;
|
|
2404
|
+
// src/validator/validateInput.ts
|
|
2405
|
+
init_schemaName();
|
|
2406
|
+
|
|
2407
|
+
// src/cli/compileOnDemand.ts
|
|
2408
|
+
import path9 from "path";
|
|
2409
|
+
import fs8 from "fs";
|
|
2410
|
+
|
|
2411
|
+
// src/cli/compileDevRoutes.ts
|
|
2412
|
+
import path8 from "path";
|
|
2413
|
+
import fs7 from "fs";
|
|
2414
|
+
import fg2 from "fast-glob";
|
|
2415
|
+
|
|
2416
|
+
// src/cli/aliasPlugin.ts
|
|
2417
|
+
import path7 from "path";
|
|
2418
|
+
import fs6 from "fs";
|
|
2419
|
+
|
|
2420
|
+
// src/utils/resolveAlias.ts
|
|
2421
|
+
function resolveAlias(specifier, config) {
|
|
2422
|
+
const candidates = [];
|
|
2423
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
2424
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
2425
|
+
if (wildcardIndex === -1) {
|
|
2426
|
+
if (specifier === pattern) {
|
|
2427
|
+
candidates.push(...targets);
|
|
2428
|
+
}
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
2432
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
2433
|
+
if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
|
|
2434
|
+
const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
2435
|
+
for (const target of targets) {
|
|
2436
|
+
candidates.push(target.replace("*", captured));
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2157
2439
|
}
|
|
2158
|
-
|
|
2159
|
-
return `z.union([${unionInner}])`;
|
|
2440
|
+
return candidates;
|
|
2160
2441
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2442
|
+
|
|
2443
|
+
// src/utils/readTsconfig.ts
|
|
2444
|
+
import ts6 from "typescript";
|
|
2445
|
+
import path6 from "path";
|
|
2446
|
+
import fs5 from "fs";
|
|
2447
|
+
function readTsconfig(rootDir) {
|
|
2448
|
+
const tsconfigPath = path6.resolve(rootDir, "tsconfig.json");
|
|
2449
|
+
if (!fs5.existsSync(tsconfigPath)) return null;
|
|
2450
|
+
const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
|
|
2451
|
+
if (configFile.error || !configFile.config) return null;
|
|
2452
|
+
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
2453
|
+
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
2454
|
+
const rawPaths = parsed.options.paths;
|
|
2455
|
+
if (!rawPaths) return null;
|
|
2456
|
+
const paths = {};
|
|
2457
|
+
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
2458
|
+
paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
|
|
2459
|
+
}
|
|
2460
|
+
return { baseUrl, paths };
|
|
2168
2461
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2462
|
+
|
|
2463
|
+
// src/cli/aliasPlugin.ts
|
|
2464
|
+
function toProdExtension(filePath) {
|
|
2465
|
+
if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
|
|
2466
|
+
if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
|
|
2467
|
+
if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
|
|
2468
|
+
return filePath;
|
|
2469
|
+
}
|
|
2470
|
+
function toProdImportPath(sourceFile, importer) {
|
|
2471
|
+
const importerDir = path7.dirname(importer);
|
|
2472
|
+
let rel = path7.relative(importerDir, sourceFile);
|
|
2473
|
+
rel = rel.split(path7.sep).join("/");
|
|
2474
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2475
|
+
return toProdExtension(rel);
|
|
2476
|
+
}
|
|
2477
|
+
function toRealPath(p) {
|
|
2478
|
+
try {
|
|
2479
|
+
return fs6.realpathSync(p);
|
|
2480
|
+
} catch {
|
|
2481
|
+
return p;
|
|
2189
2482
|
}
|
|
2190
2483
|
}
|
|
2191
|
-
function
|
|
2192
|
-
const
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2484
|
+
function isInsideDir(filePath, dir) {
|
|
2485
|
+
const rel = path7.relative(dir, filePath);
|
|
2486
|
+
return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
2487
|
+
}
|
|
2488
|
+
var APP_DIR = "src";
|
|
2489
|
+
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
2490
|
+
const appDirAbs = toRealPath(path7.resolve(rootDir, APP_DIR));
|
|
2491
|
+
const sourceReal = toRealPath(sourceFile);
|
|
2492
|
+
let rel = path7.relative(appDirAbs, sourceReal);
|
|
2493
|
+
rel = rel.split(path7.sep).join("/");
|
|
2494
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2495
|
+
return toProdExtension(rel);
|
|
2496
|
+
}
|
|
2497
|
+
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
2498
|
+
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2499
|
+
var INDEX_EXTS = [
|
|
2500
|
+
"/index.ts",
|
|
2501
|
+
"/index.tsx",
|
|
2502
|
+
"/index.js",
|
|
2503
|
+
"/index.jsx",
|
|
2504
|
+
"/index.mjs",
|
|
2505
|
+
"/index.cjs"
|
|
2506
|
+
];
|
|
2507
|
+
function resolveRelativeSpecifier(importer, specifier) {
|
|
2508
|
+
const importerDir = path7.dirname(importer);
|
|
2509
|
+
const base = path7.resolve(importerDir, specifier);
|
|
2510
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2511
|
+
return fs6.existsSync(base) ? base : null;
|
|
2512
|
+
}
|
|
2513
|
+
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
2514
|
+
return fs6.existsSync(base) ? base : null;
|
|
2515
|
+
}
|
|
2516
|
+
for (const ext of SOURCE_EXTS) {
|
|
2517
|
+
const file = base + ext;
|
|
2518
|
+
if (fs6.existsSync(file)) return file;
|
|
2519
|
+
}
|
|
2520
|
+
for (const indexExt of INDEX_EXTS) {
|
|
2521
|
+
const file = base + indexExt;
|
|
2522
|
+
if (fs6.existsSync(file)) return file;
|
|
2212
2523
|
}
|
|
2213
|
-
return
|
|
2524
|
+
return null;
|
|
2525
|
+
}
|
|
2526
|
+
function createAliasPlugin(config, options) {
|
|
2527
|
+
const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
2528
|
+
const appDirAbs = options?.rootDir ? toRealPath(path7.resolve(options.rootDir, APP_DIR)) : null;
|
|
2529
|
+
return {
|
|
2530
|
+
name: "faapi-alias",
|
|
2531
|
+
setup(build) {
|
|
2532
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
2533
|
+
let source;
|
|
2534
|
+
try {
|
|
2535
|
+
source = fs6.readFileSync(args.path, "utf8");
|
|
2536
|
+
} catch {
|
|
2537
|
+
return void 0;
|
|
2538
|
+
}
|
|
2539
|
+
const importer = args.path;
|
|
2540
|
+
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
2541
|
+
let modified = false;
|
|
2542
|
+
const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
|
|
2543
|
+
if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
|
|
2544
|
+
return full;
|
|
2545
|
+
}
|
|
2546
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2547
|
+
const resolved = resolveRelativeSpecifier(importer, specifier);
|
|
2548
|
+
if (resolved) {
|
|
2549
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2550
|
+
return full;
|
|
2551
|
+
}
|
|
2552
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
2553
|
+
modified = true;
|
|
2554
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2555
|
+
resolved,
|
|
2556
|
+
options.rootDir
|
|
2557
|
+
)}${quote}`;
|
|
2558
|
+
}
|
|
2559
|
+
modified = true;
|
|
2560
|
+
return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
|
|
2561
|
+
}
|
|
2562
|
+
return full;
|
|
2563
|
+
}
|
|
2564
|
+
const candidates = resolveAlias(specifier, config);
|
|
2565
|
+
for (const candidate of candidates) {
|
|
2566
|
+
for (const ext of SOURCE_EXTS) {
|
|
2567
|
+
const file = candidate + ext;
|
|
2568
|
+
if (fs6.existsSync(file)) {
|
|
2569
|
+
modified = true;
|
|
2570
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2571
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2572
|
+
file,
|
|
2573
|
+
options.rootDir
|
|
2574
|
+
)}${quote}`;
|
|
2575
|
+
}
|
|
2576
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
for (const indexExt of INDEX_EXTS) {
|
|
2580
|
+
const file = candidate + indexExt;
|
|
2581
|
+
if (fs6.existsSync(file)) {
|
|
2582
|
+
modified = true;
|
|
2583
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2584
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2585
|
+
file,
|
|
2586
|
+
options.rootDir
|
|
2587
|
+
)}${quote}`;
|
|
2588
|
+
}
|
|
2589
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
return full;
|
|
2594
|
+
});
|
|
2595
|
+
if (!modified) return void 0;
|
|
2596
|
+
return { contents: newSource, loader: "default" };
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
function buildAliasPlugins(rootDir) {
|
|
2602
|
+
const tsconfig = readTsconfig(rootDir);
|
|
2603
|
+
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
2214
2604
|
}
|
|
2215
2605
|
|
|
2216
|
-
// src/cli/
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2606
|
+
// src/cli/compileDevRoutes.ts
|
|
2607
|
+
var APP_DIR2 = "src";
|
|
2608
|
+
async function compileDevRoutes(options) {
|
|
2609
|
+
const { rootDir, dist, files, logLevel = "silent" } = options;
|
|
2610
|
+
const entryPoints = files ?? await fg2([`${APP_DIR2}/**/*.ts`], {
|
|
2611
|
+
cwd: rootDir,
|
|
2612
|
+
onlyFiles: true,
|
|
2613
|
+
absolute: true,
|
|
2614
|
+
ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
|
|
2615
|
+
});
|
|
2616
|
+
if (entryPoints.length === 0) {
|
|
2617
|
+
return { compiledFiles: [] };
|
|
2618
|
+
}
|
|
2619
|
+
const absDist = path8.resolve(rootDir, dist);
|
|
2620
|
+
await fs7.promises.mkdir(absDist, { recursive: true });
|
|
2621
|
+
const plugins = buildAliasPlugins(rootDir);
|
|
2622
|
+
const esbuild = await import("esbuild");
|
|
2623
|
+
const outbase = path8.resolve(rootDir, APP_DIR2);
|
|
2624
|
+
const result = await esbuild.build({
|
|
2625
|
+
entryPoints,
|
|
2626
|
+
outdir: absDist,
|
|
2627
|
+
outbase,
|
|
2628
|
+
bundle: false,
|
|
2629
|
+
platform: "node",
|
|
2630
|
+
format: "esm",
|
|
2631
|
+
sourcemap: true,
|
|
2632
|
+
packages: "external",
|
|
2633
|
+
plugins,
|
|
2634
|
+
logLevel,
|
|
2635
|
+
write: false
|
|
2636
|
+
});
|
|
2637
|
+
if (result.outputFiles) {
|
|
2638
|
+
await Promise.all(
|
|
2639
|
+
result.outputFiles.map(async (file) => {
|
|
2640
|
+
await fs7.promises.mkdir(path8.dirname(file.path), { recursive: true });
|
|
2641
|
+
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2642
|
+
await fs7.promises.writeFile(tmp, file.contents);
|
|
2643
|
+
await fs7.promises.rename(tmp, file.path);
|
|
2644
|
+
})
|
|
2645
|
+
);
|
|
2221
2646
|
}
|
|
2222
|
-
|
|
2223
|
-
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2224
|
-
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
2647
|
+
return { compiledFiles: entryPoints };
|
|
2225
2648
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2649
|
+
|
|
2650
|
+
// src/cli/compileOnDemand.ts
|
|
2651
|
+
init_generateSchemaFiles();
|
|
2652
|
+
init_generateSchemaFiles();
|
|
2653
|
+
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
2654
|
+
try {
|
|
2655
|
+
const srcStat = fs8.statSync(sourceAbsPath);
|
|
2656
|
+
const prodStat = fs8.statSync(productAbsPath);
|
|
2657
|
+
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
2658
|
+
} catch {
|
|
2659
|
+
return false;
|
|
2232
2660
|
}
|
|
2233
|
-
const idx = rel.lastIndexOf("/");
|
|
2234
|
-
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2235
|
-
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
2236
2661
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
return `${"../".repeat(depth)}${HELPERS_FILENAME}`;
|
|
2662
|
+
var compiledFiles = /* @__PURE__ */ new Set();
|
|
2663
|
+
function clearCompiledFiles() {
|
|
2664
|
+
compiledFiles.clear();
|
|
2241
2665
|
}
|
|
2242
|
-
function
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
const schemaBlocks = [];
|
|
2246
|
-
for (const source of sources) {
|
|
2247
|
-
const { schemaName, typeInfo } = source;
|
|
2248
|
-
if (!typeInfo) {
|
|
2249
|
-
continue;
|
|
2250
|
-
}
|
|
2251
|
-
const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);
|
|
2252
|
-
const block = [`// ${schemaName}`];
|
|
2253
|
-
const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(
|
|
2254
|
-
/^import \{ z \} from 'zod';\s*\n\s*\n/,
|
|
2255
|
-
""
|
|
2256
|
-
);
|
|
2257
|
-
block.push(schemaCode);
|
|
2258
|
-
block.push("");
|
|
2259
|
-
schemaBlocks.push(block.join("\n"));
|
|
2666
|
+
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
2667
|
+
if (compiledFiles.has(sourceAbsPath)) {
|
|
2668
|
+
return false;
|
|
2260
2669
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
lines.push(
|
|
2264
|
-
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
2265
|
-
);
|
|
2670
|
+
if (!fs8.existsSync(sourceAbsPath)) {
|
|
2671
|
+
return false;
|
|
2266
2672
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
2272
|
-
if (routes.length === 0) return;
|
|
2273
|
-
const { sources, allTypesByFile } = collectRouteSchemaSources(routes, rootDir);
|
|
2274
|
-
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
2275
|
-
for (const source of sources) {
|
|
2276
|
-
let list = sourcesByFile.get(source.filePath);
|
|
2277
|
-
if (!list) {
|
|
2278
|
-
list = [];
|
|
2279
|
-
sourcesByFile.set(source.filePath, list);
|
|
2280
|
-
}
|
|
2281
|
-
list.push(source);
|
|
2673
|
+
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
2674
|
+
if (productPath && isProductFresh(sourceAbsPath, productPath)) {
|
|
2675
|
+
compiledFiles.add(sourceAbsPath);
|
|
2676
|
+
return false;
|
|
2282
2677
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2678
|
+
await compileDevRoutes({
|
|
2679
|
+
rootDir,
|
|
2680
|
+
dist,
|
|
2681
|
+
files: [sourceAbsPath],
|
|
2682
|
+
logLevel: "silent"
|
|
2683
|
+
});
|
|
2684
|
+
compiledFiles.add(sourceAbsPath);
|
|
2685
|
+
return true;
|
|
2686
|
+
}
|
|
2687
|
+
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2688
|
+
const rel = path9.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2689
|
+
if (!rel.startsWith("src/")) return null;
|
|
2690
|
+
const relWithoutSrc = rel.slice(4);
|
|
2691
|
+
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2692
|
+
return path9.resolve(rootDir, dist, jsRel);
|
|
2693
|
+
}
|
|
2694
|
+
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
2695
|
+
function clearGeneratedSchemas() {
|
|
2696
|
+
generatedSchemas.clear();
|
|
2697
|
+
}
|
|
2698
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
2699
|
+
if (generatedSchemas.has(schemaPath)) {
|
|
2700
|
+
return false;
|
|
2701
|
+
}
|
|
2702
|
+
const prodAbsPath = path9.resolve(rootDir, routeFilePath);
|
|
2703
|
+
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
2704
|
+
if (!fs8.existsSync(sourceAbsPath)) {
|
|
2705
|
+
return false;
|
|
2706
|
+
}
|
|
2707
|
+
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
2708
|
+
generatedSchemas.add(schemaPath);
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
2712
|
+
if (fileRoutes.length === 0) {
|
|
2713
|
+
return false;
|
|
2714
|
+
}
|
|
2715
|
+
const sourceRelPath = path9.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2716
|
+
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2717
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
2718
|
+
generatedSchemas.add(schemaPath);
|
|
2719
|
+
return true;
|
|
2720
|
+
}
|
|
2721
|
+
async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
2722
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
2723
|
+
for (const route of routes) {
|
|
2724
|
+
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
2725
|
+
if (deleted.has(schemaPath)) continue;
|
|
2726
|
+
deleted.add(schemaPath);
|
|
2727
|
+
try {
|
|
2728
|
+
await fs8.promises.unlink(schemaPath);
|
|
2729
|
+
} catch {
|
|
2291
2730
|
}
|
|
2292
|
-
const dirIdx = relForDir.lastIndexOf("/");
|
|
2293
|
-
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
2294
|
-
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
2295
|
-
const source = generateSchemaFileSource(fileSources, allTypes, helpersImportPath);
|
|
2296
|
-
fileEntries.push({ outputPath, source });
|
|
2297
2731
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2732
|
+
}
|
|
2733
|
+
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2734
|
+
const rel = path9.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
2735
|
+
let relWithoutDist = rel;
|
|
2736
|
+
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
2737
|
+
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
2302
2738
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
);
|
|
2739
|
+
const srcRel = `src/${relWithoutDist}`;
|
|
2740
|
+
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2741
|
+
const tsAbs = path9.resolve(rootDir, tsRel);
|
|
2742
|
+
if (fs8.existsSync(tsAbs)) return tsAbs;
|
|
2743
|
+
return path9.resolve(rootDir, srcRel);
|
|
2306
2744
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2745
|
+
var devOnDemandEnabled = false;
|
|
2746
|
+
function isDevOnDemandEnabled() {
|
|
2747
|
+
return devOnDemandEnabled;
|
|
2748
|
+
}
|
|
2749
|
+
var devDistDir;
|
|
2750
|
+
function getDevDist() {
|
|
2751
|
+
return devDistDir;
|
|
2310
2752
|
}
|
|
2311
2753
|
|
|
2312
2754
|
// src/validator/validateInput.ts
|
|
@@ -2317,7 +2759,7 @@ function invalidateSchemaCache() {
|
|
|
2317
2759
|
async function loadSchemaModule(schemaPath) {
|
|
2318
2760
|
let mod = moduleCache.get(schemaPath);
|
|
2319
2761
|
if (!mod) {
|
|
2320
|
-
mod = await importWithCacheBust(schemaPath);
|
|
2762
|
+
mod = await importWithCacheBust(schemaPath, isDevOnDemandEnabled());
|
|
2321
2763
|
moduleCache.set(schemaPath, mod);
|
|
2322
2764
|
}
|
|
2323
2765
|
return mod;
|
|
@@ -2353,9 +2795,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
2353
2795
|
function mapZodIssues(error) {
|
|
2354
2796
|
return error.issues.map((issue) => {
|
|
2355
2797
|
const code = mapZodCode(issue.code, issue.message);
|
|
2356
|
-
const
|
|
2798
|
+
const path15 = issue.path.map(String).join(".") || "";
|
|
2357
2799
|
return {
|
|
2358
|
-
path:
|
|
2800
|
+
path: path15,
|
|
2359
2801
|
code,
|
|
2360
2802
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
2361
2803
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -2403,45 +2845,45 @@ import {
|
|
|
2403
2845
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
2404
2846
|
import { readFileSync } from "fs";
|
|
2405
2847
|
import { Readable as Readable2 } from "stream";
|
|
2406
|
-
import
|
|
2848
|
+
import path11 from "path";
|
|
2407
2849
|
|
|
2408
2850
|
// src/router/matchRoute.ts
|
|
2409
|
-
function matchRoute(routes, method,
|
|
2851
|
+
function matchRoute(routes, method, path15) {
|
|
2410
2852
|
for (const route of routes) {
|
|
2411
2853
|
if (route.method !== method) {
|
|
2412
2854
|
continue;
|
|
2413
2855
|
}
|
|
2414
2856
|
if (!route.isDynamic) {
|
|
2415
|
-
if (route.urlPath ===
|
|
2857
|
+
if (route.urlPath === path15) {
|
|
2416
2858
|
return { route, params: {} };
|
|
2417
2859
|
}
|
|
2418
2860
|
continue;
|
|
2419
2861
|
}
|
|
2420
|
-
const params = matchDynamicPath(route.urlPath,
|
|
2862
|
+
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2421
2863
|
if (params !== null) {
|
|
2422
2864
|
return { route, params };
|
|
2423
2865
|
}
|
|
2424
2866
|
}
|
|
2425
2867
|
return null;
|
|
2426
2868
|
}
|
|
2427
|
-
function matchWsRoute(wsRoutes,
|
|
2869
|
+
function matchWsRoute(wsRoutes, path15) {
|
|
2428
2870
|
for (const route of wsRoutes) {
|
|
2429
2871
|
if (!route.isDynamic) {
|
|
2430
|
-
if (route.urlPath ===
|
|
2872
|
+
if (route.urlPath === path15) {
|
|
2431
2873
|
return { route, params: {} };
|
|
2432
2874
|
}
|
|
2433
2875
|
continue;
|
|
2434
2876
|
}
|
|
2435
|
-
const params = matchDynamicPath(route.urlPath,
|
|
2877
|
+
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2436
2878
|
if (params !== null) {
|
|
2437
2879
|
return { route, params };
|
|
2438
2880
|
}
|
|
2439
2881
|
}
|
|
2440
2882
|
return null;
|
|
2441
2883
|
}
|
|
2442
|
-
function matchDynamicPath(pattern,
|
|
2884
|
+
function matchDynamicPath(pattern, path15, paramNames, isCatchAll) {
|
|
2443
2885
|
const patternSegments = pattern.split("/").filter(Boolean);
|
|
2444
|
-
const pathSegments =
|
|
2886
|
+
const pathSegments = path15.split("/").filter(Boolean);
|
|
2445
2887
|
if (isCatchAll) {
|
|
2446
2888
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
2447
2889
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -2486,6 +2928,9 @@ function matchDynamicPath(pattern, path11, paramNames, isCatchAll) {
|
|
|
2486
2928
|
return params;
|
|
2487
2929
|
}
|
|
2488
2930
|
|
|
2931
|
+
// src/loader/loadRouteModule.ts
|
|
2932
|
+
import fs9 from "fs";
|
|
2933
|
+
|
|
2489
2934
|
// src/loader/resolveExports.ts
|
|
2490
2935
|
function resolveExport(module, exportName) {
|
|
2491
2936
|
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
@@ -2511,10 +2956,26 @@ function validateRouteModule(value, method, filePath) {
|
|
|
2511
2956
|
}
|
|
2512
2957
|
|
|
2513
2958
|
// src/loader/loadRouteModule.ts
|
|
2514
|
-
async function loadRouteModule(filePath, method) {
|
|
2959
|
+
async function loadRouteModule(filePath, method, rootDir) {
|
|
2960
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
2961
|
+
const dist = getDevDist();
|
|
2962
|
+
if (dist) {
|
|
2963
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2964
|
+
if (sourcePath && fs9.existsSync(sourcePath)) {
|
|
2965
|
+
try {
|
|
2966
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2967
|
+
} catch (compileErr) {
|
|
2968
|
+
const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
2969
|
+
throw new Error(`Failed to compile route module "${sourcePath}": ${reason}`, {
|
|
2970
|
+
cause: compileErr
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2515
2976
|
let module;
|
|
2516
2977
|
try {
|
|
2517
|
-
module = await importWithCacheBust(filePath);
|
|
2978
|
+
module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
2518
2979
|
} catch (err) {
|
|
2519
2980
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2520
2981
|
throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
|
|
@@ -2565,6 +3026,7 @@ async function parseMultipart(request) {
|
|
|
2565
3026
|
}
|
|
2566
3027
|
|
|
2567
3028
|
// src/runtime/resolveInput.ts
|
|
3029
|
+
init_inputType();
|
|
2568
3030
|
async function resolveInput(method, request) {
|
|
2569
3031
|
const inputType = getInputTypeForMethod(method);
|
|
2570
3032
|
if (inputType === "body") {
|
|
@@ -2628,6 +3090,9 @@ async function sendNodeResponse(response, res) {
|
|
|
2628
3090
|
res.end();
|
|
2629
3091
|
}
|
|
2630
3092
|
|
|
3093
|
+
// src/server/createServer.ts
|
|
3094
|
+
init_inputType();
|
|
3095
|
+
|
|
2631
3096
|
// src/utils/getClientIp.ts
|
|
2632
3097
|
function getClientIp(req) {
|
|
2633
3098
|
const xff = req.headers["x-forwarded-for"];
|
|
@@ -2646,8 +3111,9 @@ function getClientIp(req) {
|
|
|
2646
3111
|
}
|
|
2647
3112
|
|
|
2648
3113
|
// src/server/handleWsUpgrade.ts
|
|
3114
|
+
import fs10 from "fs";
|
|
2649
3115
|
import { WebSocketServer, WebSocket } from "ws";
|
|
2650
|
-
import
|
|
3116
|
+
import path10 from "path";
|
|
2651
3117
|
|
|
2652
3118
|
// src/errors/formatErrorResponse.ts
|
|
2653
3119
|
function formatErrorResponse(error) {
|
|
@@ -2744,8 +3210,17 @@ function getPathname(req) {
|
|
|
2744
3210
|
const idx = url.indexOf("?");
|
|
2745
3211
|
return idx >= 0 ? url.slice(0, idx) : url;
|
|
2746
3212
|
}
|
|
2747
|
-
async function loadWsHandler(filePath, ctx) {
|
|
2748
|
-
|
|
3213
|
+
async function loadWsHandler(filePath, ctx, rootDir) {
|
|
3214
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
3215
|
+
const dist = getDevDist();
|
|
3216
|
+
if (dist) {
|
|
3217
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3218
|
+
if (sourcePath && fs10.existsSync(sourcePath)) {
|
|
3219
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
const module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
2749
3224
|
const handler = module["WS"];
|
|
2750
3225
|
if (typeof handler !== "function") {
|
|
2751
3226
|
throw new Error(`WS export not found in ${filePath}`);
|
|
@@ -2820,8 +3295,8 @@ function attachWebSocket(options) {
|
|
|
2820
3295
|
const finalHandler = async () => {
|
|
2821
3296
|
let handlers;
|
|
2822
3297
|
try {
|
|
2823
|
-
const absoluteFilePath =
|
|
2824
|
-
handlers = await loadWsHandler(absoluteFilePath, ctx);
|
|
3298
|
+
const absoluteFilePath = path10.resolve(rootDir, route.filePath);
|
|
3299
|
+
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
2825
3300
|
} catch (err) {
|
|
2826
3301
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2827
3302
|
console.error(`[faapi] WS handler \u52A0\u8F7D\u5931\u8D25 ${route.filePath}: ${reason}`);
|
|
@@ -2843,6 +3318,14 @@ function attachWebSocket(options) {
|
|
|
2843
3318
|
};
|
|
2844
3319
|
let response;
|
|
2845
3320
|
try {
|
|
3321
|
+
if (route.middlewares === void 0 && route.middlewarePaths) {
|
|
3322
|
+
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
3323
|
+
if (bundle) {
|
|
3324
|
+
route.middlewares = bundle.middlewares;
|
|
3325
|
+
} else {
|
|
3326
|
+
route.middlewares = [];
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
2846
3329
|
const dirMiddlewares = route.middlewares ?? [];
|
|
2847
3330
|
const allMiddlewares = globalMiddlewares && globalMiddlewares.length > 0 ? [...globalMiddlewares, ...dirMiddlewares] : dirMiddlewares;
|
|
2848
3331
|
if (allMiddlewares.length > 0) {
|
|
@@ -2866,6 +3349,7 @@ function attachWebSocket(options) {
|
|
|
2866
3349
|
}
|
|
2867
3350
|
|
|
2868
3351
|
// src/server/createServer.ts
|
|
3352
|
+
init_generateSchemaFiles();
|
|
2869
3353
|
var DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;
|
|
2870
3354
|
function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
2871
3355
|
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
@@ -2910,15 +3394,15 @@ function limitStreamSize(stream, maxSize) {
|
|
|
2910
3394
|
}
|
|
2911
3395
|
});
|
|
2912
3396
|
}
|
|
2913
|
-
function findAllowedMethods(routes,
|
|
3397
|
+
function findAllowedMethods(routes, path15) {
|
|
2914
3398
|
const methods = /* @__PURE__ */ new Set();
|
|
2915
3399
|
for (const route of routes) {
|
|
2916
|
-
if (route.urlPath ===
|
|
3400
|
+
if (route.urlPath === path15) {
|
|
2917
3401
|
methods.add(route.method);
|
|
2918
3402
|
continue;
|
|
2919
3403
|
}
|
|
2920
3404
|
if (route.isDynamic) {
|
|
2921
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3405
|
+
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2922
3406
|
if (params !== null) {
|
|
2923
3407
|
methods.add(route.method);
|
|
2924
3408
|
}
|
|
@@ -3004,16 +3488,32 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3004
3488
|
}
|
|
3005
3489
|
ctx.params = match.params;
|
|
3006
3490
|
const { route } = match;
|
|
3007
|
-
const absoluteFilePath =
|
|
3008
|
-
const routeModule = await loadRouteModule(absoluteFilePath, route.method);
|
|
3491
|
+
const absoluteFilePath = path11.resolve(rootDir, route.filePath);
|
|
3492
|
+
const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
|
|
3009
3493
|
const input = await resolveInput(route.method, request);
|
|
3010
3494
|
const inputType = getInputTypeForMethod(route.method);
|
|
3011
3495
|
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
3496
|
+
if (isDevOnDemandEnabled()) {
|
|
3497
|
+
const devDist = getDevDist();
|
|
3498
|
+
if (devDist) {
|
|
3499
|
+
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3012
3502
|
const result = await validateInput(schemaPath, route.method, inputType, input);
|
|
3013
3503
|
if (!result.valid) {
|
|
3014
3504
|
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", result.issues);
|
|
3015
3505
|
}
|
|
3016
3506
|
const body = hasBody(route.method) ? result.data : void 0;
|
|
3507
|
+
if (route.middlewares === void 0 && route.injectors === void 0 && route.middlewarePaths) {
|
|
3508
|
+
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
3509
|
+
if (bundle) {
|
|
3510
|
+
route.middlewares = bundle.middlewares;
|
|
3511
|
+
route.injectors = bundle.injectors;
|
|
3512
|
+
} else {
|
|
3513
|
+
route.middlewares = [];
|
|
3514
|
+
route.injectors = {};
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3017
3517
|
const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
|
|
3018
3518
|
const response = await invokeHandler(
|
|
3019
3519
|
routeModule.handler,
|
|
@@ -3068,7 +3568,7 @@ async function createTestServer(options) {
|
|
|
3068
3568
|
} = options;
|
|
3069
3569
|
const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
|
|
3070
3570
|
const sorted = sortRoutes(routes);
|
|
3071
|
-
const schemaDist = dist ?
|
|
3571
|
+
const schemaDist = dist ? path12.isAbsolute(dist) ? dist : path12.resolve(rootDir, dist) : await fs11.mkdtemp(path12.join(os.tmpdir(), "faapi-test-schema-"));
|
|
3072
3572
|
await generateSchemaFiles(sorted, rootDir, schemaDist);
|
|
3073
3573
|
const { server } = createServer({
|
|
3074
3574
|
routes: sorted,
|
|
@@ -3101,7 +3601,7 @@ async function createTestServer(options) {
|
|
|
3101
3601
|
await new Promise((resolve) => {
|
|
3102
3602
|
server.close(() => resolve());
|
|
3103
3603
|
});
|
|
3104
|
-
await
|
|
3604
|
+
await fs11.rm(schemaDist, { recursive: true, force: true }).catch(() => {
|
|
3105
3605
|
});
|
|
3106
3606
|
invalidateSchemaCache();
|
|
3107
3607
|
}
|
|
@@ -3243,8 +3743,8 @@ async function connectWs(baseUrl, pathname, options = {}) {
|
|
|
3243
3743
|
}
|
|
3244
3744
|
|
|
3245
3745
|
// src/cli/createAppCore.ts
|
|
3246
|
-
import
|
|
3247
|
-
import
|
|
3746
|
+
import fs13 from "fs";
|
|
3747
|
+
import path14 from "path";
|
|
3248
3748
|
import { PassThrough } from "stream";
|
|
3249
3749
|
|
|
3250
3750
|
// src/router/detectRouteConflicts.ts
|
|
@@ -3301,55 +3801,30 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
3301
3801
|
}
|
|
3302
3802
|
|
|
3303
3803
|
// src/cli/generateRoutes.ts
|
|
3304
|
-
import
|
|
3305
|
-
import
|
|
3804
|
+
import fs12 from "fs";
|
|
3805
|
+
import path13 from "path";
|
|
3306
3806
|
async function hydrateRoutes(manifest) {
|
|
3307
|
-
const hydrateRoute =
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
isDynamic: serialized.isDynamic,
|
|
3327
|
-
isCatchAll: serialized.isCatchAll,
|
|
3328
|
-
middlewares: bundle?.middlewares,
|
|
3329
|
-
injectors: bundle?.injectors
|
|
3330
|
-
};
|
|
3331
|
-
};
|
|
3332
|
-
const routes = await Promise.all(manifest.routes.map(hydrateRoute));
|
|
3333
|
-
const wsRoutes = await Promise.all(manifest.wsRoutes.map(hydrateWsRoute));
|
|
3807
|
+
const hydrateRoute = (serialized) => ({
|
|
3808
|
+
method: serialized.method,
|
|
3809
|
+
urlPath: serialized.urlPath,
|
|
3810
|
+
filePath: serialized.filePath,
|
|
3811
|
+
paramNames: serialized.paramNames,
|
|
3812
|
+
isDynamic: serialized.isDynamic,
|
|
3813
|
+
isCatchAll: serialized.isCatchAll,
|
|
3814
|
+
middlewarePaths: serialized.middlewarePaths
|
|
3815
|
+
});
|
|
3816
|
+
const hydrateWsRoute = (serialized) => ({
|
|
3817
|
+
urlPath: serialized.urlPath,
|
|
3818
|
+
filePath: serialized.filePath,
|
|
3819
|
+
paramNames: serialized.paramNames,
|
|
3820
|
+
isDynamic: serialized.isDynamic,
|
|
3821
|
+
isCatchAll: serialized.isCatchAll,
|
|
3822
|
+
middlewarePaths: serialized.middlewarePaths
|
|
3823
|
+
});
|
|
3824
|
+
const routes = manifest.routes.map(hydrateRoute);
|
|
3825
|
+
const wsRoutes = manifest.wsRoutes.map(hydrateWsRoute);
|
|
3334
3826
|
return { routes, wsRoutes };
|
|
3335
3827
|
}
|
|
3336
|
-
async function loadMiddlewarePaths(middlewarePaths) {
|
|
3337
|
-
if (middlewarePaths.length === 0) return void 0;
|
|
3338
|
-
const mergedMiddlewares = [];
|
|
3339
|
-
const mergedInjectors = {};
|
|
3340
|
-
for (const absMwPath of middlewarePaths) {
|
|
3341
|
-
let bundle = getCachedMiddlewares(absMwPath);
|
|
3342
|
-
if (bundle === void 0) {
|
|
3343
|
-
bundle = await loadMiddlewaresFile(absMwPath);
|
|
3344
|
-
setCachedMiddlewares(absMwPath, bundle);
|
|
3345
|
-
}
|
|
3346
|
-
mergedMiddlewares.push(...bundle.middlewares);
|
|
3347
|
-
for (const [name, injector] of Object.entries(bundle.injectors)) {
|
|
3348
|
-
mergedInjectors[name] = injector;
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
return { middlewares: mergedMiddlewares, injectors: mergedInjectors };
|
|
3352
|
-
}
|
|
3353
3828
|
|
|
3354
3829
|
// src/cli/loadPlugins.ts
|
|
3355
3830
|
async function loadPlugins(declarations, ctx) {
|
|
@@ -3433,8 +3908,8 @@ function isFaapiConfigKey(key) {
|
|
|
3433
3908
|
async function createAppBase(options) {
|
|
3434
3909
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
3435
3910
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
3436
|
-
const routesPath =
|
|
3437
|
-
if (!
|
|
3911
|
+
const routesPath = path14.resolve(rootDir, dist, ROUTES_FILE);
|
|
3912
|
+
if (!fs13.existsSync(routesPath)) {
|
|
3438
3913
|
throw new Error(
|
|
3439
3914
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
3440
3915
|
);
|
|
@@ -3607,6 +4082,10 @@ async function createAppBase(options) {
|
|
|
3607
4082
|
if (config?.lifecycle?.onClose) {
|
|
3608
4083
|
await config.lifecycle.onClose({ rootDir, routes: sorted, server });
|
|
3609
4084
|
}
|
|
4085
|
+
if (!server.listening) {
|
|
4086
|
+
app.server = null;
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
3610
4089
|
return new Promise((resolve) => {
|
|
3611
4090
|
server.close((err) => {
|
|
3612
4091
|
if (err) console.error("Error closing server:", err);
|
|
@@ -3636,6 +4115,7 @@ async function createAppBase(options) {
|
|
|
3636
4115
|
}
|
|
3637
4116
|
|
|
3638
4117
|
// src/cli/createDevApp.ts
|
|
4118
|
+
init_createProgram();
|
|
3639
4119
|
async function createDevApp(options) {
|
|
3640
4120
|
const { app, ctx } = await createAppBase(options);
|
|
3641
4121
|
const devApp = app;
|
|
@@ -3644,9 +4124,16 @@ async function createDevApp(options) {
|
|
|
3644
4124
|
invalidateMiddlewareCache();
|
|
3645
4125
|
invalidateProgramCache();
|
|
3646
4126
|
invalidateSchemaCache();
|
|
4127
|
+
clearCompiledFiles();
|
|
3647
4128
|
const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.dist);
|
|
3648
4129
|
const sorted = sortRoutes(reScanned.routes);
|
|
3649
|
-
|
|
4130
|
+
if (isDevOnDemandEnabled()) {
|
|
4131
|
+
await deleteSchemaFiles(sorted, ctx.rootDir, ctx.dist);
|
|
4132
|
+
clearGeneratedSchemas();
|
|
4133
|
+
} else {
|
|
4134
|
+
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
4135
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
4136
|
+
}
|
|
3650
4137
|
ctx.updateRoutes(sorted, reScanned.wsRoutes);
|
|
3651
4138
|
};
|
|
3652
4139
|
return devApp;
|