@faapi/faapi 0.0.0-canary.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/index.js ADDED
@@ -0,0 +1,665 @@
1
+ // src/ast/createProgram.ts
2
+ import ts from "typescript";
3
+ var programCache = /* @__PURE__ */ new Map();
4
+ function createProgram(filePath) {
5
+ const cached = programCache.get(filePath);
6
+ if (cached) {
7
+ return cached;
8
+ }
9
+ const program = ts.createProgram([filePath], {
10
+ strict: true,
11
+ target: ts.ScriptTarget.ES2022,
12
+ module: ts.ModuleKind.NodeNext,
13
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
14
+ skipLibCheck: true,
15
+ noEmit: true
16
+ });
17
+ programCache.set(filePath, program);
18
+ return program;
19
+ }
20
+
21
+ // src/ast/extractHandlerTypes.ts
22
+ import ts3 from "typescript";
23
+
24
+ // src/ast/resolveTypeNode.ts
25
+ import ts2 from "typescript";
26
+ var SchemaExtractionError = class extends Error {
27
+ constructor(typeText, reason, options) {
28
+ super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
29
+ this.typeText = typeText;
30
+ this.reason = reason;
31
+ this.name = "SchemaExtractionError";
32
+ }
33
+ typeText;
34
+ reason;
35
+ };
36
+ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
37
+ const kind = typeNode.kind;
38
+ switch (kind) {
39
+ case ts2.SyntaxKind.StringKeyword:
40
+ return { kind: "string" };
41
+ case ts2.SyntaxKind.NumberKeyword:
42
+ return { kind: "number" };
43
+ case ts2.SyntaxKind.BooleanKeyword:
44
+ return { kind: "boolean" };
45
+ case ts2.SyntaxKind.BigIntKeyword:
46
+ return { kind: "bigint" };
47
+ case ts2.SyntaxKind.NullKeyword:
48
+ return { kind: "null" };
49
+ case ts2.SyntaxKind.UndefinedKeyword:
50
+ return { kind: "undefined" };
51
+ case ts2.SyntaxKind.UnknownKeyword:
52
+ return { kind: "any" };
53
+ case ts2.SyntaxKind.AnyKeyword:
54
+ throw new SchemaExtractionError(typeNode.getText(), "any \u4E0D\u652F\u6301\uFF0C\u8BF7\u4F7F\u7528 unknown \u8868\u793A\u4E0D\u6821\u9A8C");
55
+ case ts2.SyntaxKind.VoidKeyword:
56
+ throw new SchemaExtractionError(typeNode.getText(), "void \u4E0D\u652F\u6301\u8FD0\u884C\u65F6\u6821\u9A8C");
57
+ case ts2.SyntaxKind.NeverKeyword:
58
+ throw new SchemaExtractionError(typeNode.getText(), "never \u4E0D\u652F\u6301\u8FD0\u884C\u65F6\u6821\u9A8C");
59
+ case ts2.SyntaxKind.ObjectKeyword:
60
+ throw new SchemaExtractionError(
61
+ typeNode.getText(),
62
+ "object \u4E0D\u652F\u6301\uFF0C\u8BF7\u4F7F\u7528\u5177\u4F53\u5BF9\u8C61\u7C7B\u578B\u6216 unknown"
63
+ );
64
+ }
65
+ if (ts2.isLiteralTypeNode(typeNode)) {
66
+ const literal = typeNode.literal;
67
+ if (ts2.isStringLiteral(literal)) {
68
+ return { kind: "literal", value: literal.text };
69
+ }
70
+ if (ts2.isNumericLiteral(literal)) {
71
+ return { kind: "literal", value: Number(literal.text) };
72
+ }
73
+ if (literal.kind === ts2.SyntaxKind.TrueKeyword) {
74
+ return { kind: "literal", value: true };
75
+ }
76
+ if (literal.kind === ts2.SyntaxKind.FalseKeyword) {
77
+ return { kind: "literal", value: false };
78
+ }
79
+ if (literal.kind === ts2.SyntaxKind.NullKeyword) {
80
+ return { kind: "null" };
81
+ }
82
+ throw new SchemaExtractionError(typeNode.getText(), "\u4E0D\u652F\u6301\u7684\u5B57\u9762\u91CF\u7C7B\u578B");
83
+ }
84
+ if (ts2.isArrayTypeNode(typeNode)) {
85
+ return {
86
+ kind: "array",
87
+ element: resolveTypeNode(typeNode.elementType, checker, visited)
88
+ };
89
+ }
90
+ if (ts2.isTupleTypeNode(typeNode)) {
91
+ const elements = typeNode.elements.map((e) => resolveTypeNode(e, checker, visited));
92
+ return { kind: "array", element: { kind: "union", members: elements } };
93
+ }
94
+ if (ts2.isUnionTypeNode(typeNode)) {
95
+ const members = typeNode.types.map((t) => resolveTypeNode(t, checker, visited));
96
+ return { kind: "union", members };
97
+ }
98
+ if (ts2.isIntersectionTypeNode(typeNode)) {
99
+ const properties = [];
100
+ for (const t of typeNode.types) {
101
+ const resolved = resolveTypeNode(t, checker, visited);
102
+ if (resolved.kind === "object") {
103
+ properties.push(...resolved.properties);
104
+ }
105
+ }
106
+ return { kind: "object", properties };
107
+ }
108
+ if (ts2.isTypeLiteralNode(typeNode)) {
109
+ return resolveTypeLiteral(typeNode, checker, visited);
110
+ }
111
+ if (ts2.isTypeOperatorNode(typeNode) && typeNode.operator === ts2.SyntaxKind.KeyOfKeyword) {
112
+ return resolveKeyOf(typeNode, checker);
113
+ }
114
+ if (ts2.isTypeReferenceNode(typeNode)) {
115
+ return resolveTypeReference(typeNode, checker, visited);
116
+ }
117
+ throw new SchemaExtractionError(typeNode.getText(), "\u4E0D\u652F\u6301\u7684\u7C7B\u578B\u8BED\u6CD5");
118
+ }
119
+ function resolveTypeLiteral(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
120
+ const properties = [];
121
+ for (const member of typeNode.members) {
122
+ if (ts2.isPropertySignature(member) && member.name) {
123
+ const name = member.name.getText();
124
+ const optional = !!member.questionToken;
125
+ const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
126
+ properties.push({ name, type, optional });
127
+ }
128
+ if (ts2.isIndexSignatureDeclaration(member)) {
129
+ const keyType = member.parameters[0]?.type ? resolveTypeNode(member.parameters[0].type, checker, visited) : { kind: "any" };
130
+ const valueType = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
131
+ return { kind: "record", key: keyType, value: valueType };
132
+ }
133
+ }
134
+ return { kind: "object", properties };
135
+ }
136
+ function extractLiteralKeys(type) {
137
+ if (type.kind === "literal" && typeof type.value === "string") {
138
+ return [type.value];
139
+ }
140
+ if (type.kind === "union") {
141
+ const keys = [];
142
+ for (const member of type.members) {
143
+ if (member.kind === "literal" && typeof member.value === "string") {
144
+ keys.push(member.value);
145
+ } else {
146
+ return null;
147
+ }
148
+ }
149
+ return keys;
150
+ }
151
+ return null;
152
+ }
153
+ function extractKeysFromChecker(typeNode, checker) {
154
+ if (!checker) return null;
155
+ const type = checker.getTypeFromTypeNode(typeNode);
156
+ if (type.isUnion()) {
157
+ const keys = [];
158
+ for (const member of type.types) {
159
+ if (member.isStringLiteral()) {
160
+ keys.push(member.value);
161
+ } else if (member.isNumberLiteral()) {
162
+ keys.push(String(member.value));
163
+ } else {
164
+ return null;
165
+ }
166
+ }
167
+ return keys;
168
+ }
169
+ if (type.isStringLiteral()) {
170
+ return [type.value];
171
+ }
172
+ if (type.isNumberLiteral()) {
173
+ return [String(type.value)];
174
+ }
175
+ return null;
176
+ }
177
+ function resolveKeyOf(typeNode, checker) {
178
+ if (!checker) {
179
+ throw new SchemaExtractionError(typeNode.getText(), "keyof T \u9700\u8981 checker \u624D\u80FD\u89E3\u6790");
180
+ }
181
+ const type = checker.getTypeFromTypeNode(typeNode);
182
+ if (type.isUnion()) {
183
+ const members = [];
184
+ for (const member of type.types) {
185
+ if (member.isStringLiteral()) {
186
+ members.push({ kind: "literal", value: member.value });
187
+ } else if (member.isNumberLiteral()) {
188
+ members.push({ kind: "literal", value: member.value });
189
+ } else {
190
+ throw new SchemaExtractionError(typeNode.getText(), "keyof T \u7684\u7ED3\u679C\u5305\u542B\u975E\u5B57\u9762\u91CF\u7C7B\u578B");
191
+ }
192
+ }
193
+ return { kind: "union", members };
194
+ }
195
+ if (type.isStringLiteral()) {
196
+ return { kind: "literal", value: type.value };
197
+ }
198
+ if (type.isNumberLiteral()) {
199
+ return { kind: "literal", value: type.value };
200
+ }
201
+ throw new SchemaExtractionError(typeNode.getText(), "keyof T \u7684\u7ED3\u679C\u65E0\u6CD5\u89E3\u6790\u4E3A\u5B57\u9762\u91CF\u8054\u5408");
202
+ }
203
+ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
204
+ const typeName = typeNode.typeName.getText();
205
+ if (typeName === "Date") {
206
+ return { kind: "date" };
207
+ }
208
+ if (typeName === "Array" && typeNode.typeArguments?.length === 1) {
209
+ return {
210
+ kind: "array",
211
+ element: resolveTypeNode(typeNode.typeArguments[0], checker, visited)
212
+ };
213
+ }
214
+ if (typeName === "Record" && typeNode.typeArguments?.length === 2) {
215
+ return {
216
+ kind: "record",
217
+ key: resolveTypeNode(typeNode.typeArguments[0], checker, visited),
218
+ value: resolveTypeNode(typeNode.typeArguments[1], checker, visited)
219
+ };
220
+ }
221
+ if ((typeName === "Partial" || typeName === "Required" || typeName === "Readonly") && typeNode.typeArguments?.length === 1) {
222
+ const inner = resolveTypeNode(typeNode.typeArguments[0], checker, visited);
223
+ if (inner.kind === "object" && typeName === "Partial") {
224
+ return {
225
+ kind: "object",
226
+ properties: inner.properties.map((p) => ({ ...p, optional: true }))
227
+ };
228
+ }
229
+ return inner;
230
+ }
231
+ if ((typeName === "Pick" || typeName === "Omit") && typeNode.typeArguments?.length === 2) {
232
+ const innerType = resolveTypeNode(typeNode.typeArguments[0], checker, visited);
233
+ if (innerType.kind !== "object") {
234
+ throw new SchemaExtractionError(
235
+ typeNode.getText(),
236
+ `${typeName} \u7684 T \u5FC5\u987B\u662F\u5BF9\u8C61\u7C7B\u578B\uFF0C\u5B9E\u9645\u4E3A ${innerType.kind}`
237
+ );
238
+ }
239
+ const keyTypeNode = typeNode.typeArguments[1];
240
+ let keys = extractLiteralKeys(resolveTypeNode(keyTypeNode, checker, visited));
241
+ if (keys === null) {
242
+ keys = extractKeysFromChecker(keyTypeNode, checker);
243
+ }
244
+ if (keys === null) {
245
+ throw new SchemaExtractionError(typeNode.getText(), `${typeName} \u7684 K \u65E0\u6CD5\u89E3\u6790\u4E3A\u5B57\u9762\u91CF\u96C6\u5408`);
246
+ }
247
+ const keySet = new Set(keys);
248
+ const properties = typeName === "Pick" ? innerType.properties.filter((p) => keySet.has(p.name)) : innerType.properties.filter((p) => !keySet.has(p.name));
249
+ return { kind: "object", properties };
250
+ }
251
+ if (typeName === "Map" || typeName === "Set" || typeName === "WeakMap" || typeName === "WeakSet") {
252
+ throw new SchemaExtractionError(
253
+ typeNode.getText(),
254
+ `${typeName} \u8FD0\u884C\u65F6\u65E0\u6CD5\u6821\u9A8C\uFF0C\u8BF7\u6539\u7528\u5BF9\u8C61\u6216\u6570\u7EC4`
255
+ );
256
+ }
257
+ if (typeName === "Promise") {
258
+ throw new SchemaExtractionError(
259
+ typeNode.getText(),
260
+ "Promise \u8FD0\u884C\u65F6\u65E0\u6CD5\u6821\u9A8C\uFF0C\u8BF7\u52FF\u5728 query/body \u7C7B\u578B\u4E2D\u4F7F\u7528"
261
+ );
262
+ }
263
+ if (visited.has(typeName)) {
264
+ return { kind: "ref", name: typeName };
265
+ }
266
+ visited.add(typeName);
267
+ if (checker) {
268
+ const symbol = typeNode.typeName.kind === ts2.SyntaxKind.Identifier ? checker.getSymbolAtLocation(typeNode.typeName) : void 0;
269
+ if (symbol) {
270
+ const declaration = symbol.declarations?.[0];
271
+ if (declaration) {
272
+ if (ts2.isInterfaceDeclaration(declaration)) {
273
+ return resolveInterfaceDeclaration(declaration, checker, visited);
274
+ }
275
+ if (ts2.isTypeAliasDeclaration(declaration)) {
276
+ return resolveTypeNode(declaration.type, checker, visited);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ throw new SchemaExtractionError(typeNode.getText(), `\u65E0\u6CD5\u89E3\u6790\u7684\u5F15\u7528\u7C7B\u578B "${typeName}"`);
282
+ }
283
+ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ new Set()) {
284
+ const properties = [];
285
+ const propMap = /* @__PURE__ */ new Map();
286
+ for (const heritageClause of node.heritageClauses ?? []) {
287
+ if (heritageClause.token === ts2.SyntaxKind.ExtendsKeyword) {
288
+ for (const expr of heritageClause.types) {
289
+ const parentType = resolveTypeNode(expr, checker, visited);
290
+ if (parentType.kind === "object") {
291
+ for (const prop of parentType.properties) {
292
+ propMap.set(prop.name, prop);
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+ for (const member of node.members) {
299
+ if (ts2.isPropertySignature(member) && member.name) {
300
+ const name = member.name.getText();
301
+ const optional = !!member.questionToken;
302
+ const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
303
+ propMap.set(name, { name, type, optional });
304
+ }
305
+ if (ts2.isIndexSignatureDeclaration(member)) {
306
+ const keyType = member.parameters[0]?.type ? resolveTypeNode(member.parameters[0].type, checker, visited) : { kind: "any" };
307
+ const valueType = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
308
+ return { kind: "record", key: keyType, value: valueType };
309
+ }
310
+ }
311
+ for (const prop of propMap.values()) {
312
+ properties.push(prop);
313
+ }
314
+ return { kind: "object", properties };
315
+ }
316
+
317
+ // src/ast/extractHandlerTypes.ts
318
+ function extractTypeInfo(program, filePath, typeName) {
319
+ const sourceFile = program.getSourceFile(filePath);
320
+ if (!sourceFile) return null;
321
+ const checker = program.getTypeChecker();
322
+ let result = null;
323
+ ts3.forEachChild(sourceFile, (node) => {
324
+ if (result) return;
325
+ if (ts3.isInterfaceDeclaration(node) && node.name.text === typeName) {
326
+ const visited = /* @__PURE__ */ new Set();
327
+ visited.add(typeName);
328
+ const runtimeType = withFileContext(
329
+ filePath,
330
+ typeName,
331
+ () => resolveInterfaceDeclaration(node, checker, visited)
332
+ );
333
+ result = {
334
+ name: typeName,
335
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
336
+ runtimeType
337
+ };
338
+ return;
339
+ }
340
+ if (ts3.isTypeAliasDeclaration(node) && node.name.text === typeName) {
341
+ const visited = /* @__PURE__ */ new Set();
342
+ visited.add(typeName);
343
+ const runtimeType = withFileContext(
344
+ filePath,
345
+ typeName,
346
+ () => resolveTypeNode(node.type, checker, visited)
347
+ );
348
+ result = {
349
+ name: typeName,
350
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
351
+ runtimeType
352
+ };
353
+ return;
354
+ }
355
+ });
356
+ return result;
357
+ }
358
+ function withFileContext(filePath, typeName, fn) {
359
+ try {
360
+ return fn();
361
+ } catch (err) {
362
+ if (err instanceof SchemaExtractionError) {
363
+ const fileName = filePath.split("/").pop() ?? filePath;
364
+ const enriched = new SchemaExtractionError(
365
+ err.typeText,
366
+ `${err.reason}\uFF08\u6587\u4EF6: ${fileName}, \u7C7B\u578B: ${typeName}\uFF09`,
367
+ { cause: err }
368
+ );
369
+ throw enriched;
370
+ }
371
+ throw err;
372
+ }
373
+ }
374
+
375
+ // src/runtime/inputType.ts
376
+ function getInputTypeForMethod(method) {
377
+ const upper = method.toUpperCase();
378
+ if (upper === "GET" || upper === "DELETE" || upper === "HEAD") {
379
+ return "query";
380
+ }
381
+ return "body";
382
+ }
383
+
384
+ // src/validator/schemaRegistry.ts
385
+ var SchemaRegistry = class {
386
+ manifest = /* @__PURE__ */ new Map();
387
+ /**
388
+ * 批量加载 manifest
389
+ * 覆盖已有数据
390
+ */
391
+ loadManifest(manifest) {
392
+ this.manifest.clear();
393
+ for (const [filePath, fileSchemas] of manifest) {
394
+ const copy = /* @__PURE__ */ new Map();
395
+ fileSchemas.forEach((value, key) => copy.set(key, value));
396
+ this.manifest.set(filePath, copy);
397
+ }
398
+ }
399
+ /**
400
+ * 查询单条 schema
401
+ * @returns SchemaEntry | null | undefined
402
+ * - SchemaEntry:有类型声明
403
+ * - null:无类型声明(跳过校验)
404
+ * - undefined:manifest 不完整(抛错)
405
+ */
406
+ get(filePath, schemaName) {
407
+ const fileSchemas = this.manifest.get(filePath);
408
+ if (!fileSchemas) return void 0;
409
+ return fileSchemas.get(schemaName);
410
+ }
411
+ /**
412
+ * 设置单个文件的所有 schema
413
+ * 覆盖该文件的已有数据
414
+ */
415
+ set(filePath, schemas) {
416
+ const copy = /* @__PURE__ */ new Map();
417
+ schemas.forEach((value, key) => copy.set(key, value));
418
+ this.manifest.set(filePath, copy);
419
+ }
420
+ /**
421
+ * 删除单个文件(文件被删除时)
422
+ */
423
+ delete(filePath) {
424
+ this.manifest.delete(filePath);
425
+ }
426
+ /**
427
+ * 判断文件是否已注册
428
+ */
429
+ hasFile(filePath) {
430
+ return this.manifest.has(filePath);
431
+ }
432
+ /**
433
+ * 清空(测试用 / watch 全量重建前)
434
+ */
435
+ clear() {
436
+ this.manifest.clear();
437
+ }
438
+ /**
439
+ * 已注册的文件数量
440
+ */
441
+ get size() {
442
+ return this.manifest.size;
443
+ }
444
+ };
445
+ var schemaRegistry = new SchemaRegistry();
446
+
447
+ // src/validator/schemaName.ts
448
+ function getSchemaName(method, inputType) {
449
+ return `${method.toUpperCase()}${inputType.charAt(0).toUpperCase() + inputType.slice(1)}`;
450
+ }
451
+
452
+ // src/validator/getSchemaProperties.ts
453
+ function getSchemaProperties(filePath, method, inputType) {
454
+ const schemaName = getSchemaName(method, inputType);
455
+ const entry = schemaRegistry.get(filePath, schemaName);
456
+ if (entry === void 0) return void 0;
457
+ if (entry === null) {
458
+ return { schemaName: null, properties: [] };
459
+ }
460
+ return {
461
+ schemaName,
462
+ properties: entry.properties.map((prop) => ({
463
+ name: prop.name,
464
+ type: runtimeTypeToString(prop.type),
465
+ required: !prop.optional
466
+ }))
467
+ };
468
+ }
469
+ function runtimeTypeToString(type) {
470
+ switch (type.kind) {
471
+ case "string":
472
+ case "number":
473
+ case "boolean":
474
+ case "bigint":
475
+ case "null":
476
+ case "undefined":
477
+ case "date":
478
+ return type.kind;
479
+ case "literal":
480
+ return JSON.stringify(type.value);
481
+ case "array":
482
+ return `${runtimeTypeToString(type.element)}[]`;
483
+ case "object":
484
+ return "object";
485
+ case "union":
486
+ return type.members.map(runtimeTypeToString).join(" | ");
487
+ case "record":
488
+ return `Record<${runtimeTypeToString(type.key)}, ${runtimeTypeToString(type.value)}>`;
489
+ case "ref":
490
+ return type.name;
491
+ case "any":
492
+ case "unknown":
493
+ return "unknown";
494
+ default:
495
+ return "unknown";
496
+ }
497
+ }
498
+
499
+ // src/middleware/cors.ts
500
+ var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
501
+ function cors(options = {}) {
502
+ const {
503
+ origin = true,
504
+ methods = DEFAULT_METHODS,
505
+ allowedHeaders,
506
+ exposeHeaders,
507
+ credentials = false,
508
+ maxAge
509
+ } = options;
510
+ return async (ctx, next) => {
511
+ const reqOrigin = ctx.headers.get("origin");
512
+ if (!reqOrigin) {
513
+ await next();
514
+ return;
515
+ }
516
+ let allowOrigin = null;
517
+ if (origin === true) {
518
+ allowOrigin = reqOrigin;
519
+ } else if (typeof origin === "string") {
520
+ allowOrigin = reqOrigin === origin ? origin : null;
521
+ } else if (Array.isArray(origin)) {
522
+ allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
523
+ }
524
+ if (!allowOrigin) {
525
+ await next();
526
+ return;
527
+ }
528
+ ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
529
+ if (origin === true || Array.isArray(origin)) {
530
+ const existingVary = ctx.headers.get("vary");
531
+ if (existingVary) {
532
+ if (!existingVary.toLowerCase().includes("origin")) {
533
+ ctx.setHeader("Vary", `${existingVary}, Origin`);
534
+ }
535
+ } else {
536
+ ctx.setHeader("Vary", "Origin");
537
+ }
538
+ }
539
+ ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
540
+ if (allowedHeaders) {
541
+ ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
542
+ } else {
543
+ const requestHeaders = ctx.headers.get("access-control-request-headers");
544
+ if (requestHeaders) {
545
+ ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
546
+ }
547
+ }
548
+ if (exposeHeaders && exposeHeaders.length > 0) {
549
+ ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
550
+ }
551
+ if (credentials) {
552
+ ctx.setHeader("Access-Control-Allow-Credentials", "true");
553
+ }
554
+ if (maxAge !== void 0) {
555
+ ctx.setHeader("Access-Control-Max-Age", String(maxAge));
556
+ }
557
+ if (ctx.method === "OPTIONS") {
558
+ return new Response(null, { status: 204 });
559
+ }
560
+ await next();
561
+ };
562
+ }
563
+
564
+ // src/middleware/logger.ts
565
+ function logger(options = {}) {
566
+ const { log = console.log } = options;
567
+ return async (ctx, next) => {
568
+ const start = Date.now();
569
+ try {
570
+ const response = await next();
571
+ const duration = Date.now() - start;
572
+ log(`${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);
573
+ return response;
574
+ } catch (err) {
575
+ const duration = Date.now() - start;
576
+ const message = err instanceof Error ? err.message : String(err);
577
+ const status = err?.statusCode ?? 500;
578
+ log(`${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
579
+ throw err;
580
+ }
581
+ };
582
+ }
583
+
584
+ // src/config/loadConfig.ts
585
+ import path from "path";
586
+ import fs from "fs";
587
+ import { pathToFileURL } from "url";
588
+ var BASE_CONFIG_FILES = ["faapi.config.ts", "faapi.config.js"];
589
+ function getEnv() {
590
+ return process.env.NODE_ENV || process.env.FAAPI_ENV || "development";
591
+ }
592
+ function deepMerge(base, override) {
593
+ const result = { ...base };
594
+ for (const key of Object.keys(override)) {
595
+ const baseVal = base[key];
596
+ const overVal = override[key];
597
+ if (baseVal instanceof Date || overVal instanceof Date || baseVal instanceof RegExp || overVal instanceof RegExp || baseVal instanceof Map || overVal instanceof Map || baseVal instanceof Set || overVal instanceof Set) {
598
+ result[key] = overVal;
599
+ continue;
600
+ }
601
+ if (baseVal !== null && overVal !== null && typeof baseVal === "object" && typeof overVal === "object" && !Array.isArray(baseVal) && !Array.isArray(overVal) && !(baseVal instanceof Function) && !(overVal instanceof Function)) {
602
+ result[key] = deepMerge(
603
+ baseVal,
604
+ overVal
605
+ );
606
+ } else {
607
+ result[key] = overVal;
608
+ }
609
+ }
610
+ return result;
611
+ }
612
+ async function loadConfigFile(filePath) {
613
+ if (!fs.existsSync(filePath)) {
614
+ return null;
615
+ }
616
+ try {
617
+ const url = pathToFileURL(filePath).href;
618
+ const module = await import(url);
619
+ return module.default ?? {};
620
+ } catch (err) {
621
+ throw new Error(
622
+ `Failed to load config file ${filePath}: ${err instanceof Error ? err.message : String(err)}`,
623
+ { cause: err }
624
+ );
625
+ }
626
+ }
627
+ async function loadConfig(rootDir, configPath) {
628
+ if (configPath) {
629
+ const resolvedPath = path.resolve(rootDir, configPath);
630
+ if (!fs.existsSync(resolvedPath)) {
631
+ throw new Error(`Config file not found: ${configPath}`);
632
+ }
633
+ return loadConfigFile(resolvedPath);
634
+ }
635
+ let baseConfig = null;
636
+ for (const fileName of BASE_CONFIG_FILES) {
637
+ const filePath = path.join(rootDir, fileName);
638
+ baseConfig = await loadConfigFile(filePath);
639
+ if (baseConfig) break;
640
+ }
641
+ if (!baseConfig) {
642
+ return null;
643
+ }
644
+ const env = getEnv();
645
+ const envFiles = [`faapi.config.${env}.ts`, `faapi.config.${env}.js`];
646
+ for (const envFile of envFiles) {
647
+ const envConfig = await loadConfigFile(path.join(rootDir, envFile));
648
+ if (envConfig) {
649
+ baseConfig = deepMerge(baseConfig, envConfig);
650
+ break;
651
+ }
652
+ }
653
+ return baseConfig;
654
+ }
655
+ export {
656
+ SchemaExtractionError,
657
+ cors,
658
+ createProgram,
659
+ extractTypeInfo,
660
+ getInputTypeForMethod,
661
+ getSchemaProperties,
662
+ loadConfig,
663
+ logger
664
+ };
665
+ //# sourceMappingURL=index.js.map