@faapi/faapi 0.0.0-canary.22b65a2 → 0.0.0-canary.2a7b6a3

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/index.js CHANGED
@@ -1,129 +1,3 @@
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
-
11
- // src/utils/isPlainObject.ts
12
- function isPlainObject(value) {
13
- if (value === null || typeof value !== "object") {
14
- return false;
15
- }
16
- if (Array.isArray(value)) {
17
- return false;
18
- }
19
- const proto = Object.getPrototypeOf(value);
20
- return proto === null || proto === Object.prototype;
21
- }
22
- var init_isPlainObject = __esm({
23
- "src/utils/isPlainObject.ts"() {
24
- "use strict";
25
- }
26
- });
27
-
28
- // src/response/toResponse.ts
29
- var toResponse_exports = {};
30
- __export(toResponse_exports, {
31
- toResponse: () => toResponse
32
- });
33
- async function toResponse(value, meta) {
34
- if (value instanceof Promise) {
35
- return toResponse(await value, meta);
36
- }
37
- const applyMeta = (headers2) => {
38
- if (!meta) return;
39
- for (const [key, val] of Object.entries(meta.headers)) {
40
- headers2.set(key, val);
41
- }
42
- for (const cookie of meta.setCookies ?? []) {
43
- headers2.append("set-cookie", cookie);
44
- }
45
- };
46
- if (value instanceof Response) {
47
- if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
48
- const headers2 = new Headers(value.headers);
49
- applyMeta(headers2);
50
- return new Response(value.body, {
51
- status: meta.status ?? value.status,
52
- headers: headers2
53
- });
54
- }
55
- return value;
56
- }
57
- if (value === null || value === void 0) {
58
- const status = meta?.status ?? 204;
59
- const headers2 = new Headers();
60
- applyMeta(headers2);
61
- return new Response(null, { status, headers: headers2 });
62
- }
63
- if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
64
- const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
65
- applyMeta(headers2);
66
- return new Response(value, {
67
- status: meta?.status ?? 200,
68
- headers: headers2
69
- });
70
- }
71
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
72
- const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
73
- applyMeta(headers2);
74
- return new Response(value, {
75
- status: meta?.status ?? 200,
76
- headers: headers2
77
- });
78
- }
79
- if (value instanceof Uint8Array) {
80
- const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
81
- applyMeta(headers2);
82
- return new Response(value, {
83
- status: meta?.status ?? 200,
84
- headers: headers2
85
- });
86
- }
87
- if (isPlainObject(value) || Array.isArray(value)) {
88
- const body2 = JSON.stringify(value);
89
- const headers2 = new Headers({ "Content-Type": "application/json" });
90
- applyMeta(headers2);
91
- return new Response(body2, {
92
- status: meta?.status ?? 200,
93
- headers: headers2
94
- });
95
- }
96
- if (typeof value === "string") {
97
- const headers2 = new Headers({ "Content-Type": "text/plain" });
98
- applyMeta(headers2);
99
- return new Response(value, {
100
- status: meta?.status ?? 200,
101
- headers: headers2
102
- });
103
- }
104
- if (typeof value === "number" || typeof value === "boolean") {
105
- const headers2 = new Headers({ "Content-Type": "text/plain" });
106
- applyMeta(headers2);
107
- return new Response(String(value), {
108
- status: meta?.status ?? 200,
109
- headers: headers2
110
- });
111
- }
112
- const body = JSON.stringify(value);
113
- const headers = new Headers({ "Content-Type": "application/json" });
114
- applyMeta(headers);
115
- return new Response(body, {
116
- status: meta?.status ?? 200,
117
- headers
118
- });
119
- }
120
- var init_toResponse = __esm({
121
- "src/response/toResponse.ts"() {
122
- "use strict";
123
- init_isPlainObject();
124
- }
125
- });
126
-
127
1
  // src/ast/createProgram.ts
128
2
  import ts from "typescript";
129
3
  var programCache = /* @__PURE__ */ new Map();
@@ -272,6 +146,9 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
272
146
  if (ts2.isTypeOperatorNode(typeNode) && typeNode.operator === ts2.SyntaxKind.KeyOfKeyword) {
273
147
  return resolveKeyOf(typeNode, checker);
274
148
  }
149
+ if (ts2.isTypeOperatorNode(typeNode) && typeNode.operator === ts2.SyntaxKind.ReadonlyKeyword) {
150
+ return resolveTypeNode(typeNode.type, checker, visited);
151
+ }
275
152
  if (ts2.isTypeReferenceNode(typeNode)) {
276
153
  return resolveTypeReference(typeNode, checker, visited);
277
154
  }
@@ -284,7 +161,11 @@ function resolveTypeLiteral(typeNode, checker, visited = /* @__PURE__ */ new Set
284
161
  const name = member.name.getText();
285
162
  const optional = !!member.questionToken;
286
163
  const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
287
- properties.push({ name, type, optional });
164
+ const constraints = extractConstraintsFromJsDoc(member, name);
165
+ validateConstraints(constraints, type, name);
166
+ properties.push(
167
+ constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional }
168
+ );
288
169
  }
289
170
  if (ts2.isIndexSignatureDeclaration(member)) {
290
171
  const keyType = member.parameters[0]?.type ? resolveTypeNode(member.parameters[0].type, checker, visited) : { kind: "any" };
@@ -366,7 +247,7 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
366
247
  if (typeName === "Date") {
367
248
  return { kind: "date" };
368
249
  }
369
- if (typeName === "Array" && typeNode.typeArguments?.length === 1) {
250
+ if ((typeName === "Array" || typeName === "ReadonlyArray") && typeNode.typeArguments?.length === 1) {
370
251
  return {
371
252
  kind: "array",
372
253
  element: resolveTypeNode(typeNode.typeArguments[0], checker, visited)
@@ -491,7 +372,12 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
491
372
  const name = member.name.getText();
492
373
  const optional = !!member.questionToken;
493
374
  const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
494
- propMap.set(name, { name, type, optional });
375
+ const constraints = extractConstraintsFromJsDoc(member, name);
376
+ validateConstraints(constraints, type, name);
377
+ propMap.set(
378
+ name,
379
+ constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional }
380
+ );
495
381
  }
496
382
  if (ts2.isIndexSignatureDeclaration(member)) {
497
383
  const keyType = member.parameters[0]?.type ? resolveTypeNode(member.parameters[0].type, checker, visited) : { kind: "any" };
@@ -504,6 +390,142 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
504
390
  }
505
391
  return { kind: "object", properties };
506
392
  }
393
+ var NUMBER_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
394
+ "max",
395
+ "min",
396
+ "int",
397
+ "positive",
398
+ "negative",
399
+ "nonnegative",
400
+ "nonpositive"
401
+ ]);
402
+ var LENGTH_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
403
+ "maxLength",
404
+ "minLength",
405
+ "length"
406
+ ]);
407
+ var STRING_FORMAT_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
408
+ "regex",
409
+ "email",
410
+ "url",
411
+ "uuid"
412
+ ]);
413
+ function extractConstraintsFromJsDoc(node, fieldName) {
414
+ const jsDocs = ts2.getJSDocCommentsAndTags(node).filter((entry) => ts2.isJSDoc(entry));
415
+ if (jsDocs.length === 0) return [];
416
+ const constraints = [];
417
+ for (const jsDoc of jsDocs) {
418
+ if (!jsDoc.tags) continue;
419
+ for (const tag of jsDoc.tags) {
420
+ const constraint = parseJsDocTag(tag, fieldName);
421
+ if (constraint) constraints.push(constraint);
422
+ }
423
+ }
424
+ return constraints;
425
+ }
426
+ function getTagCommentText(tag) {
427
+ const comment = tag.comment;
428
+ if (typeof comment === "string") return comment;
429
+ return void 0;
430
+ }
431
+ function parseJsDocTag(tag, fieldName) {
432
+ const tagName = tag.tagName.text;
433
+ switch (tagName) {
434
+ // 数值约束(带值)
435
+ case "max":
436
+ case "min": {
437
+ const value = parseNumberValue(tag, fieldName, tagName);
438
+ return { kind: tagName, value };
439
+ }
440
+ // 长度约束(带值)
441
+ case "maxLength":
442
+ case "minLength":
443
+ case "length": {
444
+ const value = parseNumberValue(tag, fieldName, tagName);
445
+ return { kind: tagName, value };
446
+ }
447
+ // 正则约束(带 /pattern/flags 值)
448
+ case "regex":
449
+ case "pattern": {
450
+ const text = getTagCommentText(tag);
451
+ if (!text) {
452
+ throw new SchemaExtractionError(fieldName, `@${tagName} \u6807\u7B7E\u9700\u8981 /pattern/flags \u5F62\u5F0F\u7684\u503C`);
453
+ }
454
+ const regex = parseRegexLiteral(text.trim(), fieldName);
455
+ return { kind: "regex", pattern: regex.pattern, flags: regex.flags };
456
+ }
457
+ // 数值约束(无值)
458
+ case "int":
459
+ case "positive":
460
+ case "negative":
461
+ case "nonnegative":
462
+ case "nonpositive":
463
+ return { kind: tagName };
464
+ // 字符串格式约束(无值)
465
+ case "email":
466
+ case "url":
467
+ case "uuid":
468
+ return { kind: tagName };
469
+ default:
470
+ return null;
471
+ }
472
+ }
473
+ function parseNumberValue(tag, fieldName, tagName) {
474
+ const text = getTagCommentText(tag);
475
+ if (!text) {
476
+ throw new SchemaExtractionError(fieldName, `@${tagName} \u6807\u7B7E\u9700\u8981\u4E00\u4E2A\u6570\u5B57\u503C`);
477
+ }
478
+ const trimmed = text.trim();
479
+ const num = Number(trimmed);
480
+ if (!Number.isFinite(num)) {
481
+ throw new SchemaExtractionError(fieldName, `@${tagName} \u6807\u7B7E\u7684\u503C "${trimmed}" \u4E0D\u662F\u6709\u6548\u6570\u5B57`);
482
+ }
483
+ return num;
484
+ }
485
+ function parseRegexLiteral(text, fieldName) {
486
+ const match = /^\/(.+)\/([gimsuy]*)$/.exec(text);
487
+ if (!match) {
488
+ throw new SchemaExtractionError(fieldName, `\u6B63\u5219\u503C "${text}" \u4E0D\u662F /pattern/flags \u5F62\u5F0F`);
489
+ }
490
+ const [, pattern, flags] = match;
491
+ if (!pattern) {
492
+ throw new SchemaExtractionError(fieldName, `\u6B63\u5219\u503C "${text}" \u7684 pattern \u90E8\u5206\u4E3A\u7A7A`);
493
+ }
494
+ return flags ? { pattern, flags } : { pattern };
495
+ }
496
+ function validateConstraints(constraints, type, fieldName) {
497
+ if (constraints.length === 0) return;
498
+ for (const constraint of constraints) {
499
+ const kind = constraint.kind;
500
+ if (NUMBER_CONSTRAINT_KINDS.has(kind)) {
501
+ if (type.kind !== "number") {
502
+ throw new SchemaExtractionError(
503
+ fieldName,
504
+ `@${kind} \u7EA6\u675F\u4EC5\u9002\u7528\u4E8E number \u5B57\u6BB5\uFF0C\u5B9E\u9645\u4E3A ${type.kind}`
505
+ );
506
+ }
507
+ continue;
508
+ }
509
+ if (LENGTH_CONSTRAINT_KINDS.has(kind)) {
510
+ if (type.kind !== "string" && type.kind !== "array") {
511
+ throw new SchemaExtractionError(
512
+ fieldName,
513
+ `@${kind} \u7EA6\u675F\u4EC5\u9002\u7528\u4E8E string \u6216 array \u5B57\u6BB5\uFF0C\u5B9E\u9645\u4E3A ${type.kind}`
514
+ );
515
+ }
516
+ continue;
517
+ }
518
+ if (STRING_FORMAT_CONSTRAINT_KINDS.has(kind)) {
519
+ if (type.kind !== "string") {
520
+ throw new SchemaExtractionError(
521
+ fieldName,
522
+ `@${kind} \u7EA6\u675F\u4EC5\u9002\u7528\u4E8E string \u5B57\u6BB5\uFF0C\u5B9E\u9645\u4E3A ${type.kind}`
523
+ );
524
+ }
525
+ continue;
526
+ }
527
+ }
528
+ }
507
529
 
508
530
  // src/ast/extractHandlerTypes.ts
509
531
  function extractTypeInfo(program, filePath, typeName) {
@@ -988,6 +1010,58 @@ async function loadConfig(rootDir, outDir) {
988
1010
  return null;
989
1011
  }
990
1012
 
1013
+ // src/errors/FaapiError.ts
1014
+ var FaapiError = class extends Error {
1015
+ constructor(code, message, statusCode) {
1016
+ super(message);
1017
+ this.code = code;
1018
+ this.statusCode = statusCode;
1019
+ this.name = "FaapiError";
1020
+ }
1021
+ code;
1022
+ statusCode;
1023
+ };
1024
+
1025
+ // src/errors/httpErrors.ts
1026
+ function deriveStatusCode(issues) {
1027
+ const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
1028
+ return has400 ? 400 : 422;
1029
+ }
1030
+ var ValidationError = class extends FaapiError {
1031
+ constructor(message, issues) {
1032
+ super("VALIDATION_ERROR", message, deriveStatusCode(issues));
1033
+ this.issues = issues;
1034
+ this.name = "ValidationError";
1035
+ }
1036
+ issues;
1037
+ };
1038
+ var RouteNotFoundError = class extends FaapiError {
1039
+ constructor(path9) {
1040
+ super("ROUTE_NOT_FOUND", `Route not found: ${path9}`, 404);
1041
+ this.name = "RouteNotFoundError";
1042
+ }
1043
+ };
1044
+ var MethodNotAllowedError = class extends FaapiError {
1045
+ constructor(method, path9, allowedMethods) {
1046
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path9}`, 405);
1047
+ this.allowedMethods = allowedMethods;
1048
+ this.name = "MethodNotAllowedError";
1049
+ }
1050
+ allowedMethods;
1051
+ };
1052
+ var InternalError = class extends FaapiError {
1053
+ constructor(message) {
1054
+ super("INTERNAL_ERROR", message, 500);
1055
+ this.name = "InternalError";
1056
+ }
1057
+ };
1058
+ var ModuleLoadError = class extends FaapiError {
1059
+ constructor(filePath, reason) {
1060
+ super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
1061
+ this.name = "ModuleLoadError";
1062
+ }
1063
+ };
1064
+
991
1065
  // src/cli/createAppCore.ts
992
1066
  import fs4 from "fs";
993
1067
  import path7 from "path";
@@ -1417,52 +1491,6 @@ async function parseMultipart(request) {
1417
1491
  return { fields, files };
1418
1492
  }
1419
1493
 
1420
- // src/errors/FaapiError.ts
1421
- var FaapiError = class extends Error {
1422
- constructor(code, message, statusCode) {
1423
- super(message);
1424
- this.code = code;
1425
- this.statusCode = statusCode;
1426
- this.name = "FaapiError";
1427
- }
1428
- code;
1429
- statusCode;
1430
- };
1431
-
1432
- // src/errors/httpErrors.ts
1433
- function deriveStatusCode(issues) {
1434
- const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
1435
- return has400 ? 400 : 422;
1436
- }
1437
- var ValidationError = class extends FaapiError {
1438
- constructor(message, issues) {
1439
- super("VALIDATION_ERROR", message, deriveStatusCode(issues));
1440
- this.issues = issues;
1441
- this.name = "ValidationError";
1442
- }
1443
- issues;
1444
- };
1445
- var RouteNotFoundError = class extends FaapiError {
1446
- constructor(path9) {
1447
- super("ROUTE_NOT_FOUND", `Route not found: ${path9}`, 404);
1448
- this.name = "RouteNotFoundError";
1449
- }
1450
- };
1451
- var MethodNotAllowedError = class extends FaapiError {
1452
- constructor(method, path9, allowedMethods) {
1453
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path9}`, 405);
1454
- this.allowedMethods = allowedMethods;
1455
- this.name = "MethodNotAllowedError";
1456
- }
1457
- allowedMethods;
1458
- };
1459
- var InternalError = class extends FaapiError {
1460
- constructor(message) {
1461
- super("INTERNAL_ERROR", message, 500);
1462
- this.name = "InternalError";
1463
- }
1464
- };
1465
-
1466
1494
  // src/runtime/resolveInput.ts
1467
1495
  async function resolveInput(method, request) {
1468
1496
  const inputType = getInputTypeForMethod(method);
@@ -1503,8 +1531,106 @@ async function resolveInput(method, request) {
1503
1531
  return queryToObject(url.searchParams);
1504
1532
  }
1505
1533
 
1506
- // src/runtime/invokeHandler.ts
1507
- init_toResponse();
1534
+ // src/utils/isPlainObject.ts
1535
+ function isPlainObject(value) {
1536
+ if (value === null || typeof value !== "object") {
1537
+ return false;
1538
+ }
1539
+ if (Array.isArray(value)) {
1540
+ return false;
1541
+ }
1542
+ const proto = Object.getPrototypeOf(value);
1543
+ return proto === null || proto === Object.prototype;
1544
+ }
1545
+
1546
+ // src/response/toResponse.ts
1547
+ async function toResponse(value, meta) {
1548
+ if (value instanceof Promise) {
1549
+ return toResponse(await value, meta);
1550
+ }
1551
+ const applyMeta = (headers2) => {
1552
+ if (!meta) return;
1553
+ for (const [key, val] of Object.entries(meta.headers)) {
1554
+ headers2.set(key, val);
1555
+ }
1556
+ for (const cookie of meta.setCookies ?? []) {
1557
+ headers2.append("set-cookie", cookie);
1558
+ }
1559
+ };
1560
+ if (value instanceof Response) {
1561
+ if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
1562
+ const headers2 = new Headers(value.headers);
1563
+ applyMeta(headers2);
1564
+ return new Response(value.body, {
1565
+ status: meta.status ?? value.status,
1566
+ headers: headers2
1567
+ });
1568
+ }
1569
+ return value;
1570
+ }
1571
+ if (value === null || value === void 0) {
1572
+ const status = meta?.status ?? 204;
1573
+ const headers2 = new Headers();
1574
+ applyMeta(headers2);
1575
+ return new Response(null, { status, headers: headers2 });
1576
+ }
1577
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
1578
+ const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
1579
+ applyMeta(headers2);
1580
+ return new Response(value, {
1581
+ status: meta?.status ?? 200,
1582
+ headers: headers2
1583
+ });
1584
+ }
1585
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
1586
+ const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
1587
+ applyMeta(headers2);
1588
+ return new Response(value, {
1589
+ status: meta?.status ?? 200,
1590
+ headers: headers2
1591
+ });
1592
+ }
1593
+ if (value instanceof Uint8Array) {
1594
+ const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
1595
+ applyMeta(headers2);
1596
+ return new Response(value, {
1597
+ status: meta?.status ?? 200,
1598
+ headers: headers2
1599
+ });
1600
+ }
1601
+ if (isPlainObject(value) || Array.isArray(value)) {
1602
+ const body2 = JSON.stringify(value);
1603
+ const headers2 = new Headers({ "Content-Type": "application/json" });
1604
+ applyMeta(headers2);
1605
+ return new Response(body2, {
1606
+ status: meta?.status ?? 200,
1607
+ headers: headers2
1608
+ });
1609
+ }
1610
+ if (typeof value === "string") {
1611
+ const headers2 = new Headers({ "Content-Type": "text/plain" });
1612
+ applyMeta(headers2);
1613
+ return new Response(value, {
1614
+ status: meta?.status ?? 200,
1615
+ headers: headers2
1616
+ });
1617
+ }
1618
+ if (typeof value === "number" || typeof value === "boolean") {
1619
+ const headers2 = new Headers({ "Content-Type": "text/plain" });
1620
+ applyMeta(headers2);
1621
+ return new Response(String(value), {
1622
+ status: meta?.status ?? 200,
1623
+ headers: headers2
1624
+ });
1625
+ }
1626
+ const body = JSON.stringify(value);
1627
+ const headers = new Headers({ "Content-Type": "application/json" });
1628
+ applyMeta(headers);
1629
+ return new Response(body, {
1630
+ status: meta?.status ?? 200,
1631
+ headers
1632
+ });
1633
+ }
1508
1634
 
1509
1635
  // src/injection/injectParams.ts
1510
1636
  function getBuiltinInjectionValue(type, ctx, body) {
@@ -1727,16 +1853,13 @@ function mapZodCode(zodCode, message) {
1727
1853
  return "TYPE_MISMATCH";
1728
1854
  case "unrecognized_keys":
1729
1855
  return "INVALID_FORMAT";
1730
- case "invalid_enum_value":
1856
+ case "invalid_value":
1731
1857
  case "invalid_string":
1732
- case "invalid_date":
1733
1858
  case "too_small":
1734
1859
  case "too_big":
1735
1860
  case "invalid_intersection_types":
1736
1861
  case "not_multiple_of":
1737
1862
  return "INVALID_VALUE";
1738
- case "not_finite":
1739
- return "COERCE_FAILED";
1740
1863
  case "custom":
1741
1864
  return "INVALID_VALUE";
1742
1865
  default:
@@ -1762,7 +1885,7 @@ function getClientIp(req) {
1762
1885
  const first = xff.split(",")[0]?.trim();
1763
1886
  if (first) return first;
1764
1887
  }
1765
- const remote = req.socket.remoteAddress;
1888
+ const remote = req.socket?.remoteAddress;
1766
1889
  if (remote) {
1767
1890
  if (remote.startsWith("::ffff:")) {
1768
1891
  return remote.slice(7);
@@ -1835,14 +1958,7 @@ function nodeHttpToWebHeaders(req) {
1835
1958
  }
1836
1959
  return headers;
1837
1960
  }
1838
- function buildErrorResponse(err, ctx, errorFormat) {
1839
- if (errorFormat) {
1840
- try {
1841
- const res = errorFormat(err, ctx);
1842
- if (res) return res;
1843
- } catch {
1844
- }
1845
- }
1961
+ function buildErrorResponse(err) {
1846
1962
  try {
1847
1963
  return formatErrorResponse(err);
1848
1964
  } catch {
@@ -1932,7 +2048,7 @@ async function sendResponseToSocket(socket, response) {
1932
2048
  socket.destroy();
1933
2049
  }
1934
2050
  function attachWebSocket(options) {
1935
- const { server, routesRef, rootDir, config, errorFormat, globalMiddlewares } = options;
2051
+ const { server, routesRef, rootDir, config, globalMiddlewares } = options;
1936
2052
  const wss = new WebSocketServer({ noServer: true });
1937
2053
  server.on("upgrade", async (req, socket, head) => {
1938
2054
  const currentWsRoutes = routesRef.wsCurrent;
@@ -1989,7 +2105,7 @@ function attachWebSocket(options) {
1989
2105
  console.error("[faapi] WS \u63E1\u624B\u540E\u4E2D\u95F4\u4EF6\u629B\u9519:", err);
1990
2106
  return;
1991
2107
  }
1992
- response = buildErrorResponse(err, ctx, errorFormat);
2108
+ response = buildErrorResponse(err);
1993
2109
  }
1994
2110
  if (upgraded) {
1995
2111
  return;
@@ -2071,12 +2187,51 @@ function collectNamedTypes(type, ctx) {
2071
2187
  }
2072
2188
  }
2073
2189
  }
2074
- function runtimeTypeToZodExpression(type, ctx) {
2190
+ function runtimeTypeToZodExpression(type, ctx, constraints) {
2075
2191
  const expr = baseExpression(type, ctx);
2192
+ const withConstraints = constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;
2076
2193
  if (ctx.coerce && (type.kind === "number" || type.kind === "boolean")) {
2077
- return wrapCoercePreprocess(type.kind, expr);
2194
+ return wrapCoercePreprocess(type.kind, withConstraints);
2195
+ }
2196
+ return withConstraints;
2197
+ }
2198
+ function applyConstraints(baseExpr, constraints, typeKind) {
2199
+ const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join("");
2200
+ return `${baseExpr}${suffix}`;
2201
+ }
2202
+ function constraintToZodChain(constraint, _typeKind) {
2203
+ switch (constraint.kind) {
2204
+ case "max":
2205
+ return `.max(${constraint.value})`;
2206
+ case "min":
2207
+ return `.min(${constraint.value})`;
2208
+ case "int":
2209
+ return ".int()";
2210
+ case "positive":
2211
+ return ".positive()";
2212
+ case "negative":
2213
+ return ".negative()";
2214
+ case "nonnegative":
2215
+ return ".nonnegative()";
2216
+ case "nonpositive":
2217
+ return ".nonpositive()";
2218
+ case "maxLength":
2219
+ return `.max(${constraint.value})`;
2220
+ case "minLength":
2221
+ return `.min(${constraint.value})`;
2222
+ case "length":
2223
+ return `.length(${constraint.value})`;
2224
+ case "regex": {
2225
+ const flags = constraint.flags ?? "";
2226
+ return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ""}))`;
2227
+ }
2228
+ case "email":
2229
+ return ".email()";
2230
+ case "url":
2231
+ return ".url()";
2232
+ case "uuid":
2233
+ return ".uuid()";
2078
2234
  }
2079
- return expr;
2080
2235
  }
2081
2236
  function baseExpression(type, ctx) {
2082
2237
  switch (type.kind) {
@@ -2174,8 +2329,9 @@ function generateTupleExpression(elements, ctx) {
2174
2329
  }
2175
2330
  function generateObjectExpression(properties, ctx) {
2176
2331
  const fields = properties.map((prop) => {
2177
- const expr = runtimeTypeToZodExpression(prop.type, ctx);
2178
- return `${JSON.stringify(prop.name)}: ${prop.optional ? `${expr}.optional()` : expr}`;
2332
+ const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);
2333
+ const finalExpr = prop.optional ? `${expr}.optional()` : expr;
2334
+ return `${JSON.stringify(prop.name)}: ${finalExpr}`;
2179
2335
  });
2180
2336
  return `z.object({ ${fields.join(", ")} })`;
2181
2337
  }
@@ -2405,8 +2561,6 @@ function createServer(options) {
2405
2561
  appDir,
2406
2562
  outDir,
2407
2563
  cors: corsOption,
2408
- responseFormat,
2409
- errorFormat,
2410
2564
  onError,
2411
2565
  config,
2412
2566
  wsRoutes,
@@ -2445,8 +2599,6 @@ function createServer(options) {
2445
2599
  req,
2446
2600
  res,
2447
2601
  configMiddlewares,
2448
- responseFormat,
2449
- errorFormat,
2450
2602
  onError,
2451
2603
  config,
2452
2604
  globalMiddlewares,
@@ -2458,11 +2610,11 @@ function createServer(options) {
2458
2610
  });
2459
2611
  });
2460
2612
  if (routesRef.wsCurrent.length > 0) {
2461
- attachWebSocket({ server, routesRef, rootDir, config, errorFormat, globalMiddlewares });
2613
+ attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });
2462
2614
  }
2463
2615
  return { server, routesRef };
2464
2616
  }
2465
- async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMiddlewares, responseFormat, errorFormat, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
2617
+ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
2466
2618
  const request = toWebRequest(req, bodyLimit);
2467
2619
  const method = request.method.toUpperCase();
2468
2620
  const urlPath = new URL(request.url).pathname;
@@ -2490,22 +2642,13 @@ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMi
2490
2642
  }
2491
2643
  const body = hasBody(route.method) ? result.data : void 0;
2492
2644
  const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
2493
- let response = await invokeHandler(
2645
+ const response = await invokeHandler(
2494
2646
  routeModule.handler,
2495
2647
  ctx,
2496
2648
  body,
2497
2649
  route.middlewares,
2498
2650
  mergedInjectors
2499
2651
  );
2500
- if (responseFormat && response.status >= 200 && response.status < 300) {
2501
- const contentType = response.headers.get("Content-Type") ?? "";
2502
- if (contentType.includes("application/json")) {
2503
- const data = await response.json();
2504
- const formatted = responseFormat(data, ctx);
2505
- const { toResponse: toResponse2 } = await Promise.resolve().then(() => (init_toResponse(), toResponse_exports));
2506
- response = await toResponse2(formatted, meta);
2507
- }
2508
- }
2509
2652
  return response;
2510
2653
  };
2511
2654
  try {
@@ -2522,7 +2665,7 @@ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMi
2522
2665
  }
2523
2666
  await sendNodeResponse(response, res);
2524
2667
  } catch (err) {
2525
- const errorResponse = buildErrorResponse(err, ctx, errorFormat);
2668
+ const errorResponse = buildErrorResponse(err);
2526
2669
  await sendNodeResponse(mergeMeta(errorResponse, meta), res);
2527
2670
  if (onError) {
2528
2671
  try {
@@ -2725,8 +2868,6 @@ var DEFAULT_PORT = 3e3;
2725
2868
  var ROUTES_FILE = "faapi-routes.js";
2726
2869
  var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
2727
2870
  "cors",
2728
- "responseFormat",
2729
- "errorFormat",
2730
2871
  "lifecycle",
2731
2872
  "middlewares",
2732
2873
  "injectors",
@@ -2772,8 +2913,6 @@ async function createAppBase(options) {
2772
2913
  appDir,
2773
2914
  outDir,
2774
2915
  cors: config?.cors ?? true,
2775
- responseFormat: config?.responseFormat,
2776
- errorFormat: config?.errorFormat,
2777
2916
  onError: config?.lifecycle?.onError,
2778
2917
  config: config ?? void 0,
2779
2918
  wsRoutes,
@@ -2786,6 +2925,7 @@ async function createAppBase(options) {
2786
2925
  const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {
2787
2926
  rootDir,
2788
2927
  routes: sorted,
2928
+ getRoutes: () => sorted,
2789
2929
  server,
2790
2930
  config: pluginConfig
2791
2931
  });
@@ -2851,6 +2991,11 @@ async function createAppBase(options) {
2851
2991
  setHeader(name, value) {
2852
2992
  this._headers[name.toLowerCase()] = value;
2853
2993
  },
2994
+ appendHeader(name, value) {
2995
+ const key = name.toLowerCase();
2996
+ const existing = this._headers[key];
2997
+ this._headers[key] = existing ? `${existing}, ${value}` : value;
2998
+ },
2854
2999
  writeHead(status, headers) {
2855
3000
  this.statusCode = status;
2856
3001
  if (headers) {
@@ -2892,6 +3037,7 @@ async function createAppBase(options) {
2892
3037
  host: "localhost",
2893
3038
  "content-type": body !== void 0 ? "application/json" : void 0
2894
3039
  };
3040
+ mockReq.socket = { remoteAddress: "127.0.0.1" };
2895
3041
  if (body !== void 0) {
2896
3042
  mockReq.push(JSON.stringify(body));
2897
3043
  }
@@ -2904,8 +3050,13 @@ async function createAppBase(options) {
2904
3050
  async close() {
2905
3051
  if (closed) return;
2906
3052
  closed = true;
2907
- server.closeIdleConnections?.();
2908
- server.closeAllConnections?.();
3053
+ const s = server;
3054
+ if (typeof s.closeIdleConnections === "function") {
3055
+ s.closeIdleConnections();
3056
+ }
3057
+ if (typeof s.closeAllConnections === "function") {
3058
+ s.closeAllConnections();
3059
+ }
2909
3060
  if (config?.lifecycle?.onClose) {
2910
3061
  await config.lifecycle.onClose({ rootDir, routes: sorted, server });
2911
3062
  }
@@ -3156,7 +3307,13 @@ async function createProdApp(options) {
3156
3307
  return app;
3157
3308
  }
3158
3309
  export {
3310
+ FaapiError,
3311
+ InternalError,
3312
+ MethodNotAllowedError,
3313
+ ModuleLoadError,
3314
+ RouteNotFoundError,
3159
3315
  SchemaExtractionError,
3316
+ ValidationError,
3160
3317
  collectRouteSchemaSources,
3161
3318
  cors,
3162
3319
  createProdApp as createApp,
@@ -3168,6 +3325,7 @@ export {
3168
3325
  helmet,
3169
3326
  invalidateProgramCache,
3170
3327
  loadConfig,
3171
- logger
3328
+ logger,
3329
+ resolveTypeNode
3172
3330
  };
3173
3331
  //# sourceMappingURL=index.js.map