@faapi/faapi 1.1.1-canary.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +3601 -3432
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1937 -1453
- 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,1468 +894,1510 @@ 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
|
-
|
|
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
|
-
};
|
|
961
|
+
}
|
|
1010
962
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
var loadTs;
|
|
1019
|
-
function setLoadTimestamp(ts6) {
|
|
1020
|
-
loadTs = ts6;
|
|
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);
|
|
968
|
+
}
|
|
969
|
+
return withConstraints;
|
|
1021
970
|
}
|
|
1022
|
-
function
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
return vi.importActual.bind(vi);
|
|
971
|
+
function applyConstraints(baseExpr, constraints, typeKind) {
|
|
972
|
+
const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join("");
|
|
973
|
+
return `${baseExpr}${suffix}`;
|
|
1026
974
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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()";
|
|
1035
1007
|
}
|
|
1036
|
-
return await import(url);
|
|
1037
1008
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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`;
|
|
1052
1049
|
}
|
|
1053
|
-
return null;
|
|
1054
1050
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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");
|
|
1061
1060
|
}
|
|
1062
|
-
function
|
|
1063
|
-
return
|
|
1061
|
+
function usesCoerceHelpers(code) {
|
|
1062
|
+
return code.includes("coerceNumber") || code.includes("coerceBoolean") || code.includes("coerceMap") || code.includes("coerceSet");
|
|
1064
1063
|
}
|
|
1065
|
-
function
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
for (const line of lines) {
|
|
1069
|
-
const trimmed = line.trim();
|
|
1070
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1071
|
-
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
1072
|
-
if (!match) continue;
|
|
1073
|
-
const [, key, rawValue] = match;
|
|
1074
|
-
const value = parseValue(rawValue, { ...fileVars, ...result });
|
|
1075
|
-
result[key] = value;
|
|
1064
|
+
function wrapCoercePreprocess(kind, inner) {
|
|
1065
|
+
if (kind === "number") {
|
|
1066
|
+
return `z.preprocess(coerceNumber, ${inner})`;
|
|
1076
1067
|
}
|
|
1077
|
-
return
|
|
1068
|
+
return `z.preprocess(coerceBoolean, ${inner})`;
|
|
1078
1069
|
}
|
|
1079
|
-
function
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
+
}
|
|
1084
1083
|
}
|
|
1085
|
-
if (
|
|
1086
|
-
|
|
1087
|
-
const inner = match ? match[1] : raw.slice(1);
|
|
1088
|
-
return expandEscapesAndVars(inner, env);
|
|
1084
|
+
if (restExpression) {
|
|
1085
|
+
return `z.tuple([${fixedExprs.join(", ")}]).rest(${restExpression})`;
|
|
1089
1086
|
}
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
case "r":
|
|
1100
|
-
return "\r";
|
|
1101
|
-
case "t":
|
|
1102
|
-
return " ";
|
|
1103
|
-
case "\\":
|
|
1104
|
-
return "\\";
|
|
1105
|
-
case '"':
|
|
1106
|
-
return '"';
|
|
1107
|
-
default:
|
|
1108
|
-
return ch;
|
|
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;
|
|
1109
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}`;
|
|
1110
1111
|
});
|
|
1111
|
-
return
|
|
1112
|
-
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
1113
|
-
(_, braced, plain) => {
|
|
1114
|
-
const varName = braced || plain;
|
|
1115
|
-
return env[varName] ?? process.env[varName] ?? "";
|
|
1116
|
-
}
|
|
1117
|
-
);
|
|
1112
|
+
return `z.object({ ${fields.join(", ")} })`;
|
|
1118
1113
|
}
|
|
1119
|
-
function
|
|
1120
|
-
const
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
const filePath = path3.join(rootDir, file);
|
|
1125
|
-
if (!fs2.existsSync(filePath)) continue;
|
|
1126
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1127
|
-
const parsed = parseEnvFile(content, merged);
|
|
1128
|
-
Object.assign(merged, parsed);
|
|
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()`;
|
|
1129
1119
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1120
|
+
if (hasNull) {
|
|
1121
|
+
const unionInner2 = nonNull.map((m) => runtimeTypeToZodExpression(m, ctx)).join(", ");
|
|
1122
|
+
return `z.union([${unionInner2}]).nullable()`;
|
|
1134
1123
|
}
|
|
1124
|
+
const unionInner = members.map((m) => runtimeTypeToZodExpression(m, ctx)).join(", ");
|
|
1125
|
+
return `z.union([${unionInner}])`;
|
|
1135
1126
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
this.code = code;
|
|
1142
|
-
this.statusCode = statusCode;
|
|
1143
|
-
this.name = "FaapiError";
|
|
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});`;
|
|
1144
1132
|
}
|
|
1145
|
-
|
|
1146
|
-
statusCode;
|
|
1147
|
-
};
|
|
1148
|
-
|
|
1149
|
-
// src/errors/httpErrors.ts
|
|
1150
|
-
function deriveStatusCode(issues) {
|
|
1151
|
-
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
1152
|
-
return has400 ? 400 : 422;
|
|
1133
|
+
return `const ${name}Schema = ${expr};`;
|
|
1153
1134
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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;
|
|
1173
1155
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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));
|
|
1180
1170
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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};`);
|
|
1186
1178
|
}
|
|
1187
|
-
|
|
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
|
+
});
|
|
1188
1212
|
|
|
1189
|
-
// src/
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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);
|
|
1195
1228
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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);
|
|
1199
1239
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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"));
|
|
1203
1267
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1268
|
+
const allSchemaCode = schemaBlocks.join("\n");
|
|
1269
|
+
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
1270
|
+
lines.push(
|
|
1271
|
+
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
1272
|
+
);
|
|
1207
1273
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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);
|
|
1216
1287
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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);
|
|
1221
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 });
|
|
1222
1304
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
+
);
|
|
1225
1313
|
}
|
|
1226
|
-
function
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
let closed = false;
|
|
1230
|
-
let aborted = false;
|
|
1231
|
-
const stream = new ReadableStream({
|
|
1232
|
-
start(c) {
|
|
1233
|
-
controller = c;
|
|
1234
|
-
},
|
|
1235
|
-
cancel() {
|
|
1236
|
-
aborted = true;
|
|
1237
|
-
closed = true;
|
|
1238
|
-
controller = null;
|
|
1239
|
-
}
|
|
1240
|
-
});
|
|
1241
|
-
const response = new Response(stream, {
|
|
1242
|
-
status: 200,
|
|
1243
|
-
headers: {
|
|
1244
|
-
"Content-Type": "text/event-stream",
|
|
1245
|
-
"Cache-Control": "no-cache",
|
|
1246
|
-
Connection: "keep-alive"
|
|
1247
|
-
}
|
|
1248
|
-
});
|
|
1249
|
-
const writer = {
|
|
1250
|
-
send(event) {
|
|
1251
|
-
if (closed || !controller) return;
|
|
1252
|
-
const text = encodeSseEvent(event);
|
|
1253
|
-
controller.enqueue(encoder.encode(text));
|
|
1254
|
-
},
|
|
1255
|
-
sendRaw(chunk) {
|
|
1256
|
-
if (closed || !controller) return;
|
|
1257
|
-
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
1258
|
-
controller.enqueue(bytes);
|
|
1259
|
-
},
|
|
1260
|
-
sendError(error) {
|
|
1261
|
-
if (closed || !controller) return;
|
|
1262
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1263
|
-
const text = encodeSseEvent({ event: "error", data: message });
|
|
1264
|
-
try {
|
|
1265
|
-
controller.enqueue(encoder.encode(text));
|
|
1266
|
-
} finally {
|
|
1267
|
-
writer.close();
|
|
1268
|
-
}
|
|
1269
|
-
},
|
|
1270
|
-
close() {
|
|
1271
|
-
if (closed) return;
|
|
1272
|
-
closed = true;
|
|
1273
|
-
if (controller) {
|
|
1274
|
-
try {
|
|
1275
|
-
controller.close();
|
|
1276
|
-
} catch {
|
|
1277
|
-
}
|
|
1278
|
-
controller = null;
|
|
1279
|
-
}
|
|
1280
|
-
},
|
|
1281
|
-
get closed() {
|
|
1282
|
-
return closed;
|
|
1283
|
-
},
|
|
1284
|
-
get aborted() {
|
|
1285
|
-
return aborted;
|
|
1286
|
-
},
|
|
1287
|
-
get response() {
|
|
1288
|
-
return response;
|
|
1289
|
-
}
|
|
1290
|
-
};
|
|
1291
|
-
return writer;
|
|
1314
|
+
async function writeSchemaFile(outputPath, source) {
|
|
1315
|
+
await fs4.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
1316
|
+
await fs4.writeFile(outputPath, source, "utf-8");
|
|
1292
1317
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
for (const pair of cookieHeader.split(";")) {
|
|
1299
|
-
const [name, ...rest] = pair.split("=");
|
|
1300
|
-
const trimmed = name?.trim();
|
|
1301
|
-
if (trimmed) {
|
|
1302
|
-
cookies.set(trimmed, rest.join("=").trim());
|
|
1303
|
-
}
|
|
1318
|
+
var init_generateSchemaFiles = __esm({
|
|
1319
|
+
"src/cli/generateSchemaFiles.ts"() {
|
|
1320
|
+
"use strict";
|
|
1321
|
+
init_collectRouteSchemaSources();
|
|
1322
|
+
init_generateZodSchema();
|
|
1304
1323
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
return parsedCookies.get(name);
|
|
1369
|
-
},
|
|
1370
|
-
setCookie(name, value, options) {
|
|
1371
|
-
meta.setCookies.push(formatSetCookie(name, value, options));
|
|
1372
|
-
},
|
|
1373
|
-
deleteCookie(name) {
|
|
1374
|
-
meta.setCookies.push(formatSetCookie(name, "", { maxAge: 0 }));
|
|
1375
|
-
},
|
|
1376
|
-
/**
|
|
1377
|
-
* 创建 SSE writer,用于流式推送事件
|
|
1378
|
-
*
|
|
1379
|
-
* handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response
|
|
1380
|
-
* 作为 HTTP 响应(Content-Type: text/event-stream)。
|
|
1381
|
-
*
|
|
1382
|
-
* 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
|
|
1383
|
-
*/
|
|
1384
|
-
sse() {
|
|
1385
|
-
const writer = createSseWriter();
|
|
1386
|
-
const ctxWithSse = ctx;
|
|
1387
|
-
ctxWithSse.__sseResponse = writer.response;
|
|
1388
|
-
ctxWithSse.__sseWriter = writer;
|
|
1389
|
-
return writer;
|
|
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");
|
|
1390
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();
|
|
1391
1395
|
};
|
|
1392
|
-
const extend = config?.extendContext;
|
|
1393
|
-
if (typeof extend === "function") {
|
|
1394
|
-
extend(ctx);
|
|
1395
|
-
}
|
|
1396
|
-
return ctx;
|
|
1397
1396
|
}
|
|
1398
1397
|
|
|
1399
|
-
// src/
|
|
1400
|
-
function
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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
|
+
};
|
|
1409
1429
|
}
|
|
1410
1430
|
|
|
1411
|
-
// src/
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
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);
|
|
1420
1452
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1453
|
+
if (opts.xFrameOptions !== false) {
|
|
1454
|
+
ctx.setHeader("X-Frame-Options", opts.xFrameOptions);
|
|
1423
1455
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
|
|
1427
|
-
const headers2 = new Headers(value.headers);
|
|
1428
|
-
applyMeta(headers2);
|
|
1429
|
-
return new Response(value.body, {
|
|
1430
|
-
status: meta.status ?? value.status,
|
|
1431
|
-
headers: headers2
|
|
1432
|
-
});
|
|
1456
|
+
if (opts.xContentTypeOptions) {
|
|
1457
|
+
ctx.setHeader("X-Content-Type-Options", "nosniff");
|
|
1433
1458
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
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) {
|
|
1509
|
+
const importActual = getVitestImportActual();
|
|
1510
|
+
if (importActual) {
|
|
1511
|
+
return await importActual(filePath);
|
|
1441
1512
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
return new Response(value, {
|
|
1446
|
-
status: meta?.status ?? 200,
|
|
1447
|
-
headers: headers2
|
|
1448
|
-
});
|
|
1513
|
+
let url = pathToFileURL(filePath).href;
|
|
1514
|
+
if (loadTs !== void 0) {
|
|
1515
|
+
url += `?t=${loadTs}`;
|
|
1449
1516
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1517
|
+
return await import(url);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// src/config/loadConfig.ts
|
|
1521
|
+
var CONFIG_PRODUCT_FILE = "faapi-config.js";
|
|
1522
|
+
async function loadConfig(rootDir, dist) {
|
|
1523
|
+
const configProductPath = path2.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
|
|
1524
|
+
if (fs.existsSync(configProductPath)) {
|
|
1525
|
+
const module = await importWithCacheBust(configProductPath);
|
|
1526
|
+
return module.default ?? {};
|
|
1457
1527
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
headers: headers2
|
|
1464
|
-
});
|
|
1528
|
+
const hasSourceConfig = fs.existsSync(path2.join(rootDir, "faapi.config.ts")) || fs.existsSync(path2.join(rootDir, "faapi.config.js"));
|
|
1529
|
+
if (hasSourceConfig) {
|
|
1530
|
+
throw new Error(
|
|
1531
|
+
`[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
1532
|
+
);
|
|
1465
1533
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// src/cli/loadEnv.ts
|
|
1538
|
+
import fs2 from "fs";
|
|
1539
|
+
import path3 from "path";
|
|
1540
|
+
function resolveEnv() {
|
|
1541
|
+
return process.env.NODE_ENV || "development";
|
|
1542
|
+
}
|
|
1543
|
+
function getEnvFiles(env) {
|
|
1544
|
+
return [".env", ".env.local", `.env.${env}`, `.env.${env}.local`];
|
|
1545
|
+
}
|
|
1546
|
+
function parseEnvFile(content, fileVars) {
|
|
1547
|
+
const result = {};
|
|
1548
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
1549
|
+
for (const line of lines) {
|
|
1550
|
+
const trimmed = line.trim();
|
|
1551
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1552
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
1553
|
+
if (!match) continue;
|
|
1554
|
+
const [, key, rawValue] = match;
|
|
1555
|
+
const value = parseValue(rawValue, { ...fileVars, ...result });
|
|
1556
|
+
result[key] = value;
|
|
1474
1557
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1558
|
+
return result;
|
|
1559
|
+
}
|
|
1560
|
+
function parseValue(raw, env) {
|
|
1561
|
+
if (raw === "") return "";
|
|
1562
|
+
if (raw[0] === "'") {
|
|
1563
|
+
const end = raw.indexOf("'", 1);
|
|
1564
|
+
return end === -1 ? raw.slice(1) : raw.slice(1, end);
|
|
1482
1565
|
}
|
|
1483
|
-
if (
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
return
|
|
1487
|
-
status: meta?.status ?? 200,
|
|
1488
|
-
headers: headers2
|
|
1489
|
-
});
|
|
1566
|
+
if (raw[0] === '"') {
|
|
1567
|
+
const match = /^"((?:\\.|[^"\\])*)"/.exec(raw);
|
|
1568
|
+
const inner = match ? match[1] : raw.slice(1);
|
|
1569
|
+
return expandEscapesAndVars(inner, env);
|
|
1490
1570
|
}
|
|
1491
|
-
const
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1571
|
+
const commentMatch = /^(.*?)(\s+#.*)$/.exec(raw);
|
|
1572
|
+
const value = commentMatch ? commentMatch[1] : raw;
|
|
1573
|
+
return value.trim();
|
|
1574
|
+
}
|
|
1575
|
+
function expandEscapesAndVars(str, env) {
|
|
1576
|
+
const escaped = str.replace(/\\(.)/g, (_, ch) => {
|
|
1577
|
+
switch (ch) {
|
|
1578
|
+
case "n":
|
|
1579
|
+
return "\n";
|
|
1580
|
+
case "r":
|
|
1581
|
+
return "\r";
|
|
1582
|
+
case "t":
|
|
1583
|
+
return " ";
|
|
1584
|
+
case "\\":
|
|
1585
|
+
return "\\";
|
|
1586
|
+
case '"':
|
|
1587
|
+
return '"';
|
|
1588
|
+
default:
|
|
1589
|
+
return ch;
|
|
1590
|
+
}
|
|
1497
1591
|
});
|
|
1592
|
+
return escaped.replace(
|
|
1593
|
+
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
1594
|
+
(_, braced, plain) => {
|
|
1595
|
+
const varName = braced || plain;
|
|
1596
|
+
return env[varName] ?? process.env[varName] ?? "";
|
|
1597
|
+
}
|
|
1598
|
+
);
|
|
1498
1599
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
const
|
|
1503
|
-
for (const
|
|
1504
|
-
|
|
1600
|
+
function loadEnv(rootDir) {
|
|
1601
|
+
const env = resolveEnv();
|
|
1602
|
+
const files = getEnvFiles(env);
|
|
1603
|
+
const merged = {};
|
|
1604
|
+
for (const file of files) {
|
|
1605
|
+
const filePath = path3.join(rootDir, file);
|
|
1606
|
+
if (!fs2.existsSync(filePath)) continue;
|
|
1607
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1608
|
+
const parsed = parseEnvFile(content, merged);
|
|
1609
|
+
Object.assign(merged, parsed);
|
|
1610
|
+
}
|
|
1611
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
1612
|
+
if (process.env[key] === void 0) {
|
|
1613
|
+
process.env[key] = value;
|
|
1614
|
+
}
|
|
1505
1615
|
}
|
|
1506
|
-
return result;
|
|
1507
1616
|
}
|
|
1508
1617
|
|
|
1509
|
-
// src/
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
case "headers":
|
|
1517
|
-
return ctx.headers;
|
|
1518
|
-
case "context":
|
|
1519
|
-
return ctx;
|
|
1520
|
-
case "cookies":
|
|
1521
|
-
return ctx.cookies;
|
|
1522
|
-
case "ip":
|
|
1523
|
-
return ctx.ip;
|
|
1524
|
-
case "ua":
|
|
1525
|
-
return ctx.ua;
|
|
1526
|
-
case "body":
|
|
1527
|
-
return body;
|
|
1528
|
-
// form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)
|
|
1529
|
-
// 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)
|
|
1530
|
-
case "form":
|
|
1531
|
-
return body;
|
|
1532
|
-
case "files":
|
|
1533
|
-
if (body && typeof body === "object" && "files" in body) {
|
|
1534
|
-
return body.files;
|
|
1535
|
-
}
|
|
1536
|
-
return [];
|
|
1537
|
-
case "fields":
|
|
1538
|
-
if (body && typeof body === "object" && "fields" in body) {
|
|
1539
|
-
return body.fields;
|
|
1540
|
-
}
|
|
1541
|
-
return {};
|
|
1542
|
-
default:
|
|
1543
|
-
return void 0;
|
|
1618
|
+
// src/errors/FaapiError.ts
|
|
1619
|
+
var FaapiError = class extends Error {
|
|
1620
|
+
constructor(code, message, statusCode) {
|
|
1621
|
+
super(message);
|
|
1622
|
+
this.code = code;
|
|
1623
|
+
this.statusCode = statusCode;
|
|
1624
|
+
this.name = "FaapiError";
|
|
1544
1625
|
}
|
|
1626
|
+
code;
|
|
1627
|
+
statusCode;
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
// src/errors/httpErrors.ts
|
|
1631
|
+
function deriveStatusCode(issues) {
|
|
1632
|
+
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
1633
|
+
return has400 ? 400 : 422;
|
|
1545
1634
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1635
|
+
var ValidationError = class extends FaapiError {
|
|
1636
|
+
constructor(message, issues) {
|
|
1637
|
+
super("VALIDATION_ERROR", message, deriveStatusCode(issues));
|
|
1638
|
+
this.issues = issues;
|
|
1639
|
+
this.name = "ValidationError";
|
|
1550
1640
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1641
|
+
issues;
|
|
1642
|
+
};
|
|
1643
|
+
var RouteNotFoundError = class extends FaapiError {
|
|
1644
|
+
constructor(path15) {
|
|
1645
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path15}`, 404);
|
|
1646
|
+
this.name = "RouteNotFoundError";
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
var MethodNotAllowedError = class extends FaapiError {
|
|
1650
|
+
constructor(method, path15, allowedMethods) {
|
|
1651
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path15}`, 405);
|
|
1652
|
+
this.allowedMethods = allowedMethods;
|
|
1653
|
+
this.name = "MethodNotAllowedError";
|
|
1654
|
+
}
|
|
1655
|
+
allowedMethods;
|
|
1656
|
+
};
|
|
1657
|
+
var InternalError = class extends FaapiError {
|
|
1658
|
+
constructor(message) {
|
|
1659
|
+
super("INTERNAL_ERROR", message, 500);
|
|
1660
|
+
this.name = "InternalError";
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
var ModuleLoadError = class extends FaapiError {
|
|
1664
|
+
constructor(filePath, reason) {
|
|
1665
|
+
super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
|
|
1666
|
+
this.name = "ModuleLoadError";
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1564
1669
|
|
|
1565
|
-
// src/runtime/
|
|
1566
|
-
function
|
|
1567
|
-
|
|
1568
|
-
if (
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
headers.set(key, value);
|
|
1670
|
+
// src/runtime/sse.ts
|
|
1671
|
+
function encodeSseEvent(event) {
|
|
1672
|
+
let out = "";
|
|
1673
|
+
if (event.comment !== void 0) {
|
|
1674
|
+
out += `: ${event.comment}
|
|
1675
|
+
`;
|
|
1572
1676
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1677
|
+
if (event.event !== void 0) {
|
|
1678
|
+
out += `event: ${event.event}
|
|
1679
|
+
`;
|
|
1575
1680
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const mw = middlewares[i];
|
|
1593
|
-
let innerResponse;
|
|
1594
|
-
const next = async () => {
|
|
1595
|
-
innerResponse = await dispatch(i + 1);
|
|
1596
|
-
return innerResponse;
|
|
1597
|
-
};
|
|
1598
|
-
const result = await mw(ctx, next);
|
|
1599
|
-
if (result instanceof Response) {
|
|
1600
|
-
return mergeMeta(result, meta);
|
|
1681
|
+
if (event.id !== void 0) {
|
|
1682
|
+
out += `id: ${event.id}
|
|
1683
|
+
`;
|
|
1684
|
+
}
|
|
1685
|
+
if (event.retry !== void 0) {
|
|
1686
|
+
out += `retry: ${event.retry}
|
|
1687
|
+
`;
|
|
1688
|
+
}
|
|
1689
|
+
if (event.data !== void 0) {
|
|
1690
|
+
let dataStr;
|
|
1691
|
+
if (typeof event.data === "string") {
|
|
1692
|
+
dataStr = event.data;
|
|
1693
|
+
} else if (event.data === null) {
|
|
1694
|
+
dataStr = "null";
|
|
1695
|
+
} else {
|
|
1696
|
+
dataStr = JSON.stringify(event.data);
|
|
1601
1697
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1698
|
+
const lines = dataStr.split("\n");
|
|
1699
|
+
for (const line of lines) {
|
|
1700
|
+
out += `data: ${line}
|
|
1701
|
+
`;
|
|
1604
1702
|
}
|
|
1605
|
-
throw new Error("\u4E2D\u95F4\u4EF6\u5FC5\u987B await next() \u6216\u8FD4\u56DE Response");
|
|
1606
1703
|
}
|
|
1607
|
-
|
|
1704
|
+
out += "\n";
|
|
1705
|
+
return out;
|
|
1608
1706
|
}
|
|
1609
|
-
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
sseWriter.close();
|
|
1707
|
+
function createSseWriter() {
|
|
1708
|
+
const encoder = new TextEncoder();
|
|
1709
|
+
let controller = null;
|
|
1710
|
+
let closed = false;
|
|
1711
|
+
let aborted = false;
|
|
1712
|
+
const stream = new ReadableStream({
|
|
1713
|
+
start(c) {
|
|
1714
|
+
controller = c;
|
|
1715
|
+
},
|
|
1716
|
+
cancel() {
|
|
1717
|
+
aborted = true;
|
|
1718
|
+
closed = true;
|
|
1719
|
+
controller = null;
|
|
1623
1720
|
}
|
|
1624
|
-
};
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
} catch (err) {
|
|
1632
|
-
autoCloseSseOnError();
|
|
1633
|
-
throw err;
|
|
1721
|
+
});
|
|
1722
|
+
const response = new Response(stream, {
|
|
1723
|
+
status: 200,
|
|
1724
|
+
headers: {
|
|
1725
|
+
"Content-Type": "text/event-stream",
|
|
1726
|
+
"Cache-Control": "no-cache",
|
|
1727
|
+
Connection: "keep-alive"
|
|
1634
1728
|
}
|
|
1635
|
-
}
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1729
|
+
});
|
|
1730
|
+
const writer = {
|
|
1731
|
+
send(event) {
|
|
1732
|
+
if (closed || !controller) return;
|
|
1733
|
+
const text = encodeSseEvent(event);
|
|
1734
|
+
controller.enqueue(encoder.encode(text));
|
|
1735
|
+
},
|
|
1736
|
+
sendRaw(chunk) {
|
|
1737
|
+
if (closed || !controller) return;
|
|
1738
|
+
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
1739
|
+
controller.enqueue(bytes);
|
|
1740
|
+
},
|
|
1741
|
+
sendError(error) {
|
|
1742
|
+
if (closed || !controller) return;
|
|
1743
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1744
|
+
const text = encodeSseEvent({ event: "error", data: message });
|
|
1745
|
+
try {
|
|
1746
|
+
controller.enqueue(encoder.encode(text));
|
|
1747
|
+
} finally {
|
|
1748
|
+
writer.close();
|
|
1749
|
+
}
|
|
1750
|
+
},
|
|
1751
|
+
close() {
|
|
1752
|
+
if (closed) return;
|
|
1753
|
+
closed = true;
|
|
1754
|
+
if (controller) {
|
|
1755
|
+
try {
|
|
1756
|
+
controller.close();
|
|
1757
|
+
} catch {
|
|
1758
|
+
}
|
|
1759
|
+
controller = null;
|
|
1760
|
+
}
|
|
1761
|
+
},
|
|
1762
|
+
get closed() {
|
|
1763
|
+
return closed;
|
|
1764
|
+
},
|
|
1765
|
+
get aborted() {
|
|
1766
|
+
return aborted;
|
|
1767
|
+
},
|
|
1768
|
+
get response() {
|
|
1769
|
+
return response;
|
|
1645
1770
|
}
|
|
1646
1771
|
};
|
|
1647
|
-
return
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
// src/testServer.ts
|
|
1651
|
-
import path8 from "path";
|
|
1652
|
-
import os from "os";
|
|
1653
|
-
import fs5 from "fs/promises";
|
|
1654
|
-
|
|
1655
|
-
// src/router/scanRoutes.ts
|
|
1656
|
-
import fg from "fast-glob";
|
|
1657
|
-
import path4 from "path";
|
|
1658
|
-
import fs3 from "fs";
|
|
1659
|
-
|
|
1660
|
-
// src/router/constants.ts
|
|
1661
|
-
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
1662
|
-
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
1663
|
-
function isHttpMethod(value) {
|
|
1664
|
-
return HTTP_METHOD_SET.has(value);
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
// src/utils/normalizePath.ts
|
|
1668
|
-
function normalizePath(path11) {
|
|
1669
|
-
if (!path11) return "";
|
|
1670
|
-
let result = path11.replace(/\\/g, "/");
|
|
1671
|
-
result = result.replace(/\/+/g, "/");
|
|
1672
|
-
result = result.replace(/\/+$/, "");
|
|
1673
|
-
if (result && !result.startsWith("/")) {
|
|
1674
|
-
result = "/" + result;
|
|
1675
|
-
}
|
|
1676
|
-
return result;
|
|
1772
|
+
return writer;
|
|
1677
1773
|
}
|
|
1678
1774
|
|
|
1679
|
-
// src/
|
|
1680
|
-
function
|
|
1681
|
-
const
|
|
1682
|
-
if (
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
const params = [];
|
|
1689
|
-
const segments = urlPath.split("/");
|
|
1690
|
-
for (const segment of segments) {
|
|
1691
|
-
if (segment.startsWith(":...")) {
|
|
1692
|
-
params.push(segment.slice(4));
|
|
1693
|
-
} else if (segment.startsWith(":")) {
|
|
1694
|
-
params.push(segment.slice(1));
|
|
1775
|
+
// src/runtime/createContext.ts
|
|
1776
|
+
function parseCookies(cookieHeader) {
|
|
1777
|
+
const cookies = /* @__PURE__ */ new Map();
|
|
1778
|
+
if (!cookieHeader) return cookies;
|
|
1779
|
+
for (const pair of cookieHeader.split(";")) {
|
|
1780
|
+
const [name, ...rest] = pair.split("=");
|
|
1781
|
+
const trimmed = name?.trim();
|
|
1782
|
+
if (trimmed) {
|
|
1783
|
+
cookies.set(trimmed, rest.join("=").trim());
|
|
1695
1784
|
}
|
|
1696
1785
|
}
|
|
1697
|
-
return
|
|
1698
|
-
}
|
|
1699
|
-
function isCatchAllSegment(segment) {
|
|
1700
|
-
return /^\[\.\.\..+\]$/.test(segment);
|
|
1786
|
+
return cookies;
|
|
1701
1787
|
}
|
|
1702
|
-
function
|
|
1703
|
-
|
|
1788
|
+
function formatSetCookie(name, value, options) {
|
|
1789
|
+
let cookie = `${name}=${value}`;
|
|
1790
|
+
if (options?.domain) cookie += `; Domain=${options.domain}`;
|
|
1791
|
+
if (options?.path) cookie += `; Path=${options.path}`;
|
|
1792
|
+
if (options?.maxAge !== void 0) cookie += `; Max-Age=${options.maxAge}`;
|
|
1793
|
+
if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
1794
|
+
if (options?.httpOnly) cookie += `; HttpOnly`;
|
|
1795
|
+
if (options?.secure) cookie += `; Secure`;
|
|
1796
|
+
if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
1797
|
+
return cookie;
|
|
1704
1798
|
}
|
|
1705
|
-
function
|
|
1706
|
-
const
|
|
1707
|
-
const
|
|
1708
|
-
const
|
|
1709
|
-
|
|
1710
|
-
|
|
1799
|
+
function createContext(request, params, config = {}, ip = "") {
|
|
1800
|
+
const url = new URL(request.url);
|
|
1801
|
+
const meta = { headers: {}, setCookies: [] };
|
|
1802
|
+
const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
|
|
1803
|
+
const cookiesObj = {};
|
|
1804
|
+
for (const [key, val] of parsedCookies) {
|
|
1805
|
+
cookiesObj[key] = val;
|
|
1711
1806
|
}
|
|
1712
|
-
const
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1807
|
+
const ctx = {
|
|
1808
|
+
request,
|
|
1809
|
+
params,
|
|
1810
|
+
query: url.searchParams,
|
|
1811
|
+
headers: request.headers,
|
|
1812
|
+
method: request.method,
|
|
1813
|
+
path: url.pathname,
|
|
1814
|
+
ip,
|
|
1815
|
+
ua: request.headers.get("user-agent") ?? "",
|
|
1816
|
+
cookies: cookiesObj,
|
|
1817
|
+
config,
|
|
1818
|
+
meta,
|
|
1819
|
+
setStatus(status) {
|
|
1820
|
+
meta.status = status;
|
|
1821
|
+
},
|
|
1822
|
+
setHeader(key, value) {
|
|
1823
|
+
meta.headers[key] = value;
|
|
1824
|
+
},
|
|
1825
|
+
setETag(value) {
|
|
1826
|
+
meta.headers["etag"] = value;
|
|
1827
|
+
},
|
|
1828
|
+
redirect(url2, status = 302) {
|
|
1829
|
+
return new Response(null, {
|
|
1830
|
+
status,
|
|
1831
|
+
headers: { Location: url2 }
|
|
1832
|
+
});
|
|
1833
|
+
},
|
|
1834
|
+
json(data, status) {
|
|
1835
|
+
const headers = { "Content-Type": "application/json" };
|
|
1836
|
+
return new Response(JSON.stringify(data), {
|
|
1837
|
+
status: status ?? 200,
|
|
1838
|
+
headers
|
|
1839
|
+
});
|
|
1840
|
+
},
|
|
1841
|
+
html(html, status) {
|
|
1842
|
+
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
1843
|
+
return new Response(html, {
|
|
1844
|
+
status: status ?? 200,
|
|
1845
|
+
headers
|
|
1846
|
+
});
|
|
1847
|
+
},
|
|
1848
|
+
getCookie(name) {
|
|
1849
|
+
return parsedCookies.get(name);
|
|
1850
|
+
},
|
|
1851
|
+
setCookie(name, value, options) {
|
|
1852
|
+
meta.setCookies.push(formatSetCookie(name, value, options));
|
|
1853
|
+
},
|
|
1854
|
+
deleteCookie(name) {
|
|
1855
|
+
meta.setCookies.push(formatSetCookie(name, "", { maxAge: 0 }));
|
|
1856
|
+
},
|
|
1857
|
+
/**
|
|
1858
|
+
* 创建 SSE writer,用于流式推送事件
|
|
1859
|
+
*
|
|
1860
|
+
* handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response
|
|
1861
|
+
* 作为 HTTP 响应(Content-Type: text/event-stream)。
|
|
1862
|
+
*
|
|
1863
|
+
* 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
|
|
1864
|
+
*/
|
|
1865
|
+
sse() {
|
|
1866
|
+
const writer = createSseWriter();
|
|
1867
|
+
const ctxWithSse = ctx;
|
|
1868
|
+
ctxWithSse.__sseResponse = writer.response;
|
|
1869
|
+
ctxWithSse.__sseWriter = writer;
|
|
1870
|
+
return writer;
|
|
1754
1871
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1872
|
+
};
|
|
1873
|
+
const extend = config?.extendContext;
|
|
1874
|
+
if (typeof extend === "function") {
|
|
1875
|
+
extend(ctx);
|
|
1758
1876
|
}
|
|
1877
|
+
return ctx;
|
|
1759
1878
|
}
|
|
1760
1879
|
|
|
1761
|
-
// src/
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1880
|
+
// src/utils/isPlainObject.ts
|
|
1881
|
+
function isPlainObject(value) {
|
|
1882
|
+
if (value === null || typeof value !== "object") {
|
|
1883
|
+
return false;
|
|
1884
|
+
}
|
|
1885
|
+
if (Array.isArray(value)) {
|
|
1886
|
+
return false;
|
|
1767
1887
|
}
|
|
1768
|
-
const
|
|
1769
|
-
return
|
|
1888
|
+
const proto = Object.getPrototypeOf(value);
|
|
1889
|
+
return proto === null || proto === Object.prototype;
|
|
1770
1890
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
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;
|
|
1891
|
+
|
|
1892
|
+
// src/response/toResponse.ts
|
|
1893
|
+
async function toResponse(value, meta) {
|
|
1894
|
+
if (value instanceof Promise) {
|
|
1895
|
+
return toResponse(await value, meta);
|
|
1798
1896
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
for (const absMwPath of mwPaths) {
|
|
1804
|
-
let bundle = getCachedMiddlewares(absMwPath);
|
|
1805
|
-
if (bundle === void 0) {
|
|
1806
|
-
bundle = await loadMiddlewaresFile(absMwPath);
|
|
1807
|
-
setCachedMiddlewares(absMwPath, bundle);
|
|
1897
|
+
const applyMeta = (headers2) => {
|
|
1898
|
+
if (!meta) return;
|
|
1899
|
+
for (const [key, val] of Object.entries(meta.headers)) {
|
|
1900
|
+
headers2.set(key, val);
|
|
1808
1901
|
}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
mergedInjectors[name] = injector;
|
|
1902
|
+
for (const cookie of meta.setCookies ?? []) {
|
|
1903
|
+
headers2.append("set-cookie", cookie);
|
|
1812
1904
|
}
|
|
1813
|
-
}
|
|
1814
|
-
if (
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
const methods = [];
|
|
1823
|
-
for (const key of Object.keys(module)) {
|
|
1824
|
-
if (isHttpMethod(key) && typeof module[key] === "function") {
|
|
1825
|
-
methods.push(key);
|
|
1826
|
-
}
|
|
1905
|
+
};
|
|
1906
|
+
if (value instanceof Response) {
|
|
1907
|
+
if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
|
|
1908
|
+
const headers2 = new Headers(value.headers);
|
|
1909
|
+
applyMeta(headers2);
|
|
1910
|
+
return new Response(value.body, {
|
|
1911
|
+
status: meta.status ?? value.status,
|
|
1912
|
+
headers: headers2
|
|
1913
|
+
});
|
|
1827
1914
|
}
|
|
1828
|
-
return
|
|
1829
|
-
} catch (err) {
|
|
1830
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1831
|
-
console.warn(`[faapi] \u52A0\u8F7D\u8DEF\u7531\u6587\u4EF6\u5931\u8D25 ${absPath}: ${reason}`);
|
|
1832
|
-
return [];
|
|
1915
|
+
return value;
|
|
1833
1916
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
return
|
|
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;
|
|
1917
|
+
if (value === null || value === void 0) {
|
|
1918
|
+
const status = meta?.status ?? 204;
|
|
1919
|
+
const headers2 = new Headers();
|
|
1920
|
+
applyMeta(headers2);
|
|
1921
|
+
return new Response(null, { status, headers: headers2 });
|
|
1843
1922
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
const routes = [];
|
|
1852
|
-
const wsRoutes = [];
|
|
1853
|
-
for (const file of files) {
|
|
1854
|
-
const normalizedFile = file.replace(/\\/g, "/");
|
|
1855
|
-
const fileName = normalizedFile.split("/").pop();
|
|
1856
|
-
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
1857
|
-
const absPath = path4.resolve(rootDir, normalizedFile);
|
|
1858
|
-
const importPath = dist ? toProdAbsPath(absPath, rootDir, dist) : absPath;
|
|
1859
|
-
const urlPath = filePathToUrlPath(normalizedFile);
|
|
1860
|
-
const paramNames = extractParamNames(urlPath);
|
|
1861
|
-
const isDynamic = paramNames.length > 0;
|
|
1862
|
-
const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
|
|
1863
|
-
const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, dist);
|
|
1864
|
-
const methods = await extractMethodsFromHandler(importPath);
|
|
1865
|
-
for (const method of methods) {
|
|
1866
|
-
routes.push({
|
|
1867
|
-
method,
|
|
1868
|
-
urlPath,
|
|
1869
|
-
filePath: normalizedFile,
|
|
1870
|
-
paramNames,
|
|
1871
|
-
isDynamic,
|
|
1872
|
-
isCatchAll: isCatchAll || void 0,
|
|
1873
|
-
middlewares: middlewareBundle?.middlewares,
|
|
1874
|
-
injectors: middlewareBundle?.injectors
|
|
1875
|
-
});
|
|
1876
|
-
}
|
|
1877
|
-
const hasWs = await hasWsExport(importPath);
|
|
1878
|
-
if (hasWs) {
|
|
1879
|
-
wsRoutes.push({
|
|
1880
|
-
urlPath,
|
|
1881
|
-
filePath: normalizedFile,
|
|
1882
|
-
paramNames,
|
|
1883
|
-
isDynamic,
|
|
1884
|
-
isCatchAll: isCatchAll || void 0,
|
|
1885
|
-
middlewares: middlewareBundle?.middlewares,
|
|
1886
|
-
injectors: middlewareBundle?.injectors
|
|
1887
|
-
});
|
|
1888
|
-
}
|
|
1889
|
-
continue;
|
|
1890
|
-
}
|
|
1923
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1924
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1925
|
+
applyMeta(headers2);
|
|
1926
|
+
return new Response(value, {
|
|
1927
|
+
status: meta?.status ?? 200,
|
|
1928
|
+
headers: headers2
|
|
1929
|
+
});
|
|
1891
1930
|
}
|
|
1892
|
-
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
return a.isDynamic ? 1 : -1;
|
|
1900
|
-
}
|
|
1901
|
-
if (a.isCatchAll !== b.isCatchAll) {
|
|
1902
|
-
return a.isCatchAll ? 1 : -1;
|
|
1903
|
-
}
|
|
1904
|
-
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
1905
|
-
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
1906
|
-
if (aSegments !== bSegments) {
|
|
1907
|
-
return aSegments - bSegments;
|
|
1908
|
-
}
|
|
1909
|
-
return a.urlPath.localeCompare(b.urlPath);
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
// src/cli/generateSchemaFiles.ts
|
|
1914
|
-
import path5 from "path";
|
|
1915
|
-
import fs4 from "fs/promises";
|
|
1916
|
-
|
|
1917
|
-
// src/ast/generateZodSchema.ts
|
|
1918
|
-
var CodeGenContext = class {
|
|
1919
|
-
/** 命名类型集合:name → RuntimeType */
|
|
1920
|
-
namedTypes = /* @__PURE__ */ new Map();
|
|
1921
|
-
/** 类型解析器(用于解析 ref 的实际类型) */
|
|
1922
|
-
resolveType;
|
|
1923
|
-
/** 入口类型原始名(typeInfo.name,用于识别入口类型的自引用) */
|
|
1924
|
-
entryTypeName = "";
|
|
1925
|
-
/** 入口类型导出名(exportName,自引用时用此名生成变量名) */
|
|
1926
|
-
entryExportName = "";
|
|
1927
|
-
/**
|
|
1928
|
-
* 是否生成 coerce 逻辑(query/params 场景,URL 来源均为 string)
|
|
1929
|
-
*
|
|
1930
|
-
* true 时为 number/boolean 字段包 z.preprocess,把合法的字符串转成对应类型。
|
|
1931
|
-
* 嵌套类型(array/object/tuple/union 等)的元素递归处理。
|
|
1932
|
-
*/
|
|
1933
|
-
coerce = false;
|
|
1934
|
-
constructor(resolveType) {
|
|
1935
|
-
this.resolveType = resolveType;
|
|
1931
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
1932
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1933
|
+
applyMeta(headers2);
|
|
1934
|
+
return new Response(value, {
|
|
1935
|
+
status: meta?.status ?? 200,
|
|
1936
|
+
headers: headers2
|
|
1937
|
+
});
|
|
1936
1938
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
case "null":
|
|
1945
|
-
case "undefined":
|
|
1946
|
-
case "any":
|
|
1947
|
-
case "unknown":
|
|
1948
|
-
case "literal":
|
|
1949
|
-
case "date":
|
|
1950
|
-
return;
|
|
1951
|
-
case "array":
|
|
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
|
-
}
|
|
1939
|
+
if (value instanceof Uint8Array) {
|
|
1940
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1941
|
+
applyMeta(headers2);
|
|
1942
|
+
return new Response(value, {
|
|
1943
|
+
status: meta?.status ?? 200,
|
|
1944
|
+
headers: headers2
|
|
1945
|
+
});
|
|
1990
1946
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1947
|
+
if (isPlainObject(value) || Array.isArray(value)) {
|
|
1948
|
+
const body2 = JSON.stringify(value);
|
|
1949
|
+
const headers2 = new Headers({ "Content-Type": "application/json" });
|
|
1950
|
+
applyMeta(headers2);
|
|
1951
|
+
return new Response(body2, {
|
|
1952
|
+
status: meta?.status ?? 200,
|
|
1953
|
+
headers: headers2
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
if (typeof value === "string") {
|
|
1957
|
+
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
1958
|
+
applyMeta(headers2);
|
|
1959
|
+
return new Response(value, {
|
|
1960
|
+
status: meta?.status ?? 200,
|
|
1961
|
+
headers: headers2
|
|
1962
|
+
});
|
|
1997
1963
|
}
|
|
1998
|
-
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1964
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1965
|
+
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
1966
|
+
applyMeta(headers2);
|
|
1967
|
+
return new Response(String(value), {
|
|
1968
|
+
status: meta?.status ?? 200,
|
|
1969
|
+
headers: headers2
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
const body = JSON.stringify(value);
|
|
1973
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1974
|
+
applyMeta(headers);
|
|
1975
|
+
return new Response(body, {
|
|
1976
|
+
status: meta?.status ?? 200,
|
|
1977
|
+
headers
|
|
1978
|
+
});
|
|
2003
1979
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
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()";
|
|
1980
|
+
|
|
1981
|
+
// src/injection/injectParams.ts
|
|
1982
|
+
init_resolveInjection();
|
|
1983
|
+
|
|
1984
|
+
// src/utils/queryToObject.ts
|
|
1985
|
+
function queryToObject(params) {
|
|
1986
|
+
const result = {};
|
|
1987
|
+
for (const [key, value] of params) {
|
|
1988
|
+
result[key] = value;
|
|
2036
1989
|
}
|
|
1990
|
+
return result;
|
|
2037
1991
|
}
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
case "
|
|
2043
|
-
return
|
|
2044
|
-
case "
|
|
2045
|
-
return
|
|
2046
|
-
case "
|
|
2047
|
-
return
|
|
2048
|
-
case "
|
|
2049
|
-
return
|
|
2050
|
-
case "
|
|
2051
|
-
return
|
|
2052
|
-
case "
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
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`;
|
|
1992
|
+
|
|
1993
|
+
// src/injection/injectParams.ts
|
|
1994
|
+
function getBuiltinInjectionValue(type, ctx, body) {
|
|
1995
|
+
switch (type) {
|
|
1996
|
+
case "query":
|
|
1997
|
+
return queryToObject(ctx.query);
|
|
1998
|
+
case "params":
|
|
1999
|
+
return ctx.params;
|
|
2000
|
+
case "headers":
|
|
2001
|
+
return ctx.headers;
|
|
2002
|
+
case "context":
|
|
2003
|
+
return ctx;
|
|
2004
|
+
case "cookies":
|
|
2005
|
+
return ctx.cookies;
|
|
2006
|
+
case "ip":
|
|
2007
|
+
return ctx.ip;
|
|
2008
|
+
case "ua":
|
|
2009
|
+
return ctx.ua;
|
|
2010
|
+
case "body":
|
|
2011
|
+
return body;
|
|
2012
|
+
// form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)
|
|
2013
|
+
// 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)
|
|
2014
|
+
case "form":
|
|
2015
|
+
return body;
|
|
2016
|
+
case "files":
|
|
2017
|
+
if (body && typeof body === "object" && "files" in body) {
|
|
2018
|
+
return body.files;
|
|
2076
2019
|
}
|
|
2077
|
-
return
|
|
2020
|
+
return [];
|
|
2021
|
+
case "fields":
|
|
2022
|
+
if (body && typeof body === "object" && "fields" in body) {
|
|
2023
|
+
return body.fields;
|
|
2024
|
+
}
|
|
2025
|
+
return {};
|
|
2026
|
+
default:
|
|
2027
|
+
return void 0;
|
|
2078
2028
|
}
|
|
2079
2029
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
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})`;
|
|
2030
|
+
async function injectParamsAsync(handler, ctx, body, injectors) {
|
|
2031
|
+
const injections = resolveInjection(handler);
|
|
2032
|
+
if (injections.length === 0) {
|
|
2033
|
+
return await handler();
|
|
2101
2034
|
}
|
|
2102
|
-
|
|
2035
|
+
const args = await Promise.all(
|
|
2036
|
+
injections.map(async (injection) => {
|
|
2037
|
+
if (injection.type !== "unknown") {
|
|
2038
|
+
return getBuiltinInjectionValue(injection.type, ctx, body);
|
|
2039
|
+
}
|
|
2040
|
+
if (injectors && injection.name in injectors) {
|
|
2041
|
+
return await injectors[injection.name](ctx);
|
|
2042
|
+
}
|
|
2043
|
+
return void 0;
|
|
2044
|
+
})
|
|
2045
|
+
);
|
|
2046
|
+
return await handler(...args);
|
|
2103
2047
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
restStarted = true;
|
|
2113
|
-
} else if (!restStarted) {
|
|
2114
|
-
fixedExprs.push(runtimeTypeToZodExpression(el.type, ctx));
|
|
2115
|
-
fixedOptional.push(el.optional);
|
|
2116
|
-
}
|
|
2048
|
+
|
|
2049
|
+
// src/runtime/invokeHandler.ts
|
|
2050
|
+
function mergeMeta(response, meta) {
|
|
2051
|
+
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
2052
|
+
if (!hasMeta) return response;
|
|
2053
|
+
const headers = new Headers(response.headers);
|
|
2054
|
+
for (const [key, value] of Object.entries(meta.headers)) {
|
|
2055
|
+
headers.set(key, value);
|
|
2117
2056
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2057
|
+
for (const cookie of meta.setCookies) {
|
|
2058
|
+
headers.append("set-cookie", cookie);
|
|
2120
2059
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2060
|
+
return new Response(response.body, {
|
|
2061
|
+
status: meta.status ?? response.status,
|
|
2062
|
+
headers
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
async function compose(middlewares, ctx, finalHandler) {
|
|
2066
|
+
const meta = ctx.meta;
|
|
2067
|
+
let index = -1;
|
|
2068
|
+
async function dispatch(i) {
|
|
2069
|
+
if (i <= index) {
|
|
2070
|
+
throw new Error("next() called multiple times");
|
|
2071
|
+
}
|
|
2072
|
+
index = i;
|
|
2073
|
+
if (i >= middlewares.length) {
|
|
2074
|
+
return await finalHandler();
|
|
2075
|
+
}
|
|
2076
|
+
const mw = middlewares[i];
|
|
2077
|
+
let innerResponse;
|
|
2078
|
+
const next = async () => {
|
|
2079
|
+
innerResponse = await dispatch(i + 1);
|
|
2080
|
+
return innerResponse;
|
|
2081
|
+
};
|
|
2082
|
+
const result = await mw(ctx, next);
|
|
2083
|
+
if (result instanceof Response) {
|
|
2084
|
+
return mergeMeta(result, meta);
|
|
2085
|
+
}
|
|
2086
|
+
if (innerResponse !== void 0) {
|
|
2087
|
+
return innerResponse;
|
|
2088
|
+
}
|
|
2089
|
+
throw new Error("\u4E2D\u95F4\u4EF6\u5FC5\u987B await next() \u6216\u8FD4\u56DE Response");
|
|
2124
2090
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2091
|
+
return await dispatch(0);
|
|
2092
|
+
}
|
|
2093
|
+
async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
2094
|
+
const meta = ctx.meta;
|
|
2095
|
+
const pickSseAndAutoClose = () => {
|
|
2096
|
+
const sseWriter = ctx.__sseWriter;
|
|
2097
|
+
if (!sseWriter) return null;
|
|
2098
|
+
if (!sseWriter.closed && !sseWriter.aborted) {
|
|
2099
|
+
sseWriter.close();
|
|
2100
|
+
}
|
|
2101
|
+
return mergeMeta(sseWriter.response, meta);
|
|
2102
|
+
};
|
|
2103
|
+
const autoCloseSseOnError = () => {
|
|
2104
|
+
const sseWriter = ctx.__sseWriter;
|
|
2105
|
+
if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {
|
|
2106
|
+
sseWriter.close();
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
if (!middlewares || middlewares.length === 0) {
|
|
2110
|
+
try {
|
|
2111
|
+
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
2112
|
+
const sseResponse = pickSseAndAutoClose();
|
|
2113
|
+
if (sseResponse) return sseResponse;
|
|
2114
|
+
return toResponse(result, meta);
|
|
2115
|
+
} catch (err) {
|
|
2116
|
+
autoCloseSseOnError();
|
|
2117
|
+
throw err;
|
|
2130
2118
|
}
|
|
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
2119
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2120
|
+
const finalHandler = async () => {
|
|
2121
|
+
try {
|
|
2122
|
+
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
2123
|
+
const sseResponse = pickSseAndAutoClose();
|
|
2124
|
+
if (sseResponse) return sseResponse;
|
|
2125
|
+
return toResponse(result, meta);
|
|
2126
|
+
} catch (err) {
|
|
2127
|
+
autoCloseSseOnError();
|
|
2128
|
+
throw err;
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
return await compose(middlewares, ctx, finalHandler);
|
|
2147
2132
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2133
|
+
|
|
2134
|
+
// src/testServer.ts
|
|
2135
|
+
import path12 from "path";
|
|
2136
|
+
import os from "os";
|
|
2137
|
+
import fs9 from "fs/promises";
|
|
2138
|
+
|
|
2139
|
+
// src/router/scanRoutes.ts
|
|
2140
|
+
import fg from "fast-glob";
|
|
2141
|
+
import path4 from "path";
|
|
2142
|
+
import fs3 from "fs";
|
|
2143
|
+
|
|
2144
|
+
// src/router/constants.ts
|
|
2145
|
+
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
2146
|
+
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
2147
|
+
|
|
2148
|
+
// src/utils/normalizePath.ts
|
|
2149
|
+
function normalizePath(path15) {
|
|
2150
|
+
if (!path15) return "";
|
|
2151
|
+
let result = path15.replace(/\\/g, "/");
|
|
2152
|
+
result = result.replace(/\/+/g, "/");
|
|
2153
|
+
result = result.replace(/\/+$/, "");
|
|
2154
|
+
if (result && !result.startsWith("/")) {
|
|
2155
|
+
result = "/" + result;
|
|
2157
2156
|
}
|
|
2158
|
-
|
|
2159
|
-
return `z.union([${unionInner}])`;
|
|
2157
|
+
return result;
|
|
2160
2158
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2159
|
+
|
|
2160
|
+
// src/router/parseRouteFile.ts
|
|
2161
|
+
function dynamicSegmentToParam(segment) {
|
|
2162
|
+
const match = segment.match(/^\[(.+)\]$/);
|
|
2163
|
+
if (match) {
|
|
2164
|
+
return ":" + match[1];
|
|
2166
2165
|
}
|
|
2167
|
-
return
|
|
2166
|
+
return segment;
|
|
2168
2167
|
}
|
|
2169
|
-
function
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
return type.properties.some((prop) => containsRef(prop.type, visited));
|
|
2179
|
-
case "union":
|
|
2180
|
-
return type.members.some((m) => containsRef(m, visited));
|
|
2181
|
-
case "record":
|
|
2182
|
-
return containsRef(type.key, visited) || containsRef(type.value, visited);
|
|
2183
|
-
case "map":
|
|
2184
|
-
return containsRef(type.key, visited) || containsRef(type.value, visited);
|
|
2185
|
-
case "set":
|
|
2186
|
-
return containsRef(type.element, visited);
|
|
2187
|
-
default:
|
|
2188
|
-
return false;
|
|
2168
|
+
function extractParamNames(urlPath) {
|
|
2169
|
+
const params = [];
|
|
2170
|
+
const segments = urlPath.split("/");
|
|
2171
|
+
for (const segment of segments) {
|
|
2172
|
+
if (segment.startsWith(":...")) {
|
|
2173
|
+
params.push(segment.slice(4));
|
|
2174
|
+
} else if (segment.startsWith(":")) {
|
|
2175
|
+
params.push(segment.slice(1));
|
|
2176
|
+
}
|
|
2189
2177
|
}
|
|
2178
|
+
return params;
|
|
2190
2179
|
}
|
|
2191
|
-
function
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
const
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
lines.push(generateNamedTypeDeclaration(n, type, ctx));
|
|
2204
|
-
}
|
|
2205
|
-
if (ctx.namedTypes.size > 0) lines.push("");
|
|
2206
|
-
const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);
|
|
2207
|
-
const hasSelfRef = containsRef(typeInfo.runtimeType, /* @__PURE__ */ new Set([typeInfo.name]));
|
|
2208
|
-
if (hasSelfRef) {
|
|
2209
|
-
lines.push(`export const ${name}Schema = z.lazy(() => ${entryExpr});`);
|
|
2210
|
-
} else {
|
|
2211
|
-
lines.push(`export const ${name}Schema = ${entryExpr};`);
|
|
2180
|
+
function isCatchAllSegment(segment) {
|
|
2181
|
+
return /^\[\.\.\..+\]$/.test(segment);
|
|
2182
|
+
}
|
|
2183
|
+
function isRouteGroup(segment) {
|
|
2184
|
+
return /^\(.+\)$/.test(segment);
|
|
2185
|
+
}
|
|
2186
|
+
function filePathToUrlPath(filePath) {
|
|
2187
|
+
const withoutPrefix = filePath.startsWith("src/") ? filePath.slice(4) : filePath;
|
|
2188
|
+
const lastSlashIndex = withoutPrefix.lastIndexOf("/");
|
|
2189
|
+
const dirPath = lastSlashIndex === -1 ? "" : withoutPrefix.slice(0, lastSlashIndex);
|
|
2190
|
+
if (!dirPath) {
|
|
2191
|
+
return "";
|
|
2212
2192
|
}
|
|
2213
|
-
|
|
2193
|
+
const segments = dirPath.split("/").filter((s) => !isRouteGroup(s)).map(dynamicSegmentToParam);
|
|
2194
|
+
return normalizePath(segments.join("/"));
|
|
2214
2195
|
}
|
|
2215
2196
|
|
|
2216
|
-
// src/
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
rel = rel.slice(4);
|
|
2221
|
-
}
|
|
2222
|
-
const idx = rel.lastIndexOf("/");
|
|
2223
|
-
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2224
|
-
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
2197
|
+
// src/middleware/loadMiddlewares.ts
|
|
2198
|
+
var middlewareCache = /* @__PURE__ */ new Map();
|
|
2199
|
+
function invalidateMiddlewareCache() {
|
|
2200
|
+
middlewareCache.clear();
|
|
2225
2201
|
}
|
|
2226
|
-
function
|
|
2227
|
-
|
|
2228
|
-
if (rel.startsWith("src/")) {
|
|
2229
|
-
rel = rel.slice(4);
|
|
2230
|
-
} else if (rel.startsWith(`${dist}/`)) {
|
|
2231
|
-
rel = rel.slice(dist.length + 1);
|
|
2232
|
-
}
|
|
2233
|
-
const idx = rel.lastIndexOf("/");
|
|
2234
|
-
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2235
|
-
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
2202
|
+
function getCachedMiddlewares(absPath) {
|
|
2203
|
+
return middlewareCache.get(absPath);
|
|
2236
2204
|
}
|
|
2237
|
-
function
|
|
2238
|
-
|
|
2239
|
-
const depth = relDir.split("/").filter(Boolean).length;
|
|
2240
|
-
return `${"../".repeat(depth)}${HELPERS_FILENAME}`;
|
|
2205
|
+
function setCachedMiddlewares(absPath, bundle) {
|
|
2206
|
+
middlewareCache.set(absPath, bundle);
|
|
2241
2207
|
}
|
|
2242
|
-
function
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
continue;
|
|
2208
|
+
async function loadMiddlewaresFile(filePath) {
|
|
2209
|
+
try {
|
|
2210
|
+
const module = await importWithCacheBust(filePath);
|
|
2211
|
+
const middlewares = module.default ?? module.middlewares ?? [];
|
|
2212
|
+
if (!Array.isArray(middlewares)) {
|
|
2213
|
+
console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
2214
|
+
return { middlewares: [], injectors: {} };
|
|
2250
2215
|
}
|
|
2251
|
-
const
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2216
|
+
const validMiddlewares = middlewares.filter((m) => {
|
|
2217
|
+
if (typeof m !== "function") {
|
|
2218
|
+
console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
|
|
2219
|
+
return false;
|
|
2220
|
+
}
|
|
2221
|
+
return true;
|
|
2222
|
+
});
|
|
2223
|
+
const injectors = module.injectors ?? {};
|
|
2224
|
+
if (typeof injectors !== "object" || injectors === null) {
|
|
2225
|
+
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
2226
|
+
return { middlewares: validMiddlewares, injectors: {} };
|
|
2227
|
+
}
|
|
2228
|
+
const validInjectors = {};
|
|
2229
|
+
for (const [name, injector] of Object.entries(injectors)) {
|
|
2230
|
+
if (typeof injector !== "function") {
|
|
2231
|
+
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
2232
|
+
continue;
|
|
2233
|
+
}
|
|
2234
|
+
validInjectors[name] = injector;
|
|
2235
|
+
}
|
|
2236
|
+
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
2237
|
+
} catch {
|
|
2238
|
+
return { middlewares: [], injectors: {} };
|
|
2260
2239
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2240
|
+
}
|
|
2241
|
+
async function loadMergedMiddlewares(middlewarePaths) {
|
|
2242
|
+
if (middlewarePaths.length === 0) return void 0;
|
|
2243
|
+
const mergedMiddlewares = [];
|
|
2244
|
+
const mergedInjectors = {};
|
|
2245
|
+
for (const absMwPath of middlewarePaths) {
|
|
2246
|
+
let bundle = getCachedMiddlewares(absMwPath);
|
|
2247
|
+
if (bundle === void 0) {
|
|
2248
|
+
bundle = await loadMiddlewaresFile(absMwPath);
|
|
2249
|
+
setCachedMiddlewares(absMwPath, bundle);
|
|
2250
|
+
}
|
|
2251
|
+
mergedMiddlewares.push(...bundle.middlewares);
|
|
2252
|
+
for (const [name, injector] of Object.entries(bundle.injectors)) {
|
|
2253
|
+
mergedInjectors[name] = injector;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
if (mergedMiddlewares.length === 0 && Object.keys(mergedInjectors).length === 0) {
|
|
2257
|
+
return void 0;
|
|
2258
|
+
}
|
|
2259
|
+
return { middlewares: mergedMiddlewares, injectors: mergedInjectors };
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
// src/router/scanRoutes.ts
|
|
2263
|
+
var HTTP_OR_WS_EXPORT_RE = new RegExp(
|
|
2264
|
+
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|WS)\b`,
|
|
2265
|
+
"g"
|
|
2266
|
+
);
|
|
2267
|
+
function extractExportsFromSource(source) {
|
|
2268
|
+
const names = /* @__PURE__ */ new Set();
|
|
2269
|
+
let match;
|
|
2270
|
+
HTTP_OR_WS_EXPORT_RE.lastIndex = 0;
|
|
2271
|
+
while ((match = HTTP_OR_WS_EXPORT_RE.exec(source)) !== null) {
|
|
2272
|
+
names.add(match[1]);
|
|
2273
|
+
}
|
|
2274
|
+
return names;
|
|
2275
|
+
}
|
|
2276
|
+
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
2277
|
+
const routeDir = path4.dirname(routeFilePath);
|
|
2278
|
+
const resolvedRoot = path4.resolve(rootDir);
|
|
2279
|
+
const paths = [];
|
|
2280
|
+
let currentDir = path4.resolve(rootDir, routeDir);
|
|
2281
|
+
while (true) {
|
|
2282
|
+
if (dist) {
|
|
2283
|
+
const mwTsPath = path4.join(currentDir, "middlewares.ts");
|
|
2284
|
+
const mwJsPath = path4.join(currentDir, "middlewares.js");
|
|
2285
|
+
const absTsPath = path4.resolve(rootDir, mwTsPath);
|
|
2286
|
+
const absJsPath = path4.resolve(rootDir, mwJsPath);
|
|
2287
|
+
const absMwPath = fs3.existsSync(absTsPath) ? absTsPath : fs3.existsSync(absJsPath) ? absJsPath : null;
|
|
2288
|
+
if (absMwPath) {
|
|
2289
|
+
const relMwPath = path4.relative(rootDir, absMwPath);
|
|
2290
|
+
const prodAbsPath = path4.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
2291
|
+
paths.push(prodAbsPath);
|
|
2292
|
+
}
|
|
2293
|
+
} else {
|
|
2294
|
+
for (const ext of [".ts", ".js"]) {
|
|
2295
|
+
const mwPath = path4.join(currentDir, `middlewares${ext}`);
|
|
2296
|
+
const absMwPath = path4.resolve(rootDir, mwPath);
|
|
2297
|
+
if (fs3.existsSync(absMwPath)) {
|
|
2298
|
+
paths.push(absMwPath);
|
|
2299
|
+
break;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
if (currentDir === resolvedRoot) break;
|
|
2304
|
+
const parentDir = path4.dirname(currentDir);
|
|
2305
|
+
if (parentDir === currentDir) break;
|
|
2306
|
+
currentDir = parentDir;
|
|
2266
2307
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
return lines.join("\n").replace(/\n+$/, "\n");
|
|
2308
|
+
paths.reverse();
|
|
2309
|
+
return paths;
|
|
2270
2310
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
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);
|
|
2311
|
+
function toProdFilePath(filePath, dist) {
|
|
2312
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
2313
|
+
if (rel.startsWith("src/")) {
|
|
2314
|
+
rel = rel.slice(4);
|
|
2282
2315
|
}
|
|
2283
|
-
const
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2316
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
2317
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
2318
|
+
}
|
|
2319
|
+
async function scanRoutes(rootDir, patterns, dist) {
|
|
2320
|
+
const files = await fg(patterns, {
|
|
2321
|
+
cwd: rootDir,
|
|
2322
|
+
onlyFiles: true,
|
|
2323
|
+
absolute: false
|
|
2324
|
+
});
|
|
2325
|
+
const routes = [];
|
|
2326
|
+
const wsRoutes = [];
|
|
2327
|
+
for (const file of files) {
|
|
2328
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
2329
|
+
const fileName = normalizedFile.split("/").pop();
|
|
2330
|
+
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
2331
|
+
const absPath = path4.resolve(rootDir, normalizedFile);
|
|
2332
|
+
const urlPath = filePathToUrlPath(normalizedFile);
|
|
2333
|
+
const paramNames = extractParamNames(urlPath);
|
|
2334
|
+
const isDynamic = paramNames.length > 0;
|
|
2335
|
+
const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
|
|
2336
|
+
let middlewarePaths;
|
|
2337
|
+
let middlewareBundle;
|
|
2338
|
+
if (dist) {
|
|
2339
|
+
middlewarePaths = collectMiddlewarePaths(normalizedFile, rootDir, dist);
|
|
2340
|
+
} else {
|
|
2341
|
+
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
2342
|
+
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
2343
|
+
}
|
|
2344
|
+
const source = await fs3.promises.readFile(absPath, "utf8").catch(() => "");
|
|
2345
|
+
const exportNames = extractExportsFromSource(source);
|
|
2346
|
+
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
2347
|
+
for (const method of methods) {
|
|
2348
|
+
routes.push({
|
|
2349
|
+
method,
|
|
2350
|
+
urlPath,
|
|
2351
|
+
filePath: normalizedFile,
|
|
2352
|
+
paramNames,
|
|
2353
|
+
isDynamic,
|
|
2354
|
+
isCatchAll: isCatchAll || void 0,
|
|
2355
|
+
middlewarePaths,
|
|
2356
|
+
middlewares: middlewareBundle?.middlewares,
|
|
2357
|
+
injectors: middlewareBundle?.injectors
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
if (exportNames.has("WS")) {
|
|
2361
|
+
wsRoutes.push({
|
|
2362
|
+
urlPath,
|
|
2363
|
+
filePath: normalizedFile,
|
|
2364
|
+
paramNames,
|
|
2365
|
+
isDynamic,
|
|
2366
|
+
isCatchAll: isCatchAll || void 0,
|
|
2367
|
+
middlewarePaths,
|
|
2368
|
+
middlewares: middlewareBundle?.middlewares,
|
|
2369
|
+
injectors: middlewareBundle?.injectors
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
continue;
|
|
2291
2373
|
}
|
|
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
|
-
}
|
|
2298
|
-
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2299
|
-
if (usesCoerceHelpers(allSourceCode)) {
|
|
2300
|
-
const helpersPath = path5.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
2301
|
-
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
2302
2374
|
}
|
|
2303
|
-
|
|
2304
|
-
fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source))
|
|
2305
|
-
);
|
|
2375
|
+
return { routes, wsRoutes };
|
|
2306
2376
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2377
|
+
|
|
2378
|
+
// src/router/sortRoutes.ts
|
|
2379
|
+
function sortRoutes(routes) {
|
|
2380
|
+
return [...routes].sort((a, b) => {
|
|
2381
|
+
if (a.isDynamic !== b.isDynamic) {
|
|
2382
|
+
return a.isDynamic ? 1 : -1;
|
|
2383
|
+
}
|
|
2384
|
+
if (a.isCatchAll !== b.isCatchAll) {
|
|
2385
|
+
return a.isCatchAll ? 1 : -1;
|
|
2386
|
+
}
|
|
2387
|
+
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
2388
|
+
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
2389
|
+
if (aSegments !== bSegments) {
|
|
2390
|
+
return aSegments - bSegments;
|
|
2391
|
+
}
|
|
2392
|
+
return a.urlPath.localeCompare(b.urlPath);
|
|
2393
|
+
});
|
|
2310
2394
|
}
|
|
2311
2395
|
|
|
2396
|
+
// src/testServer.ts
|
|
2397
|
+
init_generateSchemaFiles();
|
|
2398
|
+
|
|
2312
2399
|
// src/validator/validateInput.ts
|
|
2400
|
+
init_schemaName();
|
|
2313
2401
|
var moduleCache = /* @__PURE__ */ new Map();
|
|
2314
2402
|
function invalidateSchemaCache() {
|
|
2315
2403
|
moduleCache.clear();
|
|
@@ -2353,9 +2441,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
2353
2441
|
function mapZodIssues(error) {
|
|
2354
2442
|
return error.issues.map((issue) => {
|
|
2355
2443
|
const code = mapZodCode(issue.code, issue.message);
|
|
2356
|
-
const
|
|
2444
|
+
const path15 = issue.path.map(String).join(".") || "";
|
|
2357
2445
|
return {
|
|
2358
|
-
path:
|
|
2446
|
+
path: path15,
|
|
2359
2447
|
code,
|
|
2360
2448
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
2361
2449
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -2403,45 +2491,45 @@ import {
|
|
|
2403
2491
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
2404
2492
|
import { readFileSync } from "fs";
|
|
2405
2493
|
import { Readable as Readable2 } from "stream";
|
|
2406
|
-
import
|
|
2494
|
+
import path11 from "path";
|
|
2407
2495
|
|
|
2408
2496
|
// src/router/matchRoute.ts
|
|
2409
|
-
function matchRoute(routes, method,
|
|
2497
|
+
function matchRoute(routes, method, path15) {
|
|
2410
2498
|
for (const route of routes) {
|
|
2411
2499
|
if (route.method !== method) {
|
|
2412
2500
|
continue;
|
|
2413
2501
|
}
|
|
2414
2502
|
if (!route.isDynamic) {
|
|
2415
|
-
if (route.urlPath ===
|
|
2503
|
+
if (route.urlPath === path15) {
|
|
2416
2504
|
return { route, params: {} };
|
|
2417
2505
|
}
|
|
2418
2506
|
continue;
|
|
2419
2507
|
}
|
|
2420
|
-
const params = matchDynamicPath(route.urlPath,
|
|
2508
|
+
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2421
2509
|
if (params !== null) {
|
|
2422
2510
|
return { route, params };
|
|
2423
2511
|
}
|
|
2424
2512
|
}
|
|
2425
2513
|
return null;
|
|
2426
2514
|
}
|
|
2427
|
-
function matchWsRoute(wsRoutes,
|
|
2515
|
+
function matchWsRoute(wsRoutes, path15) {
|
|
2428
2516
|
for (const route of wsRoutes) {
|
|
2429
2517
|
if (!route.isDynamic) {
|
|
2430
|
-
if (route.urlPath ===
|
|
2518
|
+
if (route.urlPath === path15) {
|
|
2431
2519
|
return { route, params: {} };
|
|
2432
2520
|
}
|
|
2433
2521
|
continue;
|
|
2434
2522
|
}
|
|
2435
|
-
const params = matchDynamicPath(route.urlPath,
|
|
2523
|
+
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2436
2524
|
if (params !== null) {
|
|
2437
2525
|
return { route, params };
|
|
2438
2526
|
}
|
|
2439
2527
|
}
|
|
2440
2528
|
return null;
|
|
2441
2529
|
}
|
|
2442
|
-
function matchDynamicPath(pattern,
|
|
2530
|
+
function matchDynamicPath(pattern, path15, paramNames, isCatchAll) {
|
|
2443
2531
|
const patternSegments = pattern.split("/").filter(Boolean);
|
|
2444
|
-
const pathSegments =
|
|
2532
|
+
const pathSegments = path15.split("/").filter(Boolean);
|
|
2445
2533
|
if (isCatchAll) {
|
|
2446
2534
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
2447
2535
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -2510,12 +2598,379 @@ function validateRouteModule(value, method, filePath) {
|
|
|
2510
2598
|
}
|
|
2511
2599
|
}
|
|
2512
2600
|
|
|
2601
|
+
// src/cli/compileOnDemand.ts
|
|
2602
|
+
import path9 from "path";
|
|
2603
|
+
import fs8 from "fs";
|
|
2604
|
+
|
|
2605
|
+
// src/cli/compileDevRoutes.ts
|
|
2606
|
+
import path8 from "path";
|
|
2607
|
+
import fs7 from "fs";
|
|
2608
|
+
import fg2 from "fast-glob";
|
|
2609
|
+
|
|
2610
|
+
// src/cli/aliasPlugin.ts
|
|
2611
|
+
import path7 from "path";
|
|
2612
|
+
import fs6 from "fs";
|
|
2613
|
+
|
|
2614
|
+
// src/utils/resolveAlias.ts
|
|
2615
|
+
function resolveAlias(specifier, config) {
|
|
2616
|
+
const candidates = [];
|
|
2617
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
2618
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
2619
|
+
if (wildcardIndex === -1) {
|
|
2620
|
+
if (specifier === pattern) {
|
|
2621
|
+
candidates.push(...targets);
|
|
2622
|
+
}
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
2626
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
2627
|
+
if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
|
|
2628
|
+
const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
2629
|
+
for (const target of targets) {
|
|
2630
|
+
candidates.push(target.replace("*", captured));
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
return candidates;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
// src/utils/readTsconfig.ts
|
|
2638
|
+
import ts6 from "typescript";
|
|
2639
|
+
import path6 from "path";
|
|
2640
|
+
import fs5 from "fs";
|
|
2641
|
+
function readTsconfig(rootDir) {
|
|
2642
|
+
const tsconfigPath = path6.resolve(rootDir, "tsconfig.json");
|
|
2643
|
+
if (!fs5.existsSync(tsconfigPath)) return null;
|
|
2644
|
+
const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
|
|
2645
|
+
if (configFile.error || !configFile.config) return null;
|
|
2646
|
+
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
2647
|
+
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
2648
|
+
const rawPaths = parsed.options.paths;
|
|
2649
|
+
if (!rawPaths) return null;
|
|
2650
|
+
const paths = {};
|
|
2651
|
+
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
2652
|
+
paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
|
|
2653
|
+
}
|
|
2654
|
+
return { baseUrl, paths };
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// src/cli/aliasPlugin.ts
|
|
2658
|
+
function toProdExtension(filePath) {
|
|
2659
|
+
if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
|
|
2660
|
+
if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
|
|
2661
|
+
if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
|
|
2662
|
+
return filePath;
|
|
2663
|
+
}
|
|
2664
|
+
function toProdImportPath(sourceFile, importer) {
|
|
2665
|
+
const importerDir = path7.dirname(importer);
|
|
2666
|
+
let rel = path7.relative(importerDir, sourceFile);
|
|
2667
|
+
rel = rel.split(path7.sep).join("/");
|
|
2668
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2669
|
+
return toProdExtension(rel);
|
|
2670
|
+
}
|
|
2671
|
+
function toRealPath(p) {
|
|
2672
|
+
try {
|
|
2673
|
+
return fs6.realpathSync(p);
|
|
2674
|
+
} catch {
|
|
2675
|
+
return p;
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
function isInsideDir(filePath, dir) {
|
|
2679
|
+
const rel = path7.relative(dir, filePath);
|
|
2680
|
+
return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
2681
|
+
}
|
|
2682
|
+
var APP_DIR = "src";
|
|
2683
|
+
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
2684
|
+
const appDirAbs = toRealPath(path7.resolve(rootDir, APP_DIR));
|
|
2685
|
+
const sourceReal = toRealPath(sourceFile);
|
|
2686
|
+
let rel = path7.relative(appDirAbs, sourceReal);
|
|
2687
|
+
rel = rel.split(path7.sep).join("/");
|
|
2688
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2689
|
+
return toProdExtension(rel);
|
|
2690
|
+
}
|
|
2691
|
+
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
2692
|
+
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2693
|
+
var INDEX_EXTS = [
|
|
2694
|
+
"/index.ts",
|
|
2695
|
+
"/index.tsx",
|
|
2696
|
+
"/index.js",
|
|
2697
|
+
"/index.jsx",
|
|
2698
|
+
"/index.mjs",
|
|
2699
|
+
"/index.cjs"
|
|
2700
|
+
];
|
|
2701
|
+
function resolveRelativeSpecifier(importer, specifier) {
|
|
2702
|
+
const importerDir = path7.dirname(importer);
|
|
2703
|
+
const base = path7.resolve(importerDir, specifier);
|
|
2704
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2705
|
+
return fs6.existsSync(base) ? base : null;
|
|
2706
|
+
}
|
|
2707
|
+
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
2708
|
+
return fs6.existsSync(base) ? base : null;
|
|
2709
|
+
}
|
|
2710
|
+
for (const ext of SOURCE_EXTS) {
|
|
2711
|
+
const file = base + ext;
|
|
2712
|
+
if (fs6.existsSync(file)) return file;
|
|
2713
|
+
}
|
|
2714
|
+
for (const indexExt of INDEX_EXTS) {
|
|
2715
|
+
const file = base + indexExt;
|
|
2716
|
+
if (fs6.existsSync(file)) return file;
|
|
2717
|
+
}
|
|
2718
|
+
return null;
|
|
2719
|
+
}
|
|
2720
|
+
function createAliasPlugin(config, options) {
|
|
2721
|
+
const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
2722
|
+
const appDirAbs = options?.rootDir ? toRealPath(path7.resolve(options.rootDir, APP_DIR)) : null;
|
|
2723
|
+
return {
|
|
2724
|
+
name: "faapi-alias",
|
|
2725
|
+
setup(build) {
|
|
2726
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
2727
|
+
let source;
|
|
2728
|
+
try {
|
|
2729
|
+
source = fs6.readFileSync(args.path, "utf8");
|
|
2730
|
+
} catch {
|
|
2731
|
+
return void 0;
|
|
2732
|
+
}
|
|
2733
|
+
const importer = args.path;
|
|
2734
|
+
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
2735
|
+
let modified = false;
|
|
2736
|
+
const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
|
|
2737
|
+
if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
|
|
2738
|
+
return full;
|
|
2739
|
+
}
|
|
2740
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2741
|
+
const resolved = resolveRelativeSpecifier(importer, specifier);
|
|
2742
|
+
if (resolved) {
|
|
2743
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2744
|
+
return full;
|
|
2745
|
+
}
|
|
2746
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
2747
|
+
modified = true;
|
|
2748
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2749
|
+
resolved,
|
|
2750
|
+
options.rootDir
|
|
2751
|
+
)}${quote}`;
|
|
2752
|
+
}
|
|
2753
|
+
modified = true;
|
|
2754
|
+
return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
|
|
2755
|
+
}
|
|
2756
|
+
return full;
|
|
2757
|
+
}
|
|
2758
|
+
const candidates = resolveAlias(specifier, config);
|
|
2759
|
+
for (const candidate of candidates) {
|
|
2760
|
+
for (const ext of SOURCE_EXTS) {
|
|
2761
|
+
const file = candidate + ext;
|
|
2762
|
+
if (fs6.existsSync(file)) {
|
|
2763
|
+
modified = true;
|
|
2764
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2765
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2766
|
+
file,
|
|
2767
|
+
options.rootDir
|
|
2768
|
+
)}${quote}`;
|
|
2769
|
+
}
|
|
2770
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
for (const indexExt of INDEX_EXTS) {
|
|
2774
|
+
const file = candidate + indexExt;
|
|
2775
|
+
if (fs6.existsSync(file)) {
|
|
2776
|
+
modified = true;
|
|
2777
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2778
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2779
|
+
file,
|
|
2780
|
+
options.rootDir
|
|
2781
|
+
)}${quote}`;
|
|
2782
|
+
}
|
|
2783
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
return full;
|
|
2788
|
+
});
|
|
2789
|
+
if (!modified) return void 0;
|
|
2790
|
+
return { contents: newSource, loader: "default" };
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
function buildAliasPlugins(rootDir) {
|
|
2796
|
+
const tsconfig = readTsconfig(rootDir);
|
|
2797
|
+
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
// src/cli/compileDevRoutes.ts
|
|
2801
|
+
var APP_DIR2 = "src";
|
|
2802
|
+
async function compileDevRoutes(options) {
|
|
2803
|
+
const { rootDir, dist, files, logLevel = "silent" } = options;
|
|
2804
|
+
const entryPoints = files ?? await fg2([`${APP_DIR2}/**/*.ts`], {
|
|
2805
|
+
cwd: rootDir,
|
|
2806
|
+
onlyFiles: true,
|
|
2807
|
+
absolute: true,
|
|
2808
|
+
ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
|
|
2809
|
+
});
|
|
2810
|
+
if (entryPoints.length === 0) {
|
|
2811
|
+
return { compiledFiles: [] };
|
|
2812
|
+
}
|
|
2813
|
+
const absDist = path8.resolve(rootDir, dist);
|
|
2814
|
+
await fs7.promises.mkdir(absDist, { recursive: true });
|
|
2815
|
+
const plugins = buildAliasPlugins(rootDir);
|
|
2816
|
+
const esbuild = await import("esbuild");
|
|
2817
|
+
const outbase = path8.resolve(rootDir, APP_DIR2);
|
|
2818
|
+
const result = await esbuild.build({
|
|
2819
|
+
entryPoints,
|
|
2820
|
+
outdir: absDist,
|
|
2821
|
+
outbase,
|
|
2822
|
+
bundle: false,
|
|
2823
|
+
platform: "node",
|
|
2824
|
+
format: "esm",
|
|
2825
|
+
sourcemap: true,
|
|
2826
|
+
packages: "external",
|
|
2827
|
+
plugins,
|
|
2828
|
+
logLevel,
|
|
2829
|
+
write: false
|
|
2830
|
+
});
|
|
2831
|
+
if (result.outputFiles) {
|
|
2832
|
+
await Promise.all(
|
|
2833
|
+
result.outputFiles.map(async (file) => {
|
|
2834
|
+
await fs7.promises.mkdir(path8.dirname(file.path), { recursive: true });
|
|
2835
|
+
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2836
|
+
await fs7.promises.writeFile(tmp, file.contents);
|
|
2837
|
+
await fs7.promises.rename(tmp, file.path);
|
|
2838
|
+
})
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
return { compiledFiles: entryPoints };
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
// src/cli/compileOnDemand.ts
|
|
2845
|
+
init_generateSchemaFiles();
|
|
2846
|
+
init_generateSchemaFiles();
|
|
2847
|
+
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
2848
|
+
try {
|
|
2849
|
+
const srcStat = fs8.statSync(sourceAbsPath);
|
|
2850
|
+
const prodStat = fs8.statSync(productAbsPath);
|
|
2851
|
+
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
2852
|
+
} catch {
|
|
2853
|
+
return false;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
var compiledFiles = /* @__PURE__ */ new Set();
|
|
2857
|
+
function clearCompiledFiles() {
|
|
2858
|
+
compiledFiles.clear();
|
|
2859
|
+
}
|
|
2860
|
+
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
2861
|
+
if (compiledFiles.has(sourceAbsPath)) {
|
|
2862
|
+
return false;
|
|
2863
|
+
}
|
|
2864
|
+
if (!fs8.existsSync(sourceAbsPath)) {
|
|
2865
|
+
return false;
|
|
2866
|
+
}
|
|
2867
|
+
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
2868
|
+
if (productPath && isProductFresh(sourceAbsPath, productPath)) {
|
|
2869
|
+
compiledFiles.add(sourceAbsPath);
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2872
|
+
await compileDevRoutes({
|
|
2873
|
+
rootDir,
|
|
2874
|
+
dist,
|
|
2875
|
+
files: [sourceAbsPath],
|
|
2876
|
+
logLevel: "silent"
|
|
2877
|
+
});
|
|
2878
|
+
compiledFiles.add(sourceAbsPath);
|
|
2879
|
+
return true;
|
|
2880
|
+
}
|
|
2881
|
+
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2882
|
+
const rel = path9.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2883
|
+
if (!rel.startsWith("src/")) return null;
|
|
2884
|
+
const relWithoutSrc = rel.slice(4);
|
|
2885
|
+
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2886
|
+
return path9.resolve(rootDir, dist, jsRel);
|
|
2887
|
+
}
|
|
2888
|
+
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
2889
|
+
function clearGeneratedSchemas() {
|
|
2890
|
+
generatedSchemas.clear();
|
|
2891
|
+
}
|
|
2892
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
2893
|
+
if (generatedSchemas.has(schemaPath)) {
|
|
2894
|
+
return false;
|
|
2895
|
+
}
|
|
2896
|
+
const prodAbsPath = path9.resolve(rootDir, routeFilePath);
|
|
2897
|
+
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
2898
|
+
if (!fs8.existsSync(sourceAbsPath)) {
|
|
2899
|
+
return false;
|
|
2900
|
+
}
|
|
2901
|
+
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
2902
|
+
generatedSchemas.add(schemaPath);
|
|
2903
|
+
return false;
|
|
2904
|
+
}
|
|
2905
|
+
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
2906
|
+
if (fileRoutes.length === 0) {
|
|
2907
|
+
return false;
|
|
2908
|
+
}
|
|
2909
|
+
const sourceRelPath = path9.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2910
|
+
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2911
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
2912
|
+
generatedSchemas.add(schemaPath);
|
|
2913
|
+
return true;
|
|
2914
|
+
}
|
|
2915
|
+
async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
2916
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
2917
|
+
for (const route of routes) {
|
|
2918
|
+
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
2919
|
+
if (deleted.has(schemaPath)) continue;
|
|
2920
|
+
deleted.add(schemaPath);
|
|
2921
|
+
try {
|
|
2922
|
+
await fs8.promises.unlink(schemaPath);
|
|
2923
|
+
} catch {
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2928
|
+
const rel = path9.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
2929
|
+
let relWithoutDist = rel;
|
|
2930
|
+
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
2931
|
+
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
2932
|
+
}
|
|
2933
|
+
const srcRel = `src/${relWithoutDist}`;
|
|
2934
|
+
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2935
|
+
const tsAbs = path9.resolve(rootDir, tsRel);
|
|
2936
|
+
if (fs8.existsSync(tsAbs)) return tsAbs;
|
|
2937
|
+
return path9.resolve(rootDir, srcRel);
|
|
2938
|
+
}
|
|
2939
|
+
var devOnDemandEnabled = false;
|
|
2940
|
+
function isDevOnDemandEnabled() {
|
|
2941
|
+
return devOnDemandEnabled;
|
|
2942
|
+
}
|
|
2943
|
+
var devDistDir;
|
|
2944
|
+
function getDevDist() {
|
|
2945
|
+
return devDistDir;
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2513
2948
|
// src/loader/loadRouteModule.ts
|
|
2514
|
-
async function loadRouteModule(filePath, method) {
|
|
2949
|
+
async function loadRouteModule(filePath, method, rootDir) {
|
|
2515
2950
|
let module;
|
|
2516
2951
|
try {
|
|
2517
2952
|
module = await importWithCacheBust(filePath);
|
|
2518
2953
|
} catch (err) {
|
|
2954
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
2955
|
+
const dist = getDevDist();
|
|
2956
|
+
if (dist) {
|
|
2957
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2958
|
+
try {
|
|
2959
|
+
const compiled = await ensureCompiled(sourcePath, rootDir, dist);
|
|
2960
|
+
if (compiled) {
|
|
2961
|
+
module = await importWithCacheBust(filePath);
|
|
2962
|
+
const handler2 = resolveExport(module, method);
|
|
2963
|
+
validateRouteModule(handler2, method, filePath);
|
|
2964
|
+
return { handler: handler2, method };
|
|
2965
|
+
}
|
|
2966
|
+
} catch (compileErr) {
|
|
2967
|
+
const compileReason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
2968
|
+
throw new Error(`Failed to compile route module "${sourcePath}": ${compileReason}`, {
|
|
2969
|
+
cause: compileErr
|
|
2970
|
+
});
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2519
2974
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2520
2975
|
throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
|
|
2521
2976
|
}
|
|
@@ -2565,6 +3020,7 @@ async function parseMultipart(request) {
|
|
|
2565
3020
|
}
|
|
2566
3021
|
|
|
2567
3022
|
// src/runtime/resolveInput.ts
|
|
3023
|
+
init_inputType();
|
|
2568
3024
|
async function resolveInput(method, request) {
|
|
2569
3025
|
const inputType = getInputTypeForMethod(method);
|
|
2570
3026
|
if (inputType === "body") {
|
|
@@ -2628,6 +3084,9 @@ async function sendNodeResponse(response, res) {
|
|
|
2628
3084
|
res.end();
|
|
2629
3085
|
}
|
|
2630
3086
|
|
|
3087
|
+
// src/server/createServer.ts
|
|
3088
|
+
init_inputType();
|
|
3089
|
+
|
|
2631
3090
|
// src/utils/getClientIp.ts
|
|
2632
3091
|
function getClientIp(req) {
|
|
2633
3092
|
const xff = req.headers["x-forwarded-for"];
|
|
@@ -2647,7 +3106,7 @@ function getClientIp(req) {
|
|
|
2647
3106
|
|
|
2648
3107
|
// src/server/handleWsUpgrade.ts
|
|
2649
3108
|
import { WebSocketServer, WebSocket } from "ws";
|
|
2650
|
-
import
|
|
3109
|
+
import path10 from "path";
|
|
2651
3110
|
|
|
2652
3111
|
// src/errors/formatErrorResponse.ts
|
|
2653
3112
|
function formatErrorResponse(error) {
|
|
@@ -2744,8 +3203,25 @@ function getPathname(req) {
|
|
|
2744
3203
|
const idx = url.indexOf("?");
|
|
2745
3204
|
return idx >= 0 ? url.slice(0, idx) : url;
|
|
2746
3205
|
}
|
|
2747
|
-
async function loadWsHandler(filePath, ctx) {
|
|
2748
|
-
|
|
3206
|
+
async function loadWsHandler(filePath, ctx, rootDir) {
|
|
3207
|
+
let module;
|
|
3208
|
+
try {
|
|
3209
|
+
module = await importWithCacheBust(filePath);
|
|
3210
|
+
} catch (err) {
|
|
3211
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
3212
|
+
const dist = getDevDist();
|
|
3213
|
+
if (dist) {
|
|
3214
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3215
|
+
const compiled = await ensureCompiled(sourcePath, rootDir, dist);
|
|
3216
|
+
if (compiled) {
|
|
3217
|
+
module = await importWithCacheBust(filePath);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
if (!module) {
|
|
3222
|
+
throw err;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
2749
3225
|
const handler = module["WS"];
|
|
2750
3226
|
if (typeof handler !== "function") {
|
|
2751
3227
|
throw new Error(`WS export not found in ${filePath}`);
|
|
@@ -2820,8 +3296,8 @@ function attachWebSocket(options) {
|
|
|
2820
3296
|
const finalHandler = async () => {
|
|
2821
3297
|
let handlers;
|
|
2822
3298
|
try {
|
|
2823
|
-
const absoluteFilePath =
|
|
2824
|
-
handlers = await loadWsHandler(absoluteFilePath, ctx);
|
|
3299
|
+
const absoluteFilePath = path10.resolve(rootDir, route.filePath);
|
|
3300
|
+
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
2825
3301
|
} catch (err) {
|
|
2826
3302
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2827
3303
|
console.error(`[faapi] WS handler \u52A0\u8F7D\u5931\u8D25 ${route.filePath}: ${reason}`);
|
|
@@ -2843,6 +3319,14 @@ function attachWebSocket(options) {
|
|
|
2843
3319
|
};
|
|
2844
3320
|
let response;
|
|
2845
3321
|
try {
|
|
3322
|
+
if (route.middlewares === void 0 && route.middlewarePaths) {
|
|
3323
|
+
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
3324
|
+
if (bundle) {
|
|
3325
|
+
route.middlewares = bundle.middlewares;
|
|
3326
|
+
} else {
|
|
3327
|
+
route.middlewares = [];
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
2846
3330
|
const dirMiddlewares = route.middlewares ?? [];
|
|
2847
3331
|
const allMiddlewares = globalMiddlewares && globalMiddlewares.length > 0 ? [...globalMiddlewares, ...dirMiddlewares] : dirMiddlewares;
|
|
2848
3332
|
if (allMiddlewares.length > 0) {
|
|
@@ -2866,6 +3350,7 @@ function attachWebSocket(options) {
|
|
|
2866
3350
|
}
|
|
2867
3351
|
|
|
2868
3352
|
// src/server/createServer.ts
|
|
3353
|
+
init_generateSchemaFiles();
|
|
2869
3354
|
var DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;
|
|
2870
3355
|
function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
2871
3356
|
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
@@ -2910,15 +3395,15 @@ function limitStreamSize(stream, maxSize) {
|
|
|
2910
3395
|
}
|
|
2911
3396
|
});
|
|
2912
3397
|
}
|
|
2913
|
-
function findAllowedMethods(routes,
|
|
3398
|
+
function findAllowedMethods(routes, path15) {
|
|
2914
3399
|
const methods = /* @__PURE__ */ new Set();
|
|
2915
3400
|
for (const route of routes) {
|
|
2916
|
-
if (route.urlPath ===
|
|
3401
|
+
if (route.urlPath === path15) {
|
|
2917
3402
|
methods.add(route.method);
|
|
2918
3403
|
continue;
|
|
2919
3404
|
}
|
|
2920
3405
|
if (route.isDynamic) {
|
|
2921
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3406
|
+
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2922
3407
|
if (params !== null) {
|
|
2923
3408
|
methods.add(route.method);
|
|
2924
3409
|
}
|
|
@@ -3004,16 +3489,32 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3004
3489
|
}
|
|
3005
3490
|
ctx.params = match.params;
|
|
3006
3491
|
const { route } = match;
|
|
3007
|
-
const absoluteFilePath =
|
|
3008
|
-
const routeModule = await loadRouteModule(absoluteFilePath, route.method);
|
|
3492
|
+
const absoluteFilePath = path11.resolve(rootDir, route.filePath);
|
|
3493
|
+
const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
|
|
3009
3494
|
const input = await resolveInput(route.method, request);
|
|
3010
3495
|
const inputType = getInputTypeForMethod(route.method);
|
|
3011
3496
|
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
3497
|
+
if (isDevOnDemandEnabled()) {
|
|
3498
|
+
const devDist = getDevDist();
|
|
3499
|
+
if (devDist) {
|
|
3500
|
+
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3012
3503
|
const result = await validateInput(schemaPath, route.method, inputType, input);
|
|
3013
3504
|
if (!result.valid) {
|
|
3014
3505
|
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", result.issues);
|
|
3015
3506
|
}
|
|
3016
3507
|
const body = hasBody(route.method) ? result.data : void 0;
|
|
3508
|
+
if (route.middlewares === void 0 && route.injectors === void 0 && route.middlewarePaths) {
|
|
3509
|
+
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
3510
|
+
if (bundle) {
|
|
3511
|
+
route.middlewares = bundle.middlewares;
|
|
3512
|
+
route.injectors = bundle.injectors;
|
|
3513
|
+
} else {
|
|
3514
|
+
route.middlewares = [];
|
|
3515
|
+
route.injectors = {};
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3017
3518
|
const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
|
|
3018
3519
|
const response = await invokeHandler(
|
|
3019
3520
|
routeModule.handler,
|
|
@@ -3068,7 +3569,7 @@ async function createTestServer(options) {
|
|
|
3068
3569
|
} = options;
|
|
3069
3570
|
const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
|
|
3070
3571
|
const sorted = sortRoutes(routes);
|
|
3071
|
-
const schemaDist = dist ?
|
|
3572
|
+
const schemaDist = dist ? path12.isAbsolute(dist) ? dist : path12.resolve(rootDir, dist) : await fs9.mkdtemp(path12.join(os.tmpdir(), "faapi-test-schema-"));
|
|
3072
3573
|
await generateSchemaFiles(sorted, rootDir, schemaDist);
|
|
3073
3574
|
const { server } = createServer({
|
|
3074
3575
|
routes: sorted,
|
|
@@ -3101,7 +3602,7 @@ async function createTestServer(options) {
|
|
|
3101
3602
|
await new Promise((resolve) => {
|
|
3102
3603
|
server.close(() => resolve());
|
|
3103
3604
|
});
|
|
3104
|
-
await
|
|
3605
|
+
await fs9.rm(schemaDist, { recursive: true, force: true }).catch(() => {
|
|
3105
3606
|
});
|
|
3106
3607
|
invalidateSchemaCache();
|
|
3107
3608
|
}
|
|
@@ -3243,8 +3744,8 @@ async function connectWs(baseUrl, pathname, options = {}) {
|
|
|
3243
3744
|
}
|
|
3244
3745
|
|
|
3245
3746
|
// src/cli/createAppCore.ts
|
|
3246
|
-
import
|
|
3247
|
-
import
|
|
3747
|
+
import fs11 from "fs";
|
|
3748
|
+
import path14 from "path";
|
|
3248
3749
|
import { PassThrough } from "stream";
|
|
3249
3750
|
|
|
3250
3751
|
// src/router/detectRouteConflicts.ts
|
|
@@ -3301,55 +3802,30 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
3301
3802
|
}
|
|
3302
3803
|
|
|
3303
3804
|
// src/cli/generateRoutes.ts
|
|
3304
|
-
import
|
|
3305
|
-
import
|
|
3805
|
+
import fs10 from "fs";
|
|
3806
|
+
import path13 from "path";
|
|
3306
3807
|
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));
|
|
3808
|
+
const hydrateRoute = (serialized) => ({
|
|
3809
|
+
method: serialized.method,
|
|
3810
|
+
urlPath: serialized.urlPath,
|
|
3811
|
+
filePath: serialized.filePath,
|
|
3812
|
+
paramNames: serialized.paramNames,
|
|
3813
|
+
isDynamic: serialized.isDynamic,
|
|
3814
|
+
isCatchAll: serialized.isCatchAll,
|
|
3815
|
+
middlewarePaths: serialized.middlewarePaths
|
|
3816
|
+
});
|
|
3817
|
+
const hydrateWsRoute = (serialized) => ({
|
|
3818
|
+
urlPath: serialized.urlPath,
|
|
3819
|
+
filePath: serialized.filePath,
|
|
3820
|
+
paramNames: serialized.paramNames,
|
|
3821
|
+
isDynamic: serialized.isDynamic,
|
|
3822
|
+
isCatchAll: serialized.isCatchAll,
|
|
3823
|
+
middlewarePaths: serialized.middlewarePaths
|
|
3824
|
+
});
|
|
3825
|
+
const routes = manifest.routes.map(hydrateRoute);
|
|
3826
|
+
const wsRoutes = manifest.wsRoutes.map(hydrateWsRoute);
|
|
3334
3827
|
return { routes, wsRoutes };
|
|
3335
3828
|
}
|
|
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
3829
|
|
|
3354
3830
|
// src/cli/loadPlugins.ts
|
|
3355
3831
|
async function loadPlugins(declarations, ctx) {
|
|
@@ -3433,8 +3909,8 @@ function isFaapiConfigKey(key) {
|
|
|
3433
3909
|
async function createAppBase(options) {
|
|
3434
3910
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
3435
3911
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
3436
|
-
const routesPath =
|
|
3437
|
-
if (!
|
|
3912
|
+
const routesPath = path14.resolve(rootDir, dist, ROUTES_FILE);
|
|
3913
|
+
if (!fs11.existsSync(routesPath)) {
|
|
3438
3914
|
throw new Error(
|
|
3439
3915
|
`[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
3916
|
);
|
|
@@ -3636,6 +4112,7 @@ async function createAppBase(options) {
|
|
|
3636
4112
|
}
|
|
3637
4113
|
|
|
3638
4114
|
// src/cli/createDevApp.ts
|
|
4115
|
+
init_createProgram();
|
|
3639
4116
|
async function createDevApp(options) {
|
|
3640
4117
|
const { app, ctx } = await createAppBase(options);
|
|
3641
4118
|
const devApp = app;
|
|
@@ -3644,9 +4121,16 @@ async function createDevApp(options) {
|
|
|
3644
4121
|
invalidateMiddlewareCache();
|
|
3645
4122
|
invalidateProgramCache();
|
|
3646
4123
|
invalidateSchemaCache();
|
|
4124
|
+
clearCompiledFiles();
|
|
3647
4125
|
const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.dist);
|
|
3648
4126
|
const sorted = sortRoutes(reScanned.routes);
|
|
3649
|
-
|
|
4127
|
+
if (isDevOnDemandEnabled()) {
|
|
4128
|
+
await deleteSchemaFiles(sorted, ctx.rootDir, ctx.dist);
|
|
4129
|
+
clearGeneratedSchemas();
|
|
4130
|
+
} else {
|
|
4131
|
+
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
4132
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
4133
|
+
}
|
|
3650
4134
|
ctx.updateRoutes(sorted, reScanned.wsRoutes);
|
|
3651
4135
|
};
|
|
3652
4136
|
return devApp;
|