@faapi/faapi 0.0.0-canary.c6dc0f7 → 0.0.0-canary.e2ee43b

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 CHANGED
@@ -23,9 +23,9 @@ var init_constants = __esm({
23
23
  });
24
24
 
25
25
  // src/utils/normalizePath.ts
26
- function normalizePath(path12) {
27
- if (!path12) return "";
28
- let result = path12.replace(/\\/g, "/");
26
+ function normalizePath(path15) {
27
+ if (!path15) return "";
28
+ let result = path15.replace(/\\/g, "/");
29
29
  result = result.replace(/\/+/g, "/");
30
30
  result = result.replace(/\/+$/, "");
31
31
  if (result && !result.startsWith("/")) {
@@ -89,9 +89,9 @@ function getLoadTimestamp() {
89
89
  }
90
90
  async function importWithCacheBust(filePath) {
91
91
  let url = pathToFileURL(filePath).href;
92
- const ts6 = getLoadTimestamp();
93
- if (ts6 !== void 0) {
94
- url += `?t=${ts6}`;
92
+ const ts7 = getLoadTimestamp();
93
+ if (ts7 !== void 0) {
94
+ url += `?t=${ts7}`;
95
95
  }
96
96
  return await import(url);
97
97
  }
@@ -157,18 +157,32 @@ var init_loadMiddlewares = __esm({
157
157
  import fg from "fast-glob";
158
158
  import path from "path";
159
159
  import fs from "fs";
160
- async function findMergedMiddlewares(routeFilePath, rootDir) {
160
+ function toProdAbsPath(sourceAbsPath, rootDir, prodDir) {
161
+ const rel = path.relative(rootDir, sourceAbsPath);
162
+ const prodRel = `${prodDir}/${rel.replace(/\.ts$/, ".js")}`;
163
+ return path.resolve(rootDir, prodRel);
164
+ }
165
+ async function findMergedMiddlewares(routeFilePath, rootDir, prodDir) {
161
166
  const routeDir = path.dirname(routeFilePath);
162
167
  const resolvedRoot = path.resolve(rootDir);
163
168
  const mwPaths = [];
164
169
  let currentDir = path.resolve(rootDir, routeDir);
165
170
  while (true) {
166
- for (const ext of [".ts", ".js"]) {
167
- const mwPath = path.join(currentDir, `middlewares${ext}`);
171
+ if (prodDir) {
172
+ const mwPath = path.join(currentDir, "middlewares.js");
168
173
  const absMwPath = path.resolve(rootDir, mwPath);
169
- if (fs.existsSync(absMwPath)) {
170
- mwPaths.push(absMwPath);
171
- break;
174
+ const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, prodDir);
175
+ if (fs.existsSync(prodAbsMwPath)) {
176
+ mwPaths.push(prodAbsMwPath);
177
+ }
178
+ } else {
179
+ for (const ext of [".ts", ".js"]) {
180
+ const mwPath = path.join(currentDir, `middlewares${ext}`);
181
+ const absMwPath = path.resolve(rootDir, mwPath);
182
+ if (fs.existsSync(absMwPath)) {
183
+ mwPaths.push(absMwPath);
184
+ break;
185
+ }
172
186
  }
173
187
  }
174
188
  if (currentDir === resolvedRoot) break;
@@ -222,7 +236,7 @@ async function hasWsExport(absPath) {
222
236
  return false;
223
237
  }
224
238
  }
225
- async function scanRoutes(rootDir, patterns, appDir) {
239
+ async function scanRoutes(rootDir, patterns, appDir, prodDir) {
226
240
  const dir = appDir ?? ".";
227
241
  const files = await fg(patterns, {
228
242
  cwd: rootDir,
@@ -236,12 +250,13 @@ async function scanRoutes(rootDir, patterns, appDir) {
236
250
  const fileName = normalizedFile.split("/").pop();
237
251
  if (fileName === "handler.ts" || fileName === "handler.js") {
238
252
  const absPath = path.resolve(rootDir, normalizedFile);
253
+ const importPath = prodDir ? toProdAbsPath(absPath, rootDir, prodDir) : absPath;
239
254
  const urlPath = filePathToUrlPath(normalizedFile, dir);
240
255
  const paramNames = extractParamNames(urlPath);
241
256
  const isDynamic = paramNames.length > 0;
242
257
  const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
243
- const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir);
244
- const methods = await extractMethodsFromHandler(absPath);
258
+ const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, prodDir);
259
+ const methods = await extractMethodsFromHandler(importPath);
245
260
  for (const method of methods) {
246
261
  routes.push({
247
262
  method,
@@ -254,7 +269,7 @@ async function scanRoutes(rootDir, patterns, appDir) {
254
269
  injectors: middlewareBundle?.injectors
255
270
  });
256
271
  }
257
- const hasWs = await hasWsExport(absPath);
272
+ const hasWs = await hasWsExport(importPath);
258
273
  if (hasWs) {
259
274
  wsRoutes.push({
260
275
  urlPath,
@@ -1122,35 +1137,35 @@ function generateObjectValidation(type, varName, issuesVar, ctx, pathExpr) {
1122
1137
  return lines.join("\n");
1123
1138
  }
1124
1139
  function generateValueValidation(type, varName, issuesVar, ctx, pathExpr) {
1125
- const path12 = pathExpr ?? "''";
1140
+ const path15 = pathExpr ?? "''";
1126
1141
  switch (type.kind) {
1127
1142
  case "any":
1128
1143
  case "unknown":
1129
1144
  return "";
1130
1145
  // 不校验
1131
1146
  case "string":
1132
- return `if (typeof ${varName} !== 'string') ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'string', received: typeof ${varName}, message: '\u671F\u671B string\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1147
+ return `if (typeof ${varName} !== 'string') ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'string', received: typeof ${varName}, message: '\u671F\u671B string\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1133
1148
  case "number":
1134
- return `if (typeof ${varName} !== 'number' || Number.isNaN(${varName})) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'number', received: typeof ${varName}, message: '\u671F\u671B number\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1149
+ return `if (typeof ${varName} !== 'number' || Number.isNaN(${varName})) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'number', received: typeof ${varName}, message: '\u671F\u671B number\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1135
1150
  case "boolean":
1136
- return `if (typeof ${varName} !== 'boolean') ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'boolean', received: typeof ${varName}, message: '\u671F\u671B boolean\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1151
+ return `if (typeof ${varName} !== 'boolean') ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'boolean', received: typeof ${varName}, message: '\u671F\u671B boolean\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1137
1152
  case "bigint":
1138
- return `if (typeof ${varName} !== 'bigint') ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'bigint', received: typeof ${varName}, message: '\u671F\u671B bigint\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1153
+ return `if (typeof ${varName} !== 'bigint') ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'bigint', received: typeof ${varName}, message: '\u671F\u671B bigint\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1139
1154
  case "null":
1140
- return `if (${varName} !== null) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'null', received: typeof ${varName}, message: '\u671F\u671B null\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1155
+ return `if (${varName} !== null) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'null', received: typeof ${varName}, message: '\u671F\u671B null\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
1141
1156
  case "undefined":
1142
- return `if (${varName} !== undefined) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'undefined', received: typeof ${varName}, message: '\u671F\u671B undefined' });`;
1157
+ return `if (${varName} !== undefined) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'undefined', received: typeof ${varName}, message: '\u671F\u671B undefined' });`;
1143
1158
  case "literal":
1144
- return `if (${varName} !== ${JSON.stringify(type.value)}) ${issuesVar}.push({ path: ${path12}, code: 'INVALID_VALUE', expected: ${JSON.stringify(JSON.stringify(type.value))}, received: JSON.stringify(${varName}), message: '\u671F\u671B\u5B57\u9762\u91CF ${JSON.stringify(type.value)}\uFF0C\u5B9E\u9645 ' + JSON.stringify(${varName}) });`;
1159
+ return `if (${varName} !== ${JSON.stringify(type.value)}) ${issuesVar}.push({ path: ${path15}, code: 'INVALID_VALUE', expected: ${JSON.stringify(JSON.stringify(type.value))}, received: JSON.stringify(${varName}), message: '\u671F\u671B\u5B57\u9762\u91CF ${JSON.stringify(type.value)}\uFF0C\u5B9E\u9645 ' + JSON.stringify(${varName}) });`;
1145
1160
  case "date":
1146
1161
  return `if (${varName} instanceof Date) { /* Date \u5B9E\u4F8B,\u901A\u8FC7 */ }
1147
- else if (typeof ${varName} !== 'string') ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'Date | ISO 8601 string', received: typeof ${varName}, message: '\u671F\u671B Date \u6216 ISO 8601 \u5B57\u7B26\u4E32\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
1148
- else if (!/^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?(Z|[+-]\\d{2}:?\\d{2})?)?$/.test(${varName}) || isNaN(new Date(${varName}).getTime())) ${issuesVar}.push({ path: ${path12}, code: 'INVALID_FORMAT', expected: 'ISO 8601', received: String(${varName}), message: '\u4E0D\u662F\u5408\u6CD5\u7684 ISO 8601 \u65E5\u671F\u5B57\u7B26\u4E32: ' + ${varName} });`;
1162
+ else if (typeof ${varName} !== 'string') ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'Date | ISO 8601 string', received: typeof ${varName}, message: '\u671F\u671B Date \u6216 ISO 8601 \u5B57\u7B26\u4E32\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
1163
+ else if (!/^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?(Z|[+-]\\d{2}:?\\d{2})?)?$/.test(${varName}) || isNaN(new Date(${varName}).getTime())) ${issuesVar}.push({ path: ${path15}, code: 'INVALID_FORMAT', expected: 'ISO 8601', received: String(${varName}), message: '\u4E0D\u662F\u5408\u6CD5\u7684 ISO 8601 \u65E5\u671F\u5B57\u7B26\u4E32: ' + ${varName} });`;
1149
1164
  case "array": {
1150
1165
  const id = ctx.nextVarId();
1151
1166
  const itemVar = `item${id}`;
1152
1167
  const indexVar = `i${id}`;
1153
- const elemPath = `${path12} + '[' + ${indexVar} + ']'`;
1168
+ const elemPath = `${path15} + '[' + ${indexVar} + ']'`;
1154
1169
  const elemValidation = generateValueValidation(
1155
1170
  type.element,
1156
1171
  itemVar,
@@ -1158,7 +1173,7 @@ else if (!/^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?(Z|[+-]\\d{2
1158
1173
  ctx,
1159
1174
  elemPath
1160
1175
  );
1161
- return `if (!Array.isArray(${varName})) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'array', received: typeof ${varName}, message: '\u671F\u671B\u6570\u7EC4\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
1176
+ return `if (!Array.isArray(${varName})) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'array', received: typeof ${varName}, message: '\u671F\u671B\u6570\u7EC4\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
1162
1177
  else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) { const ${itemVar} = ${varName}[${indexVar}]; ${elemValidation} }`;
1163
1178
  }
1164
1179
  case "tuple": {
@@ -1170,21 +1185,21 @@ else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) {
1170
1185
  const indexVar = `ti${id}`;
1171
1186
  const lines = [];
1172
1187
  lines.push(
1173
- `if (!Array.isArray(${varName})) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'tuple', received: typeof ${varName}, message: '\u671F\u671B\u5143\u7EC4\uFF08\u6570\u7EC4\uFF09\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`
1188
+ `if (!Array.isArray(${varName})) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'tuple', received: typeof ${varName}, message: '\u671F\u671B\u5143\u7EC4\uFF08\u6570\u7EC4\uFF09\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`
1174
1189
  );
1175
1190
  lines.push(`else {`);
1176
1191
  lines.push(
1177
- `if (${varName}.length < ${minRequired}) ${issuesVar}.push({ path: ${path12}, code: 'MISSING_FIELD', expected: 'tuple length >= ${minRequired}', received: 'length ' + ${varName}.length, message: '\u5143\u7EC4\u957F\u5EA6\u4E0D\u8DB3\uFF0C\u671F\u671B\u81F3\u5C11 ${minRequired}\uFF0C\u5B9E\u9645 ' + ${varName}.length });`
1192
+ `if (${varName}.length < ${minRequired}) ${issuesVar}.push({ path: ${path15}, code: 'MISSING_FIELD', expected: 'tuple length >= ${minRequired}', received: 'length ' + ${varName}.length, message: '\u5143\u7EC4\u957F\u5EA6\u4E0D\u8DB3\uFF0C\u671F\u671B\u81F3\u5C11 ${minRequired}\uFF0C\u5B9E\u9645 ' + ${varName}.length });`
1178
1193
  );
1179
1194
  if (!restElement) {
1180
1195
  lines.push(
1181
- `if (${varName}.length > ${fixedCount}) ${issuesVar}.push({ path: ${path12}, code: 'INVALID_VALUE', expected: 'tuple length = ${fixedCount}', received: 'length ' + ${varName}.length, message: '\u5143\u7EC4\u957F\u5EA6\u8D85\u51FA\uFF0C\u671F\u671B ${fixedCount}\uFF0C\u5B9E\u9645 ' + ${varName}.length });`
1196
+ `if (${varName}.length > ${fixedCount}) ${issuesVar}.push({ path: ${path15}, code: 'INVALID_VALUE', expected: 'tuple length = ${fixedCount}', received: 'length ' + ${varName}.length, message: '\u5143\u7EC4\u957F\u5EA6\u8D85\u51FA\uFF0C\u671F\u671B ${fixedCount}\uFF0C\u5B9E\u9645 ' + ${varName}.length });`
1182
1197
  );
1183
1198
  }
1184
1199
  for (let i = 0; i < type.elements.length; i++) {
1185
1200
  const elem = type.elements[i];
1186
1201
  if (elem.rest) continue;
1187
- const elemPath = `${path12} + '[' + ${i} + ']'`;
1202
+ const elemPath = `${path15} + '[' + ${i} + ']'`;
1188
1203
  const elemAccess = `${varName}[${i}]`;
1189
1204
  if (elem.optional) {
1190
1205
  lines.push(`if (${i} < ${varName}.length && ${elemAccess} !== undefined) {`);
@@ -1195,7 +1210,7 @@ else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) {
1195
1210
  lines.push(`}`);
1196
1211
  }
1197
1212
  if (restElement) {
1198
- const restPath = `${path12} + '[' + ${indexVar} + ']'`;
1213
+ const restPath = `${path15} + '[' + ${indexVar} + ']'`;
1199
1214
  const restValidation = generateValueValidation(
1200
1215
  restElement.type,
1201
1216
  itemVar,
@@ -1213,23 +1228,23 @@ else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) {
1213
1228
  case "union": {
1214
1229
  const tempVar = `tempIssues_${Math.random().toString(36).slice(2, 8)}`;
1215
1230
  const memberChecks = type.members.map((member) => {
1216
- const check = generateValueValidation(member, varName, tempVar, ctx, path12);
1231
+ const check = generateValueValidation(member, varName, tempVar, ctx, path15);
1217
1232
  return `(() => { const ${tempVar} = []; ${check}; return ${tempVar}.length === 0; })()`;
1218
1233
  });
1219
1234
  const expected = runtimeTypeToExpected(type);
1220
- return `if (!(${memberChecks.join(" || ")})) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: ${JSON.stringify(expected)}, received: typeof ${varName}, message: '\u503C ' + JSON.stringify(${varName}) + ' \u4E0D\u5339\u914D\u8054\u5408\u7C7B\u578B ${expected.replace(/'/g, "\\'")}' });`;
1235
+ return `if (!(${memberChecks.join(" || ")})) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: ${JSON.stringify(expected)}, received: typeof ${varName}, message: '\u503C ' + JSON.stringify(${varName}) + ' \u4E0D\u5339\u914D\u8054\u5408\u7C7B\u578B ${expected.replace(/'/g, "\\'")}' });`;
1221
1236
  }
1222
1237
  case "record": {
1223
- const valuePath = `${path12} + '.' + key`;
1238
+ const valuePath = `${path15} + '.' + key`;
1224
1239
  const valueValidation = generateValueValidation(type.value, "val", issuesVar, ctx, valuePath);
1225
- return `if (typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'object', received: typeof ${varName}, message: '\u671F\u671B\u5BF9\u8C61\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
1240
+ return `if (typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'object', received: typeof ${varName}, message: '\u671F\u671B\u5BF9\u8C61\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
1226
1241
  else for (const [key, val] of Object.entries(${varName})) { ${valueValidation} }`;
1227
1242
  }
1228
1243
  case "object": {
1229
- return generateInlineObjectValidation(type, varName, issuesVar, ctx, path12);
1244
+ return generateInlineObjectValidation(type, varName, issuesVar, ctx, path15);
1230
1245
  }
1231
1246
  case "ref": {
1232
- return `validate_${type.name}(${varName}, ${path12}, ${issuesVar});`;
1247
+ return `validate_${type.name}(${varName}, ${path15}, ${issuesVar});`;
1233
1248
  }
1234
1249
  }
1235
1250
  }
@@ -1289,6 +1304,75 @@ var init_generateValidatorCode = __esm({
1289
1304
  }
1290
1305
  });
1291
1306
 
1307
+ // src/validator/schemaRegistry.ts
1308
+ var SchemaRegistry, schemaRegistry;
1309
+ var init_schemaRegistry = __esm({
1310
+ "src/validator/schemaRegistry.ts"() {
1311
+ "use strict";
1312
+ SchemaRegistry = class {
1313
+ manifest = /* @__PURE__ */ new Map();
1314
+ /**
1315
+ * 批量加载 manifest
1316
+ * 覆盖已有数据
1317
+ */
1318
+ loadManifest(manifest) {
1319
+ this.manifest.clear();
1320
+ for (const [filePath, fileSchemas] of manifest) {
1321
+ const copy = /* @__PURE__ */ new Map();
1322
+ fileSchemas.forEach((value, key) => copy.set(key, value));
1323
+ this.manifest.set(filePath, copy);
1324
+ }
1325
+ }
1326
+ /**
1327
+ * 查询单条 schema
1328
+ * @returns SchemaEntry | null | undefined
1329
+ * - SchemaEntry:有类型声明
1330
+ * - null:无类型声明(跳过校验)
1331
+ * - undefined:manifest 不完整(抛错)
1332
+ */
1333
+ get(filePath, schemaName) {
1334
+ const fileSchemas = this.manifest.get(filePath);
1335
+ if (!fileSchemas) return void 0;
1336
+ return fileSchemas.get(schemaName);
1337
+ }
1338
+ /**
1339
+ * 设置单个文件的所有 schema
1340
+ * 覆盖该文件的已有数据
1341
+ */
1342
+ set(filePath, schemas) {
1343
+ const copy = /* @__PURE__ */ new Map();
1344
+ schemas.forEach((value, key) => copy.set(key, value));
1345
+ this.manifest.set(filePath, copy);
1346
+ }
1347
+ /**
1348
+ * 删除单个文件(文件被删除时)
1349
+ */
1350
+ delete(filePath) {
1351
+ this.manifest.delete(filePath);
1352
+ }
1353
+ /**
1354
+ * 判断文件是否已注册
1355
+ */
1356
+ hasFile(filePath) {
1357
+ return this.manifest.has(filePath);
1358
+ }
1359
+ /**
1360
+ * 清空(测试用 / watch 全量重建前)
1361
+ */
1362
+ clear() {
1363
+ this.manifest.clear();
1364
+ }
1365
+ /**
1366
+ * 已注册的文件数量
1367
+ */
1368
+ get size() {
1369
+ return this.manifest.size;
1370
+ }
1371
+ };
1372
+ schemaRegistry = new SchemaRegistry();
1373
+ }
1374
+ });
1375
+
1292
1376
  // src/ast/createProgram.ts
1293
1377
  import ts4 from "typescript";
1294
1378
  function invalidateProgramCache() {
@@ -1521,9 +1605,17 @@ async function writeSchemaModule(entries, allTypesMap, outputPath) {
1521
1605
  await fs3.mkdir(path4.dirname(outputPath), { recursive: true });
1522
1606
  await fs3.writeFile(outputPath, source, "utf-8");
1523
1607
  }
1608
+ async function generateSchemaFile(routes, rootDir, outputPath) {
1609
+ const { sources, allTypesByFile } = collectRouteSchemaSources(routes, rootDir);
1610
+ const entries = sources.map(({ filePath, schemaName, typeInfo }) => ({
1611
+ filePath,
1612
+ schemaName,
1613
+ typeInfo
1614
+ }));
1615
+ await writeSchemaModule(entries, allTypesByFile, outputPath);
1616
+ }
1524
1617
  async function readManifestFile(inputPath) {
1525
- const fileUrl = `file://${inputPath}`;
1526
- const mod = await import(fileUrl);
1618
+ const mod = await importWithCacheBust(inputPath);
1527
1619
  const validators = mod.validators;
1528
1620
  const properties = mod.properties ?? {};
1529
1621
  const manifest = /* @__PURE__ */ new Map();
@@ -1546,27 +1638,51 @@ async function readManifestFile(inputPath) {
1546
1638
  }
1547
1639
  return manifest;
1548
1640
  }
1641
+ function remapManifestKeys(manifest, rootDir, prodDir) {
1642
+ const remapped = /* @__PURE__ */ new Map();
1643
+ const rootPrefix = rootDir + path4.sep;
1644
+ for (const [filePath, fileSchemas] of manifest) {
1645
+ let rel = filePath;
1646
+ if (filePath.startsWith(rootPrefix)) {
1647
+ rel = filePath.slice(rootPrefix.length);
1648
+ } else if (filePath.startsWith(rootDir)) {
1649
+ rel = filePath.slice(rootDir.length).replace(/^[/\\]/, "");
1650
+ }
1651
+ const prodRel = `${prodDir}/${rel.replace(/\.ts$/, ".js")}`;
1652
+ const prodAbs = path4.resolve(rootDir, prodRel);
1653
+ remapped.set(prodAbs, fileSchemas);
1654
+ }
1655
+ return remapped;
1656
+ }
1657
+ async function loadSchemaToRegistry(schemaPath, rootDir, prodDir, remap = true) {
1658
+ const manifest = await readManifestFile(schemaPath);
1659
+ const finalManifest = remap ? remapManifestKeys(manifest, rootDir, prodDir) : manifest;
1660
+ schemaRegistry.loadManifest(finalManifest);
1661
+ return finalManifest;
1662
+ }
1549
1663
  var init_generateSchema = __esm({
1550
1664
  "src/cli/generateSchema.ts"() {
1551
1665
  "use strict";
1552
1666
  init_generateValidatorCode();
1667
+ init_schemaRegistry();
1553
1668
  init_collectRouteSchemaSources();
1669
+ init_importWithCacheBust();
1554
1670
  }
1555
1671
  });
1556
1672
 
1557
1673
  // src/cli/generateRoutes.ts
1558
1674
  import fs4 from "fs";
1559
1675
  import path5 from "path";
1560
- function toProdFilePath(filePath) {
1676
+ function toProdFilePath(filePath, prodDir) {
1561
1677
  const jsPath = filePath.replace(/\.ts$/, ".js");
1562
- return jsPath.startsWith("dist/") ? jsPath : `dist/${jsPath}`;
1678
+ return jsPath.startsWith(`${prodDir}/`) ? jsPath : `${prodDir}/${jsPath}`;
1563
1679
  }
1564
- function serializeRoutes(routes, wsRoutes, rootDir) {
1680
+ function serializeRoutes(routes, wsRoutes, rootDir, prodDir = "dist") {
1565
1681
  const serialize = (route) => {
1566
- const middlewarePaths = extractMiddlewarePaths(route.filePath, rootDir);
1682
+ const middlewarePaths = extractMiddlewarePaths(route.filePath, rootDir, prodDir);
1567
1683
  const serialized = {
1568
1684
  urlPath: route.urlPath,
1569
- filePath: toProdFilePath(route.filePath),
1685
+ filePath: toProdFilePath(route.filePath, prodDir),
1570
1686
  paramNames: route.paramNames,
1571
1687
  isDynamic: route.isDynamic,
1572
1688
  isCatchAll: route.isCatchAll,
@@ -1582,21 +1698,21 @@ function serializeRoutes(routes, wsRoutes, rootDir) {
1582
1698
  wsRoutes: wsRoutes.map(serialize)
1583
1699
  };
1584
1700
  }
1585
- function extractMiddlewarePaths(routeFilePath, rootDir) {
1701
+ function extractMiddlewarePaths(routeFilePath, rootDir, prodDir) {
1586
1702
  const routeDir = path5.dirname(routeFilePath);
1587
1703
  const resolvedRoot = path5.resolve(rootDir);
1588
1704
  const paths = [];
1589
1705
  let currentDir = path5.resolve(rootDir, routeDir);
1590
1706
  while (true) {
1591
- for (const ext of [".ts", ".js"]) {
1592
- const mwPath = path5.join(currentDir, `middlewares${ext}`);
1593
- const absMwPath = path5.resolve(rootDir, mwPath);
1594
- if (fs4.existsSync(absMwPath)) {
1595
- const relMwPath = path5.relative(rootDir, absMwPath);
1596
- const prodAbsPath = path5.resolve(rootDir, toProdFilePath(relMwPath));
1597
- paths.push(prodAbsPath);
1598
- break;
1599
- }
1707
+ const mwTsPath = path5.join(currentDir, "middlewares.ts");
1708
+ const mwJsPath = path5.join(currentDir, "middlewares.js");
1709
+ const absTsPath = path5.resolve(rootDir, mwTsPath);
1710
+ const absJsPath = path5.resolve(rootDir, mwJsPath);
1711
+ const absMwPath = fs4.existsSync(absTsPath) ? absTsPath : fs4.existsSync(absJsPath) ? absJsPath : null;
1712
+ if (absMwPath) {
1713
+ const relMwPath = path5.relative(rootDir, absMwPath);
1714
+ const prodAbsPath = path5.resolve(rootDir, toProdFilePath(relMwPath, prodDir));
1715
+ paths.push(prodAbsPath);
1600
1716
  }
1601
1717
  if (currentDir === resolvedRoot) break;
1602
1718
  const parentDir = path5.dirname(currentDir);
@@ -1669,23 +1785,184 @@ var init_generateRoutes = __esm({
1669
1785
  }
1670
1786
  });
1671
1787
 
1788
+ // src/utils/readTsconfig.ts
1789
+ import ts6 from "typescript";
1790
+ import path6 from "path";
1791
+ import fs5 from "fs";
1792
+ function readTsconfig(rootDir) {
1793
+ const tsconfigPath = path6.resolve(rootDir, "tsconfig.json");
1794
+ if (!fs5.existsSync(tsconfigPath)) return null;
1795
+ const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
1796
+ if (configFile.error || !configFile.config) return null;
1797
+ const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
1798
+ const baseUrl = parsed.options.baseUrl ?? rootDir;
1799
+ const rawPaths = parsed.options.paths;
1800
+ if (!rawPaths) return null;
1801
+ const paths = {};
1802
+ for (const [pattern, targets] of Object.entries(rawPaths)) {
1803
+ paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
1804
+ }
1805
+ return { baseUrl, paths };
1806
+ }
1807
+ var init_readTsconfig = __esm({
1808
+ "src/utils/readTsconfig.ts"() {
1809
+ "use strict";
1810
+ }
1811
+ });
1812
+
1813
+ // src/utils/resolveAlias.ts
1814
+ function resolveAlias(specifier, config) {
1815
+ const candidates = [];
1816
+ for (const [pattern, targets] of Object.entries(config.paths)) {
1817
+ const wildcardIndex = pattern.indexOf("*");
1818
+ if (wildcardIndex === -1) {
1819
+ if (specifier === pattern) {
1820
+ candidates.push(...targets);
1821
+ }
1822
+ continue;
1823
+ }
1824
+ const prefix = pattern.slice(0, wildcardIndex);
1825
+ const suffix = pattern.slice(wildcardIndex + 1);
1826
+ if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
1827
+ const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
1828
+ for (const target of targets) {
1829
+ candidates.push(target.replace("*", captured));
1830
+ }
1831
+ }
1832
+ }
1833
+ return candidates;
1834
+ }
1835
+ var init_resolveAlias = __esm({
1836
+ "src/utils/resolveAlias.ts"() {
1837
+ "use strict";
1838
+ }
1839
+ });
1840
+
1841
+ // src/cli/compileRoutes.ts
1842
+ import path7 from "path";
1843
+ import fs6 from "fs";
1844
+ import fg2 from "fast-glob";
1845
+ function toProdExtension(filePath) {
1846
+ if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
1847
+ if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
1848
+ if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
1849
+ return filePath;
1850
+ }
1851
+ function toProdImportPath(sourceFile, importer) {
1852
+ const importerDir = path7.dirname(importer);
1853
+ let rel = path7.relative(importerDir, sourceFile);
1854
+ rel = rel.split(path7.sep).join("/");
1855
+ if (!rel.startsWith(".")) rel = "./" + rel;
1856
+ return toProdExtension(rel);
1857
+ }
1858
+ function createAliasPlugin(config) {
1859
+ const EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1860
+ const INDEX_EXTS = [
1861
+ "/index.ts",
1862
+ "/index.tsx",
1863
+ "/index.js",
1864
+ "/index.jsx",
1865
+ "/index.mjs",
1866
+ "/index.cjs"
1867
+ ];
1868
+ const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
1869
+ return {
1870
+ name: "faapi-alias",
1871
+ setup(build) {
1872
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
1873
+ let source;
1874
+ try {
1875
+ source = fs6.readFileSync(args.path, "utf8");
1876
+ } catch {
1877
+ return void 0;
1878
+ }
1879
+ const importer = args.path;
1880
+ let modified = false;
1881
+ const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
1882
+ if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
1883
+ return full;
1884
+ }
1885
+ const candidates = resolveAlias(specifier, config);
1886
+ for (const candidate of candidates) {
1887
+ for (const ext of EXTS) {
1888
+ const file = candidate + ext;
1889
+ if (fs6.existsSync(file)) {
1890
+ modified = true;
1891
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1892
+ }
1893
+ }
1894
+ for (const indexExt of INDEX_EXTS) {
1895
+ const file = candidate + indexExt;
1896
+ if (fs6.existsSync(file)) {
1897
+ modified = true;
1898
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1899
+ }
1900
+ }
1901
+ }
1902
+ return full;
1903
+ });
1904
+ if (!modified) return void 0;
1905
+ return { contents: newSource, loader: "default" };
1906
+ });
1907
+ }
1908
+ };
1909
+ }
1910
+ async function compileRoutes(options) {
1911
+ const { rootDir, appDir, outDir, files, logLevel = "silent" } = options;
1912
+ const allFiles = files ?? await fg2([`${appDir}/**/*.ts`], {
1913
+ cwd: rootDir,
1914
+ onlyFiles: true,
1915
+ absolute: true,
1916
+ ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
1917
+ });
1918
+ if (allFiles.length === 0) {
1919
+ return { compiledFiles: [] };
1920
+ }
1921
+ const absOutDir = path7.resolve(rootDir, outDir);
1922
+ await fs6.promises.mkdir(absOutDir, { recursive: true });
1923
+ const tsconfig = readTsconfig(rootDir);
1924
+ const plugins = tsconfig ? [createAliasPlugin(tsconfig)] : [];
1925
+ const esbuild = await import("esbuild");
1926
+ await esbuild.build({
1927
+ entryPoints: allFiles,
1928
+ outdir: absOutDir,
1929
+ outbase: rootDir,
1930
+ bundle: false,
1931
+ platform: "node",
1932
+ format: "esm",
1933
+ sourcemap: true,
1934
+ packages: "external",
1935
+ plugins,
1936
+ logLevel
1937
+ });
1938
+ return { compiledFiles: allFiles };
1939
+ }
1940
+ var init_compileRoutes = __esm({
1941
+ "src/cli/compileRoutes.ts"() {
1942
+ "use strict";
1943
+ init_readTsconfig();
1944
+ init_resolveAlias();
1945
+ }
1946
+ });
1947
+
1672
1948
  // src/cli/buildCommand.ts
1673
1949
  var buildCommand_exports = {};
1674
1950
  __export(buildCommand_exports, {
1675
1951
  buildCommand: () => buildCommand,
1676
1952
  parseBuildArgs: () => parseBuildArgs
1677
1953
  });
1678
- import path6 from "path";
1679
- import fs5 from "fs";
1680
- import fg2 from "fast-glob";
1954
+ import path8 from "path";
1681
1955
  async function buildCommand(options) {
1682
1956
  const { rootDir, patterns, appDir, outdir, types } = options;
1683
1957
  console.log("faapi build started");
1684
1958
  console.log(`- Root: ${rootDir}`);
1685
1959
  console.log(`- Patterns: ${patterns.join(", ")}`);
1686
1960
  console.log(`- Output: ${outdir}`);
1687
- console.log("\n[1/5] Scanning routes...");
1688
- const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir);
1961
+ console.log("\n[1/5] Compiling TypeScript...");
1962
+ const result = await compileRoutes({ rootDir, appDir, outDir: outdir, logLevel: "silent" });
1963
+ console.log(` Compiled ${result.compiledFiles.length} file(s)`);
1964
+ console.log("\n[2/5] Scanning routes...");
1965
+ const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir, outdir);
1689
1966
  const sorted = sortRoutes(routes);
1690
1967
  console.log(` Found ${sorted.length} routes, ${wsRoutes.length} WS routes`);
1691
1968
  const conflicts = detectRouteConflicts(sorted);
@@ -1698,62 +1975,21 @@ async function buildCommand(options) {
1698
1975
  }
1699
1976
  }
1700
1977
  }
1701
- console.log("\n[2/5] Generating types...");
1702
- const typesPath = types ? path6.resolve(rootDir, types) : path6.resolve(rootDir, "faapi-types.ts");
1978
+ console.log("\n[3/5] Generating types...");
1979
+ const typesPath = types ? path8.resolve(rootDir, types) : path8.resolve(rootDir, "faapi-types.ts");
1703
1980
  await generateTypes(sorted, rootDir, typesPath);
1704
1981
  console.log(` Written to ${typesPath}`);
1705
- console.log("\n[3/5] Generating schema module...");
1706
- const schemaPath = path6.resolve(rootDir, outdir, "faapi-schema.js");
1707
- const { entries, allTypesByFile } = extractSchemaEntries(sorted, rootDir);
1708
- await writeSchemaModule(entries, allTypesByFile, schemaPath);
1982
+ console.log("\n[4/5] Generating schema module...");
1983
+ const schemaPath = path8.resolve(rootDir, outdir, "faapi-schema.js");
1984
+ await generateSchemaFile(sorted, rootDir, schemaPath);
1709
1985
  console.log(` Written to ${schemaPath}`);
1710
- console.log("\n[4/5] Generating routes manifest...");
1711
- const routesPath = path6.resolve(rootDir, outdir, "faapi-routes.js");
1712
- const serialized = serializeRoutes(sorted, wsRoutes, rootDir);
1986
+ console.log("\n[5/5] Generating routes manifest...");
1987
+ const routesPath = path8.resolve(rootDir, outdir, "faapi-routes.js");
1988
+ const serialized = serializeRoutes(sorted, wsRoutes, rootDir, outdir);
1713
1989
  await writeRoutesModule(serialized, routesPath);
1714
1990
  console.log(` Written to ${routesPath}`);
1715
- console.log("\n[5/5] Compiling TypeScript...");
1716
- await compileTypeScript(rootDir, patterns, appDir, outdir);
1717
- console.log(" Done");
1718
1991
  console.log("\nfaapi build completed");
1719
1992
  }
1720
- async function compileTypeScript(rootDir, patterns, appDir, outdir) {
1721
- const esbuild = await import("esbuild");
1722
- const allPatterns = [...patterns, `${appDir}/**/middlewares.ts`];
1723
- const files = await fg2(allPatterns, {
1724
- cwd: rootDir,
1725
- onlyFiles: true,
1726
- absolute: true
1727
- });
1728
- if (files.length === 0) {
1729
- console.log(" No TypeScript files to compile");
1730
- return;
1731
- }
1732
- const absOutdir = path6.resolve(rootDir, outdir);
1733
- await fs5.promises.mkdir(absOutdir, { recursive: true });
1734
- await esbuild.build({
1735
- entryPoints: files,
1736
- outdir: absOutdir,
1737
- outbase: rootDir,
1738
- bundle: false,
1739
- // 不 bundle,逐文件编译
1740
- platform: "node",
1741
- format: "esm",
1742
- sourcemap: true,
1743
- packages: "external",
1744
- // 依赖保持外部引用
1745
- logLevel: "info"
1746
- });
1747
- }
1748
- function extractSchemaEntries(routes, rootDir) {
1749
- const { sources, allTypesByFile } = collectRouteSchemaSources(routes, rootDir);
1750
- const entries = sources.map(({ filePath, schemaName, typeInfo }) => ({
1751
- filePath,
1752
- schemaName,
1753
- typeInfo
1754
- }));
1755
- return { entries, allTypesByFile };
1756
- }
1757
1993
  function parseBuildArgs(argv) {
1758
1994
  const rootDir = process.cwd();
1759
1995
  let outdir = "dist";
@@ -1790,7 +2026,7 @@ var init_buildCommand = __esm({
1790
2026
  init_generateTypes();
1791
2027
  init_generateSchema();
1792
2028
  init_generateRoutes();
1793
- init_collectRouteSchemaSources();
2029
+ init_compileRoutes();
1794
2030
  }
1795
2031
  });
1796
2032
 
@@ -2441,42 +2677,42 @@ var init_parseArgs = __esm({
2441
2677
  });
2442
2678
 
2443
2679
  // src/router/matchRoute.ts
2444
- function matchRoute(routes, method, path12) {
2680
+ function matchRoute(routes, method, path15) {
2445
2681
  for (const route of routes) {
2446
2682
  if (route.method !== method) {
2447
2683
  continue;
2448
2684
  }
2449
2685
  if (!route.isDynamic) {
2450
- if (route.urlPath === path12) {
2686
+ if (route.urlPath === path15) {
2451
2687
  return { route, params: {} };
2452
2688
  }
2453
2689
  continue;
2454
2690
  }
2455
- const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
2691
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2456
2692
  if (params !== null) {
2457
2693
  return { route, params };
2458
2694
  }
2459
2695
  }
2460
2696
  return null;
2461
2697
  }
2462
- function matchWsRoute(wsRoutes, path12) {
2698
+ function matchWsRoute(wsRoutes, path15) {
2463
2699
  for (const route of wsRoutes) {
2464
2700
  if (!route.isDynamic) {
2465
- if (route.urlPath === path12) {
2701
+ if (route.urlPath === path15) {
2466
2702
  return { route, params: {} };
2467
2703
  }
2468
2704
  continue;
2469
2705
  }
2470
- const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
2706
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2471
2707
  if (params !== null) {
2472
2708
  return { route, params };
2473
2709
  }
2474
2710
  }
2475
2711
  return null;
2476
2712
  }
2477
- function matchDynamicPath(pattern, path12, paramNames, isCatchAll) {
2713
+ function matchDynamicPath(pattern, path15, paramNames, isCatchAll) {
2478
2714
  const patternSegments = pattern.split("/").filter(Boolean);
2479
- const pathSegments = path12.split("/").filter(Boolean);
2715
+ const pathSegments = path15.split("/").filter(Boolean);
2480
2716
  if (isCatchAll) {
2481
2717
  const nonCatchAllCount = patternSegments.length - 1;
2482
2718
  if (pathSegments.length <= nonCatchAllCount) {
@@ -2895,14 +3131,14 @@ var init_httpErrors = __esm({
2895
3131
  issues;
2896
3132
  };
2897
3133
  RouteNotFoundError = class extends FaapiError {
2898
- constructor(path12) {
2899
- super("ROUTE_NOT_FOUND", `Route not found: ${path12}`, 404);
3134
+ constructor(path15) {
3135
+ super("ROUTE_NOT_FOUND", `Route not found: ${path15}`, 404);
2900
3136
  this.name = "RouteNotFoundError";
2901
3137
  }
2902
3138
  };
2903
3139
  MethodNotAllowedError = class extends FaapiError {
2904
- constructor(method, path12, allowedMethods) {
2905
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path12}`, 405);
3140
+ constructor(method, path15, allowedMethods) {
3141
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path15}`, 405);
2906
3142
  this.allowedMethods = allowedMethods;
2907
3143
  this.name = "MethodNotAllowedError";
2908
3144
  }
@@ -3261,75 +3497,6 @@ var init_sendNodeResponse = __esm({
3261
3497
  }
3262
3498
  });
3263
3499
 
3264
- // src/validator/schemaRegistry.ts
3265
- var SchemaRegistry, schemaRegistry;
3266
- var init_schemaRegistry = __esm({
3267
- "src/validator/schemaRegistry.ts"() {
3268
- "use strict";
3269
- SchemaRegistry = class {
3270
- manifest = /* @__PURE__ */ new Map();
3271
- /**
3272
- * 批量加载 manifest
3273
- * 覆盖已有数据
3274
- */
3275
- loadManifest(manifest) {
3276
- this.manifest.clear();
3277
- for (const [filePath, fileSchemas] of manifest) {
3278
- const copy = /* @__PURE__ */ new Map();
3279
- fileSchemas.forEach((value, key) => copy.set(key, value));
3280
- this.manifest.set(filePath, copy);
3281
- }
3282
- }
3283
- /**
3284
- * 查询单条 schema
3285
- * @returns SchemaEntry | null | undefined
3286
- * - SchemaEntry:有类型声明
3287
- * - null:无类型声明(跳过校验)
3288
- * - undefined:manifest 不完整(抛错)
3289
- */
3290
- get(filePath, schemaName) {
3291
- const fileSchemas = this.manifest.get(filePath);
3292
- if (!fileSchemas) return void 0;
3293
- return fileSchemas.get(schemaName);
3294
- }
3295
- /**
3296
- * 设置单个文件的所有 schema
3297
- * 覆盖该文件的已有数据
3298
- */
3299
- set(filePath, schemas) {
3300
- const copy = /* @__PURE__ */ new Map();
3301
- schemas.forEach((value, key) => copy.set(key, value));
3302
- this.manifest.set(filePath, copy);
3303
- }
3304
- /**
3305
- * 删除单个文件(文件被删除时)
3306
- */
3307
- delete(filePath) {
3308
- this.manifest.delete(filePath);
3309
- }
3310
- /**
3311
- * 判断文件是否已注册
3312
- */
3313
- hasFile(filePath) {
3314
- return this.manifest.has(filePath);
3315
- }
3316
- /**
3317
- * 清空(测试用 / watch 全量重建前)
3318
- */
3319
- clear() {
3320
- this.manifest.clear();
3321
- }
3322
- /**
3323
- * 已注册的文件数量
3324
- */
3325
- get size() {
3326
- return this.manifest.size;
3327
- }
3328
- };
3329
- schemaRegistry = new SchemaRegistry();
3330
- }
3331
- });
3332
-
3333
3500
  // src/validator/coerceInput.ts
3334
3501
  function coerceInput(input, properties) {
3335
3502
  const issues = [];
@@ -3342,16 +3509,16 @@ function coerceInput(input, properties) {
3342
3509
  }
3343
3510
  return { data, issues };
3344
3511
  }
3345
- function coerceValue(value, type, path12, issues) {
3512
+ function coerceValue(value, type, path15, issues) {
3346
3513
  switch (type.kind) {
3347
3514
  case "number":
3348
3515
  if (typeof value === "string") {
3349
- return coerceStringToNumber(value, path12, issues);
3516
+ return coerceStringToNumber(value, path15, issues);
3350
3517
  }
3351
3518
  return value;
3352
3519
  case "boolean":
3353
3520
  if (typeof value === "string") {
3354
- return coerceStringToBoolean(value, path12, issues);
3521
+ return coerceStringToBoolean(value, path15, issues);
3355
3522
  }
3356
3523
  return value;
3357
3524
  case "array":
@@ -3359,7 +3526,7 @@ function coerceValue(value, type, path12, issues) {
3359
3526
  return value;
3360
3527
  }
3361
3528
  if (Array.isArray(value)) {
3362
- return value.map((item, i) => coerceValue(item, type.element, `${path12}[${i}]`, issues));
3529
+ return value.map((item, i) => coerceValue(item, type.element, `${path15}[${i}]`, issues));
3363
3530
  }
3364
3531
  return value;
3365
3532
  case "tuple": {
@@ -3368,10 +3535,10 @@ function coerceValue(value, type, path12, issues) {
3368
3535
  return value.map((item, i) => {
3369
3536
  const elem = type.elements[i];
3370
3537
  if (elem && !elem.rest) {
3371
- return coerceValue(item, elem.type, `${path12}[${i}]`, issues);
3538
+ return coerceValue(item, elem.type, `${path15}[${i}]`, issues);
3372
3539
  }
3373
3540
  if (restElement) {
3374
- return coerceValue(item, restElement.type, `${path12}[${i}]`, issues);
3541
+ return coerceValue(item, restElement.type, `${path15}[${i}]`, issues);
3375
3542
  }
3376
3543
  return item;
3377
3544
  });
@@ -3379,7 +3546,7 @@ function coerceValue(value, type, path12, issues) {
3379
3546
  case "union":
3380
3547
  for (const member of type.members) {
3381
3548
  const tempIssues = [];
3382
- const coerced = coerceValue(value, member, path12, tempIssues);
3549
+ const coerced = coerceValue(value, member, path15, tempIssues);
3383
3550
  if (tempIssues.length === 0) {
3384
3551
  return coerced;
3385
3552
  }
@@ -3400,7 +3567,7 @@ function coerceValue(value, type, path12, issues) {
3400
3567
  result[prop.name] = coerceValue(
3401
3568
  obj[prop.name],
3402
3569
  prop.type,
3403
- `${path12}.${prop.name}`,
3570
+ `${path15}.${prop.name}`,
3404
3571
  issues
3405
3572
  );
3406
3573
  }
@@ -3412,31 +3579,31 @@ function coerceValue(value, type, path12, issues) {
3412
3579
  return value;
3413
3580
  }
3414
3581
  }
3415
- function coerceStringToNumber(value, path12, issues) {
3582
+ function coerceStringToNumber(value, path15, issues) {
3416
3583
  if (value.trim() === "") {
3417
3584
  issues.push({
3418
- path: path12,
3585
+ path: path15,
3419
3586
  code: "COERCE_FAILED",
3420
3587
  expected: "number",
3421
3588
  received: "string",
3422
- message: `\u5B57\u6BB5 "${path12}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3589
+ message: `\u5B57\u6BB5 "${path15}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3423
3590
  });
3424
3591
  return value;
3425
3592
  }
3426
3593
  const num = Number(value);
3427
3594
  if (Number.isNaN(num)) {
3428
3595
  issues.push({
3429
- path: path12,
3596
+ path: path15,
3430
3597
  code: "COERCE_FAILED",
3431
3598
  expected: "number",
3432
3599
  received: "string",
3433
- message: `\u5B57\u6BB5 "${path12}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3600
+ message: `\u5B57\u6BB5 "${path15}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3434
3601
  });
3435
3602
  return value;
3436
3603
  }
3437
3604
  return num;
3438
3605
  }
3439
- function coerceStringToBoolean(value, path12, issues) {
3606
+ function coerceStringToBoolean(value, path15, issues) {
3440
3607
  if (value === "true" || value === "1") {
3441
3608
  return true;
3442
3609
  }
@@ -3444,11 +3611,11 @@ function coerceStringToBoolean(value, path12, issues) {
3444
3611
  return false;
3445
3612
  }
3446
3613
  issues.push({
3447
- path: path12,
3614
+ path: path15,
3448
3615
  code: "COERCE_FAILED",
3449
3616
  expected: "boolean",
3450
3617
  received: "string",
3451
- message: `\u5B57\u6BB5 "${path12}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A boolean`
3618
+ message: `\u5B57\u6BB5 "${path15}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A boolean`
3452
3619
  });
3453
3620
  return value;
3454
3621
  }
@@ -3588,26 +3755,26 @@ var init_cors = __esm({
3588
3755
  });
3589
3756
 
3590
3757
  // src/server/serveStatic.ts
3591
- import fs6 from "fs";
3592
- import path7 from "path";
3758
+ import fs7 from "fs";
3759
+ import path9 from "path";
3593
3760
  import { createReadStream } from "fs";
3594
3761
  import { Readable as Readable2 } from "stream";
3595
3762
  async function serveStatic(urlPath, staticDir) {
3596
- const resolved = path7.resolve(staticDir, "." + urlPath);
3597
- const relative3 = path7.relative(staticDir, resolved);
3598
- if (relative3.startsWith("..") || path7.isAbsolute(relative3)) {
3763
+ const resolved = path9.resolve(staticDir, "." + urlPath);
3764
+ const relative3 = path9.relative(staticDir, resolved);
3765
+ if (relative3.startsWith("..") || path9.isAbsolute(relative3)) {
3599
3766
  return null;
3600
3767
  }
3601
3768
  let stat4;
3602
3769
  try {
3603
- stat4 = await fs6.promises.stat(resolved);
3770
+ stat4 = await fs7.promises.stat(resolved);
3604
3771
  } catch {
3605
3772
  return null;
3606
3773
  }
3607
3774
  if (stat4.isDirectory()) {
3608
- const indexPath = path7.join(resolved, "index.html");
3775
+ const indexPath = path9.join(resolved, "index.html");
3609
3776
  try {
3610
- const indexStat = await fs6.promises.stat(indexPath);
3777
+ const indexStat = await fs7.promises.stat(indexPath);
3611
3778
  if (indexStat.isFile()) {
3612
3779
  return serveFile(indexPath, indexStat.size);
3613
3780
  }
@@ -3621,7 +3788,7 @@ async function serveStatic(urlPath, staticDir) {
3621
3788
  return serveFile(resolved, stat4.size);
3622
3789
  }
3623
3790
  function serveFile(filePath, size) {
3624
- const ext = path7.extname(filePath).toLowerCase();
3791
+ const ext = path9.extname(filePath).toLowerCase();
3625
3792
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
3626
3793
  const stream = createReadStream(filePath);
3627
3794
  const webStream = Readable2.toWeb(stream);
@@ -3821,7 +3988,7 @@ var init_wsHandler = __esm({
3821
3988
 
3822
3989
  // src/server/handleWsUpgrade.ts
3823
3990
  import { WebSocketServer, WebSocket } from "ws";
3824
- import path8 from "path";
3991
+ import path10 from "path";
3825
3992
  function getPathname(req) {
3826
3993
  const url = req.url ?? "/";
3827
3994
  const idx = url.indexOf("?");
@@ -3904,7 +4071,7 @@ function attachWebSocket(options) {
3904
4071
  const finalHandler = async () => {
3905
4072
  let handlers;
3906
4073
  try {
3907
- const absoluteFilePath = path8.resolve(rootDir, route.filePath);
4074
+ const absoluteFilePath = path10.resolve(rootDir, route.filePath);
3908
4075
  handlers = await loadWsHandler(absoluteFilePath, ctx);
3909
4076
  } catch (err) {
3910
4077
  const reason = err instanceof Error ? err.message : String(err);
@@ -3966,7 +4133,7 @@ import {
3966
4133
  createServer as createHttpServer
3967
4134
  } from "http";
3968
4135
  import { Readable as Readable3 } from "stream";
3969
- import path9 from "path";
4136
+ import path11 from "path";
3970
4137
  function toWebRequest(req) {
3971
4138
  const forwardedProto = req.headers["x-forwarded-proto"];
3972
4139
  const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
@@ -4010,15 +4177,15 @@ function limitStreamSize(stream, maxSize) {
4010
4177
  }
4011
4178
  });
4012
4179
  }
4013
- function findAllowedMethods(routes, path12) {
4180
+ function findAllowedMethods(routes, path15) {
4014
4181
  const methods = /* @__PURE__ */ new Set();
4015
4182
  for (const route of routes) {
4016
- if (route.urlPath === path12) {
4183
+ if (route.urlPath === path15) {
4017
4184
  methods.add(route.method);
4018
4185
  continue;
4019
4186
  }
4020
4187
  if (route.isDynamic) {
4021
- const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
4188
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
4022
4189
  if (params !== null) {
4023
4190
  methods.add(route.method);
4024
4191
  }
@@ -4085,7 +4252,7 @@ async function handleRequest(routes, rootDir, req, res, corsMiddleware, staticDi
4085
4252
  const match = matchRoute(routes, method, urlPath);
4086
4253
  if (!match) {
4087
4254
  if (staticDir) {
4088
- const absStaticDir = path9.resolve(rootDir, staticDir);
4255
+ const absStaticDir = path11.resolve(rootDir, staticDir);
4089
4256
  const staticResponse = await serveStatic(urlPath, absStaticDir);
4090
4257
  if (staticResponse) {
4091
4258
  return mergeMeta(staticResponse, meta);
@@ -4099,7 +4266,7 @@ async function handleRequest(routes, rootDir, req, res, corsMiddleware, staticDi
4099
4266
  }
4100
4267
  ctx.params = match.params;
4101
4268
  const { route } = match;
4102
- const absoluteFilePath = path9.resolve(rootDir, route.filePath);
4269
+ const absoluteFilePath = path11.resolve(rootDir, route.filePath);
4103
4270
  const routeModule = await loadRouteModule(absoluteFilePath, route.method);
4104
4271
  const input = await resolveInput(route.method, request);
4105
4272
  const inputType = getInputTypeForMethod(route.method);
@@ -4354,7 +4521,7 @@ var init_esm = __esm({
4354
4521
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
4355
4522
  const statMethod = opts.lstat ? lstat : stat;
4356
4523
  if (wantBigintFsStats) {
4357
- this._stat = (path12) => statMethod(path12, { bigint: true });
4524
+ this._stat = (path15) => statMethod(path15, { bigint: true });
4358
4525
  } else {
4359
4526
  this._stat = statMethod;
4360
4527
  }
@@ -4379,8 +4546,8 @@ var init_esm = __esm({
4379
4546
  const par = this.parent;
4380
4547
  const fil = par && par.files;
4381
4548
  if (fil && fil.length > 0) {
4382
- const { path: path12, depth } = par;
4383
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
4549
+ const { path: path15, depth } = par;
4550
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path15));
4384
4551
  const awaited = await Promise.all(slice);
4385
4552
  for (const entry of awaited) {
4386
4553
  if (!entry)
@@ -4420,20 +4587,20 @@ var init_esm = __esm({
4420
4587
  this.reading = false;
4421
4588
  }
4422
4589
  }
4423
- async _exploreDir(path12, depth) {
4590
+ async _exploreDir(path15, depth) {
4424
4591
  let files;
4425
4592
  try {
4426
- files = await readdir(path12, this._rdOptions);
4593
+ files = await readdir(path15, this._rdOptions);
4427
4594
  } catch (error) {
4428
4595
  this._onError(error);
4429
4596
  }
4430
- return { files, depth, path: path12 };
4597
+ return { files, depth, path: path15 };
4431
4598
  }
4432
- async _formatEntry(dirent, path12) {
4599
+ async _formatEntry(dirent, path15) {
4433
4600
  let entry;
4434
4601
  const basename3 = this._isDirent ? dirent.name : dirent;
4435
4602
  try {
4436
- const fullPath = presolve(pjoin(path12, basename3));
4603
+ const fullPath = presolve(pjoin(path15, basename3));
4437
4604
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
4438
4605
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
4439
4606
  } catch (err) {
@@ -4494,16 +4661,16 @@ import { watchFile, unwatchFile, watch as fs_watch } from "fs";
4494
4661
  import { open, stat as stat2, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
4495
4662
  import * as sysPath from "path";
4496
4663
  import { type as osType } from "os";
4497
- function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
4664
+ function createFsWatchInstance(path15, options, listener, errHandler, emitRaw) {
4498
4665
  const handleEvent = (rawEvent, evPath) => {
4499
- listener(path12);
4500
- emitRaw(rawEvent, evPath, { watchedPath: path12 });
4501
- if (evPath && path12 !== evPath) {
4502
- fsWatchBroadcast(sysPath.resolve(path12, evPath), KEY_LISTENERS, sysPath.join(path12, evPath));
4666
+ listener(path15);
4667
+ emitRaw(rawEvent, evPath, { watchedPath: path15 });
4668
+ if (evPath && path15 !== evPath) {
4669
+ fsWatchBroadcast(sysPath.resolve(path15, evPath), KEY_LISTENERS, sysPath.join(path15, evPath));
4503
4670
  }
4504
4671
  };
4505
4672
  try {
4506
- return fs_watch(path12, {
4673
+ return fs_watch(path15, {
4507
4674
  persistent: options.persistent
4508
4675
  }, handleEvent);
4509
4676
  } catch (error) {
@@ -4848,12 +5015,12 @@ var init_handler = __esm({
4848
5015
  listener(val1, val2, val3);
4849
5016
  });
4850
5017
  };
4851
- setFsWatchListener = (path12, fullPath, options, handlers) => {
5018
+ setFsWatchListener = (path15, fullPath, options, handlers) => {
4852
5019
  const { listener, errHandler, rawEmitter } = handlers;
4853
5020
  let cont = FsWatchInstances.get(fullPath);
4854
5021
  let watcher;
4855
5022
  if (!options.persistent) {
4856
- watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
5023
+ watcher = createFsWatchInstance(path15, options, listener, errHandler, rawEmitter);
4857
5024
  if (!watcher)
4858
5025
  return;
4859
5026
  return watcher.close.bind(watcher);
@@ -4864,7 +5031,7 @@ var init_handler = __esm({
4864
5031
  addAndConvert(cont, KEY_RAW, rawEmitter);
4865
5032
  } else {
4866
5033
  watcher = createFsWatchInstance(
4867
- path12,
5034
+ path15,
4868
5035
  options,
4869
5036
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
4870
5037
  errHandler,
@@ -4879,7 +5046,7 @@ var init_handler = __esm({
4879
5046
  cont.watcherUnusable = true;
4880
5047
  if (isWindows && error.code === "EPERM") {
4881
5048
  try {
4882
- const fd = await open(path12, "r");
5049
+ const fd = await open(path15, "r");
4883
5050
  await fd.close();
4884
5051
  broadcastErr(error);
4885
5052
  } catch (err) {
@@ -4910,7 +5077,7 @@ var init_handler = __esm({
4910
5077
  };
4911
5078
  };
4912
5079
  FsWatchFileInstances = /* @__PURE__ */ new Map();
4913
- setFsWatchFileListener = (path12, fullPath, options, handlers) => {
5080
+ setFsWatchFileListener = (path15, fullPath, options, handlers) => {
4914
5081
  const { listener, rawEmitter } = handlers;
4915
5082
  let cont = FsWatchFileInstances.get(fullPath);
4916
5083
  const copts = cont && cont.options;
@@ -4932,7 +5099,7 @@ var init_handler = __esm({
4932
5099
  });
4933
5100
  const currmtime = curr.mtimeMs;
4934
5101
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
4935
- foreach(cont.listeners, (listener2) => listener2(path12, curr));
5102
+ foreach(cont.listeners, (listener2) => listener2(path15, curr));
4936
5103
  }
4937
5104
  })
4938
5105
  };
@@ -4960,13 +5127,13 @@ var init_handler = __esm({
4960
5127
  * @param listener on fs change
4961
5128
  * @returns closer for the watcher instance
4962
5129
  */
4963
- _watchWithNodeFs(path12, listener) {
5130
+ _watchWithNodeFs(path15, listener) {
4964
5131
  const opts = this.fsw.options;
4965
- const directory = sysPath.dirname(path12);
4966
- const basename3 = sysPath.basename(path12);
5132
+ const directory = sysPath.dirname(path15);
5133
+ const basename3 = sysPath.basename(path15);
4967
5134
  const parent = this.fsw._getWatchedDir(directory);
4968
5135
  parent.add(basename3);
4969
- const absolutePath = sysPath.resolve(path12);
5136
+ const absolutePath = sysPath.resolve(path15);
4970
5137
  const options = {
4971
5138
  persistent: opts.persistent
4972
5139
  };
@@ -4976,12 +5143,12 @@ var init_handler = __esm({
4976
5143
  if (opts.usePolling) {
4977
5144
  const enableBin = opts.interval !== opts.binaryInterval;
4978
5145
  options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
4979
- closer = setFsWatchFileListener(path12, absolutePath, options, {
5146
+ closer = setFsWatchFileListener(path15, absolutePath, options, {
4980
5147
  listener,
4981
5148
  rawEmitter: this.fsw._emitRaw
4982
5149
  });
4983
5150
  } else {
4984
- closer = setFsWatchListener(path12, absolutePath, options, {
5151
+ closer = setFsWatchListener(path15, absolutePath, options, {
4985
5152
  listener,
4986
5153
  errHandler: this._boundHandleError,
4987
5154
  rawEmitter: this.fsw._emitRaw
@@ -5003,7 +5170,7 @@ var init_handler = __esm({
5003
5170
  let prevStats = stats;
5004
5171
  if (parent.has(basename3))
5005
5172
  return;
5006
- const listener = async (path12, newStats) => {
5173
+ const listener = async (path15, newStats) => {
5007
5174
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
5008
5175
  return;
5009
5176
  if (!newStats || newStats.mtimeMs === 0) {
@@ -5017,11 +5184,11 @@ var init_handler = __esm({
5017
5184
  this.fsw._emit(EV.CHANGE, file, newStats2);
5018
5185
  }
5019
5186
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
5020
- this.fsw._closeFile(path12);
5187
+ this.fsw._closeFile(path15);
5021
5188
  prevStats = newStats2;
5022
5189
  const closer2 = this._watchWithNodeFs(file, listener);
5023
5190
  if (closer2)
5024
- this.fsw._addPathCloser(path12, closer2);
5191
+ this.fsw._addPathCloser(path15, closer2);
5025
5192
  } else {
5026
5193
  prevStats = newStats2;
5027
5194
  }
@@ -5053,7 +5220,7 @@ var init_handler = __esm({
5053
5220
  * @param item basename of this item
5054
5221
  * @returns true if no more processing is needed for this entry.
5055
5222
  */
5056
- async _handleSymlink(entry, directory, path12, item) {
5223
+ async _handleSymlink(entry, directory, path15, item) {
5057
5224
  if (this.fsw.closed) {
5058
5225
  return;
5059
5226
  }
@@ -5063,7 +5230,7 @@ var init_handler = __esm({
5063
5230
  this.fsw._incrReadyCount();
5064
5231
  let linkPath;
5065
5232
  try {
5066
- linkPath = await fsrealpath(path12);
5233
+ linkPath = await fsrealpath(path15);
5067
5234
  } catch (e) {
5068
5235
  this.fsw._emitReady();
5069
5236
  return true;
@@ -5073,12 +5240,12 @@ var init_handler = __esm({
5073
5240
  if (dir.has(item)) {
5074
5241
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
5075
5242
  this.fsw._symlinkPaths.set(full, linkPath);
5076
- this.fsw._emit(EV.CHANGE, path12, entry.stats);
5243
+ this.fsw._emit(EV.CHANGE, path15, entry.stats);
5077
5244
  }
5078
5245
  } else {
5079
5246
  dir.add(item);
5080
5247
  this.fsw._symlinkPaths.set(full, linkPath);
5081
- this.fsw._emit(EV.ADD, path12, entry.stats);
5248
+ this.fsw._emit(EV.ADD, path15, entry.stats);
5082
5249
  }
5083
5250
  this.fsw._emitReady();
5084
5251
  return true;
@@ -5107,9 +5274,9 @@ var init_handler = __esm({
5107
5274
  return;
5108
5275
  }
5109
5276
  const item = entry.path;
5110
- let path12 = sysPath.join(directory, item);
5277
+ let path15 = sysPath.join(directory, item);
5111
5278
  current.add(item);
5112
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
5279
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path15, item)) {
5113
5280
  return;
5114
5281
  }
5115
5282
  if (this.fsw.closed) {
@@ -5118,8 +5285,8 @@ var init_handler = __esm({
5118
5285
  }
5119
5286
  if (item === target || !target && !previous.has(item)) {
5120
5287
  this.fsw._incrReadyCount();
5121
- path12 = sysPath.join(dir, sysPath.relative(dir, path12));
5122
- this._addToNodeFs(path12, initialAdd, wh, depth + 1);
5288
+ path15 = sysPath.join(dir, sysPath.relative(dir, path15));
5289
+ this._addToNodeFs(path15, initialAdd, wh, depth + 1);
5123
5290
  }
5124
5291
  }).on(EV.ERROR, this._boundHandleError);
5125
5292
  return new Promise((resolve3, reject) => {
@@ -5188,13 +5355,13 @@ var init_handler = __esm({
5188
5355
  * @param depth Child path actually targeted for watch
5189
5356
  * @param target Child path actually targeted for watch
5190
5357
  */
5191
- async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
5358
+ async _addToNodeFs(path15, initialAdd, priorWh, depth, target) {
5192
5359
  const ready = this.fsw._emitReady;
5193
- if (this.fsw._isIgnored(path12) || this.fsw.closed) {
5360
+ if (this.fsw._isIgnored(path15) || this.fsw.closed) {
5194
5361
  ready();
5195
5362
  return false;
5196
5363
  }
5197
- const wh = this.fsw._getWatchHelpers(path12);
5364
+ const wh = this.fsw._getWatchHelpers(path15);
5198
5365
  if (priorWh) {
5199
5366
  wh.filterPath = (entry) => priorWh.filterPath(entry);
5200
5367
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -5210,8 +5377,8 @@ var init_handler = __esm({
5210
5377
  const follow = this.fsw.options.followSymlinks;
5211
5378
  let closer;
5212
5379
  if (stats.isDirectory()) {
5213
- const absPath = sysPath.resolve(path12);
5214
- const targetPath = follow ? await fsrealpath(path12) : path12;
5380
+ const absPath = sysPath.resolve(path15);
5381
+ const targetPath = follow ? await fsrealpath(path15) : path15;
5215
5382
  if (this.fsw.closed)
5216
5383
  return;
5217
5384
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -5221,29 +5388,29 @@ var init_handler = __esm({
5221
5388
  this.fsw._symlinkPaths.set(absPath, targetPath);
5222
5389
  }
5223
5390
  } else if (stats.isSymbolicLink()) {
5224
- const targetPath = follow ? await fsrealpath(path12) : path12;
5391
+ const targetPath = follow ? await fsrealpath(path15) : path15;
5225
5392
  if (this.fsw.closed)
5226
5393
  return;
5227
5394
  const parent = sysPath.dirname(wh.watchPath);
5228
5395
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
5229
5396
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
5230
- closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
5397
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path15, wh, targetPath);
5231
5398
  if (this.fsw.closed)
5232
5399
  return;
5233
5400
  if (targetPath !== void 0) {
5234
- this.fsw._symlinkPaths.set(sysPath.resolve(path12), targetPath);
5401
+ this.fsw._symlinkPaths.set(sysPath.resolve(path15), targetPath);
5235
5402
  }
5236
5403
  } else {
5237
5404
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
5238
5405
  }
5239
5406
  ready();
5240
5407
  if (closer)
5241
- this.fsw._addPathCloser(path12, closer);
5408
+ this.fsw._addPathCloser(path15, closer);
5242
5409
  return false;
5243
5410
  } catch (error) {
5244
5411
  if (this.fsw._handleError(error)) {
5245
5412
  ready();
5246
- return path12;
5413
+ return path15;
5247
5414
  }
5248
5415
  }
5249
5416
  }
@@ -5282,26 +5449,26 @@ function createPattern(matcher) {
5282
5449
  }
5283
5450
  return () => false;
5284
5451
  }
5285
- function normalizePath2(path12) {
5286
- if (typeof path12 !== "string")
5452
+ function normalizePath2(path15) {
5453
+ if (typeof path15 !== "string")
5287
5454
  throw new Error("string expected");
5288
- path12 = sysPath2.normalize(path12);
5289
- path12 = path12.replace(/\\/g, "/");
5455
+ path15 = sysPath2.normalize(path15);
5456
+ path15 = path15.replace(/\\/g, "/");
5290
5457
  let prepend = false;
5291
- if (path12.startsWith("//"))
5458
+ if (path15.startsWith("//"))
5292
5459
  prepend = true;
5293
5460
  const DOUBLE_SLASH_RE2 = /\/\//;
5294
- while (path12.match(DOUBLE_SLASH_RE2))
5295
- path12 = path12.replace(DOUBLE_SLASH_RE2, "/");
5461
+ while (path15.match(DOUBLE_SLASH_RE2))
5462
+ path15 = path15.replace(DOUBLE_SLASH_RE2, "/");
5296
5463
  if (prepend)
5297
- path12 = "/" + path12;
5298
- return path12;
5464
+ path15 = "/" + path15;
5465
+ return path15;
5299
5466
  }
5300
5467
  function matchPatterns(patterns, testString, stats) {
5301
- const path12 = normalizePath2(testString);
5468
+ const path15 = normalizePath2(testString);
5302
5469
  for (let index = 0; index < patterns.length; index++) {
5303
5470
  const pattern = patterns[index];
5304
- if (pattern(path12, stats)) {
5471
+ if (pattern(path15, stats)) {
5305
5472
  return true;
5306
5473
  }
5307
5474
  }
@@ -5362,19 +5529,19 @@ var init_esm2 = __esm({
5362
5529
  }
5363
5530
  return str;
5364
5531
  };
5365
- normalizePathToUnix = (path12) => toUnix(sysPath2.normalize(toUnix(path12)));
5366
- normalizeIgnored = (cwd = "") => (path12) => {
5367
- if (typeof path12 === "string") {
5368
- return normalizePathToUnix(sysPath2.isAbsolute(path12) ? path12 : sysPath2.join(cwd, path12));
5532
+ normalizePathToUnix = (path15) => toUnix(sysPath2.normalize(toUnix(path15)));
5533
+ normalizeIgnored = (cwd = "") => (path15) => {
5534
+ if (typeof path15 === "string") {
5535
+ return normalizePathToUnix(sysPath2.isAbsolute(path15) ? path15 : sysPath2.join(cwd, path15));
5369
5536
  } else {
5370
- return path12;
5537
+ return path15;
5371
5538
  }
5372
5539
  };
5373
- getAbsolutePath = (path12, cwd) => {
5374
- if (sysPath2.isAbsolute(path12)) {
5375
- return path12;
5540
+ getAbsolutePath = (path15, cwd) => {
5541
+ if (sysPath2.isAbsolute(path15)) {
5542
+ return path15;
5376
5543
  }
5377
- return sysPath2.join(cwd, path12);
5544
+ return sysPath2.join(cwd, path15);
5378
5545
  };
5379
5546
  EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
5380
5547
  DirEntry = class {
@@ -5429,10 +5596,10 @@ var init_esm2 = __esm({
5429
5596
  STAT_METHOD_F = "stat";
5430
5597
  STAT_METHOD_L = "lstat";
5431
5598
  WatchHelper = class {
5432
- constructor(path12, follow, fsw) {
5599
+ constructor(path15, follow, fsw) {
5433
5600
  this.fsw = fsw;
5434
- const watchPath = path12;
5435
- this.path = path12 = path12.replace(REPLACER_RE, "");
5601
+ const watchPath = path15;
5602
+ this.path = path15 = path15.replace(REPLACER_RE, "");
5436
5603
  this.watchPath = watchPath;
5437
5604
  this.fullWatchPath = sysPath2.resolve(watchPath);
5438
5605
  this.dirParts = [];
@@ -5554,20 +5721,20 @@ var init_esm2 = __esm({
5554
5721
  this._closePromise = void 0;
5555
5722
  let paths = unifyPaths(paths_);
5556
5723
  if (cwd) {
5557
- paths = paths.map((path12) => {
5558
- const absPath = getAbsolutePath(path12, cwd);
5724
+ paths = paths.map((path15) => {
5725
+ const absPath = getAbsolutePath(path15, cwd);
5559
5726
  return absPath;
5560
5727
  });
5561
5728
  }
5562
- paths.forEach((path12) => {
5563
- this._removeIgnoredPath(path12);
5729
+ paths.forEach((path15) => {
5730
+ this._removeIgnoredPath(path15);
5564
5731
  });
5565
5732
  this._userIgnored = void 0;
5566
5733
  if (!this._readyCount)
5567
5734
  this._readyCount = 0;
5568
5735
  this._readyCount += paths.length;
5569
- Promise.all(paths.map(async (path12) => {
5570
- const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
5736
+ Promise.all(paths.map(async (path15) => {
5737
+ const res = await this._nodeFsHandler._addToNodeFs(path15, !_internal, void 0, 0, _origAdd);
5571
5738
  if (res)
5572
5739
  this._emitReady();
5573
5740
  return res;
@@ -5589,17 +5756,17 @@ var init_esm2 = __esm({
5589
5756
  return this;
5590
5757
  const paths = unifyPaths(paths_);
5591
5758
  const { cwd } = this.options;
5592
- paths.forEach((path12) => {
5593
- if (!sysPath2.isAbsolute(path12) && !this._closers.has(path12)) {
5759
+ paths.forEach((path15) => {
5760
+ if (!sysPath2.isAbsolute(path15) && !this._closers.has(path15)) {
5594
5761
  if (cwd)
5595
- path12 = sysPath2.join(cwd, path12);
5596
- path12 = sysPath2.resolve(path12);
5762
+ path15 = sysPath2.join(cwd, path15);
5763
+ path15 = sysPath2.resolve(path15);
5597
5764
  }
5598
- this._closePath(path12);
5599
- this._addIgnoredPath(path12);
5600
- if (this._watched.has(path12)) {
5765
+ this._closePath(path15);
5766
+ this._addIgnoredPath(path15);
5767
+ if (this._watched.has(path15)) {
5601
5768
  this._addIgnoredPath({
5602
- path: path12,
5769
+ path: path15,
5603
5770
  recursive: true
5604
5771
  });
5605
5772
  }
@@ -5663,38 +5830,38 @@ var init_esm2 = __esm({
5663
5830
  * @param stats arguments to be passed with event
5664
5831
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
5665
5832
  */
5666
- async _emit(event, path12, stats) {
5833
+ async _emit(event, path15, stats) {
5667
5834
  if (this.closed)
5668
5835
  return;
5669
5836
  const opts = this.options;
5670
5837
  if (isWindows)
5671
- path12 = sysPath2.normalize(path12);
5838
+ path15 = sysPath2.normalize(path15);
5672
5839
  if (opts.cwd)
5673
- path12 = sysPath2.relative(opts.cwd, path12);
5674
- const args = [path12];
5840
+ path15 = sysPath2.relative(opts.cwd, path15);
5841
+ const args = [path15];
5675
5842
  if (stats != null)
5676
5843
  args.push(stats);
5677
5844
  const awf = opts.awaitWriteFinish;
5678
5845
  let pw;
5679
- if (awf && (pw = this._pendingWrites.get(path12))) {
5846
+ if (awf && (pw = this._pendingWrites.get(path15))) {
5680
5847
  pw.lastChange = /* @__PURE__ */ new Date();
5681
5848
  return this;
5682
5849
  }
5683
5850
  if (opts.atomic) {
5684
5851
  if (event === EVENTS.UNLINK) {
5685
- this._pendingUnlinks.set(path12, [event, ...args]);
5852
+ this._pendingUnlinks.set(path15, [event, ...args]);
5686
5853
  setTimeout(() => {
5687
- this._pendingUnlinks.forEach((entry, path13) => {
5854
+ this._pendingUnlinks.forEach((entry, path16) => {
5688
5855
  this.emit(...entry);
5689
5856
  this.emit(EVENTS.ALL, ...entry);
5690
- this._pendingUnlinks.delete(path13);
5857
+ this._pendingUnlinks.delete(path16);
5691
5858
  });
5692
5859
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
5693
5860
  return this;
5694
5861
  }
5695
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
5862
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path15)) {
5696
5863
  event = EVENTS.CHANGE;
5697
- this._pendingUnlinks.delete(path12);
5864
+ this._pendingUnlinks.delete(path15);
5698
5865
  }
5699
5866
  }
5700
5867
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -5712,16 +5879,16 @@ var init_esm2 = __esm({
5712
5879
  this.emitWithAll(event, args);
5713
5880
  }
5714
5881
  };
5715
- this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
5882
+ this._awaitWriteFinish(path15, awf.stabilityThreshold, event, awfEmit);
5716
5883
  return this;
5717
5884
  }
5718
5885
  if (event === EVENTS.CHANGE) {
5719
- const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
5886
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path15, 50);
5720
5887
  if (isThrottled)
5721
5888
  return this;
5722
5889
  }
5723
5890
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
5724
- const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path12) : path12;
5891
+ const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path15) : path15;
5725
5892
  let stats2;
5726
5893
  try {
5727
5894
  stats2 = await stat3(fullPath);
@@ -5752,23 +5919,23 @@ var init_esm2 = __esm({
5752
5919
  * @param timeout duration of time to suppress duplicate actions
5753
5920
  * @returns tracking object or false if action should be suppressed
5754
5921
  */
5755
- _throttle(actionType, path12, timeout) {
5922
+ _throttle(actionType, path15, timeout) {
5756
5923
  if (!this._throttled.has(actionType)) {
5757
5924
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
5758
5925
  }
5759
5926
  const action = this._throttled.get(actionType);
5760
5927
  if (!action)
5761
5928
  throw new Error("invalid throttle");
5762
- const actionPath = action.get(path12);
5929
+ const actionPath = action.get(path15);
5763
5930
  if (actionPath) {
5764
5931
  actionPath.count++;
5765
5932
  return false;
5766
5933
  }
5767
5934
  let timeoutObject;
5768
5935
  const clear = () => {
5769
- const item = action.get(path12);
5936
+ const item = action.get(path15);
5770
5937
  const count = item ? item.count : 0;
5771
- action.delete(path12);
5938
+ action.delete(path15);
5772
5939
  clearTimeout(timeoutObject);
5773
5940
  if (item)
5774
5941
  clearTimeout(item.timeoutObject);
@@ -5776,7 +5943,7 @@ var init_esm2 = __esm({
5776
5943
  };
5777
5944
  timeoutObject = setTimeout(clear, timeout);
5778
5945
  const thr = { timeoutObject, clear, count: 0 };
5779
- action.set(path12, thr);
5946
+ action.set(path15, thr);
5780
5947
  return thr;
5781
5948
  }
5782
5949
  _incrReadyCount() {
@@ -5790,44 +5957,44 @@ var init_esm2 = __esm({
5790
5957
  * @param event
5791
5958
  * @param awfEmit Callback to be called when ready for event to be emitted.
5792
5959
  */
5793
- _awaitWriteFinish(path12, threshold, event, awfEmit) {
5960
+ _awaitWriteFinish(path15, threshold, event, awfEmit) {
5794
5961
  const awf = this.options.awaitWriteFinish;
5795
5962
  if (typeof awf !== "object")
5796
5963
  return;
5797
5964
  const pollInterval = awf.pollInterval;
5798
5965
  let timeoutHandler;
5799
- let fullPath = path12;
5800
- if (this.options.cwd && !sysPath2.isAbsolute(path12)) {
5801
- fullPath = sysPath2.join(this.options.cwd, path12);
5966
+ let fullPath = path15;
5967
+ if (this.options.cwd && !sysPath2.isAbsolute(path15)) {
5968
+ fullPath = sysPath2.join(this.options.cwd, path15);
5802
5969
  }
5803
5970
  const now = /* @__PURE__ */ new Date();
5804
5971
  const writes = this._pendingWrites;
5805
5972
  function awaitWriteFinishFn(prevStat) {
5806
5973
  statcb(fullPath, (err, curStat) => {
5807
- if (err || !writes.has(path12)) {
5974
+ if (err || !writes.has(path15)) {
5808
5975
  if (err && err.code !== "ENOENT")
5809
5976
  awfEmit(err);
5810
5977
  return;
5811
5978
  }
5812
5979
  const now2 = Number(/* @__PURE__ */ new Date());
5813
5980
  if (prevStat && curStat.size !== prevStat.size) {
5814
- writes.get(path12).lastChange = now2;
5981
+ writes.get(path15).lastChange = now2;
5815
5982
  }
5816
- const pw = writes.get(path12);
5983
+ const pw = writes.get(path15);
5817
5984
  const df = now2 - pw.lastChange;
5818
5985
  if (df >= threshold) {
5819
- writes.delete(path12);
5986
+ writes.delete(path15);
5820
5987
  awfEmit(void 0, curStat);
5821
5988
  } else {
5822
5989
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
5823
5990
  }
5824
5991
  });
5825
5992
  }
5826
- if (!writes.has(path12)) {
5827
- writes.set(path12, {
5993
+ if (!writes.has(path15)) {
5994
+ writes.set(path15, {
5828
5995
  lastChange: now,
5829
5996
  cancelWait: () => {
5830
- writes.delete(path12);
5997
+ writes.delete(path15);
5831
5998
  clearTimeout(timeoutHandler);
5832
5999
  return event;
5833
6000
  }
@@ -5838,8 +6005,8 @@ var init_esm2 = __esm({
5838
6005
  /**
5839
6006
  * Determines whether user has asked to ignore this path.
5840
6007
  */
5841
- _isIgnored(path12, stats) {
5842
- if (this.options.atomic && DOT_RE.test(path12))
6008
+ _isIgnored(path15, stats) {
6009
+ if (this.options.atomic && DOT_RE.test(path15))
5843
6010
  return true;
5844
6011
  if (!this._userIgnored) {
5845
6012
  const { cwd } = this.options;
@@ -5849,17 +6016,17 @@ var init_esm2 = __esm({
5849
6016
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
5850
6017
  this._userIgnored = anymatch(list, void 0);
5851
6018
  }
5852
- return this._userIgnored(path12, stats);
6019
+ return this._userIgnored(path15, stats);
5853
6020
  }
5854
- _isntIgnored(path12, stat4) {
5855
- return !this._isIgnored(path12, stat4);
6021
+ _isntIgnored(path15, stat4) {
6022
+ return !this._isIgnored(path15, stat4);
5856
6023
  }
5857
6024
  /**
5858
6025
  * Provides a set of common helpers and properties relating to symlink handling.
5859
6026
  * @param path file or directory pattern being watched
5860
6027
  */
5861
- _getWatchHelpers(path12) {
5862
- return new WatchHelper(path12, this.options.followSymlinks, this);
6028
+ _getWatchHelpers(path15) {
6029
+ return new WatchHelper(path15, this.options.followSymlinks, this);
5863
6030
  }
5864
6031
  // Directory helpers
5865
6032
  // -----------------
@@ -5891,63 +6058,63 @@ var init_esm2 = __esm({
5891
6058
  * @param item base path of item/directory
5892
6059
  */
5893
6060
  _remove(directory, item, isDirectory) {
5894
- const path12 = sysPath2.join(directory, item);
5895
- const fullPath = sysPath2.resolve(path12);
5896
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path12) || this._watched.has(fullPath);
5897
- if (!this._throttle("remove", path12, 100))
6061
+ const path15 = sysPath2.join(directory, item);
6062
+ const fullPath = sysPath2.resolve(path15);
6063
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path15) || this._watched.has(fullPath);
6064
+ if (!this._throttle("remove", path15, 100))
5898
6065
  return;
5899
6066
  if (!isDirectory && this._watched.size === 1) {
5900
6067
  this.add(directory, item, true);
5901
6068
  }
5902
- const wp = this._getWatchedDir(path12);
6069
+ const wp = this._getWatchedDir(path15);
5903
6070
  const nestedDirectoryChildren = wp.getChildren();
5904
- nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
6071
+ nestedDirectoryChildren.forEach((nested) => this._remove(path15, nested));
5905
6072
  const parent = this._getWatchedDir(directory);
5906
6073
  const wasTracked = parent.has(item);
5907
6074
  parent.remove(item);
5908
6075
  if (this._symlinkPaths.has(fullPath)) {
5909
6076
  this._symlinkPaths.delete(fullPath);
5910
6077
  }
5911
- let relPath = path12;
6078
+ let relPath = path15;
5912
6079
  if (this.options.cwd)
5913
- relPath = sysPath2.relative(this.options.cwd, path12);
6080
+ relPath = sysPath2.relative(this.options.cwd, path15);
5914
6081
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
5915
6082
  const event = this._pendingWrites.get(relPath).cancelWait();
5916
6083
  if (event === EVENTS.ADD)
5917
6084
  return;
5918
6085
  }
5919
- this._watched.delete(path12);
6086
+ this._watched.delete(path15);
5920
6087
  this._watched.delete(fullPath);
5921
6088
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
5922
- if (wasTracked && !this._isIgnored(path12))
5923
- this._emit(eventName, path12);
5924
- this._closePath(path12);
6089
+ if (wasTracked && !this._isIgnored(path15))
6090
+ this._emit(eventName, path15);
6091
+ this._closePath(path15);
5925
6092
  }
5926
6093
  /**
5927
6094
  * Closes all watchers for a path
5928
6095
  */
5929
- _closePath(path12) {
5930
- this._closeFile(path12);
5931
- const dir = sysPath2.dirname(path12);
5932
- this._getWatchedDir(dir).remove(sysPath2.basename(path12));
6096
+ _closePath(path15) {
6097
+ this._closeFile(path15);
6098
+ const dir = sysPath2.dirname(path15);
6099
+ this._getWatchedDir(dir).remove(sysPath2.basename(path15));
5933
6100
  }
5934
6101
  /**
5935
6102
  * Closes only file-specific watchers
5936
6103
  */
5937
- _closeFile(path12) {
5938
- const closers = this._closers.get(path12);
6104
+ _closeFile(path15) {
6105
+ const closers = this._closers.get(path15);
5939
6106
  if (!closers)
5940
6107
  return;
5941
6108
  closers.forEach((closer) => closer());
5942
- this._closers.delete(path12);
6109
+ this._closers.delete(path15);
5943
6110
  }
5944
- _addPathCloser(path12, closer) {
6111
+ _addPathCloser(path15, closer) {
5945
6112
  if (!closer)
5946
6113
  return;
5947
- let list = this._closers.get(path12);
6114
+ let list = this._closers.get(path15);
5948
6115
  if (!list) {
5949
6116
  list = [];
5950
- this._closers.set(path12, list);
6117
+ this._closers.set(path15, list);
5951
6118
  }
5952
6119
  list.push(closer);
5953
6120
  }
@@ -5974,23 +6141,36 @@ var init_esm2 = __esm({
5974
6141
  });
5975
6142
 
5976
6143
  // src/cli/watcher.ts
6144
+ import path12 from "path";
5977
6145
  function startWatcher(options) {
5978
6146
  const { rootDir, patterns, appDir } = options;
5979
6147
  let rebuildTimer = null;
6148
+ let pendingFiles = /* @__PURE__ */ new Set();
5980
6149
  async function rebuildRoutes() {
5981
6150
  try {
5982
6151
  const timestamp = Date.now();
5983
6152
  globalThis.__FAAPI_LOAD_TS__ = timestamp;
5984
6153
  invalidateMiddlewareCache();
5985
6154
  invalidateProgramCache();
5986
- const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir);
6155
+ const filesToCompile = Array.from(pendingFiles);
6156
+ pendingFiles = /* @__PURE__ */ new Set();
6157
+ if (filesToCompile.length > 0) {
6158
+ await compileRoutes({
6159
+ rootDir,
6160
+ appDir,
6161
+ outDir: DEV_OUT_DIR,
6162
+ files: filesToCompile
6163
+ });
6164
+ }
6165
+ const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir, DEV_OUT_DIR);
5987
6166
  const sorted = sortRoutes(routes);
5988
- const manifest = extractSchemasForRoutes(sorted, rootDir);
5989
- schemaRegistry.loadManifest(manifest);
6167
+ const schemaPath = path12.resolve(rootDir, DEV_OUT_DIR, "faapi-schema.js");
6168
+ await generateSchemaFile(sorted, rootDir, schemaPath);
6169
+ await loadSchemaToRegistry(schemaPath, rootDir, DEV_OUT_DIR, false);
5990
6170
  updateServerRoutes(sorted, wsRoutes);
5991
- const wsCount = wsRoutes.length;
6171
+ const recompiledCount = filesToCompile.length;
5992
6172
  console.log(
5993
- `- Routes rebuilt: ${sorted.length} route(s), ${wsCount} WS route(s), ${manifest.size} file(s)`
6173
+ `- Routes rebuilt: ${sorted.length} route(s), ${wsRoutes.length} WS route(s)${recompiledCount > 0 ? `, ${recompiledCount} file(s) recompiled` : ""}`
5994
6174
  );
5995
6175
  } catch (err) {
5996
6176
  console.error("- Error rebuilding routes:", err instanceof Error ? err.message : String(err));
@@ -6003,21 +6183,45 @@ function startWatcher(options) {
6003
6183
  void rebuildRoutes();
6004
6184
  }, 100);
6005
6185
  }
6006
- const watchPatterns = [...patterns, `${appDir}/**/middlewares.ts`];
6007
- const watcher = esm_default.watch(watchPatterns, {
6186
+ const watcher = esm_default.watch(appDir, {
6008
6187
  cwd: rootDir,
6009
6188
  ignoreInitial: true,
6010
- ignored: ["**/node_modules/**", "**/.faapi/**", "**/dist/**"]
6189
+ ignored: (filePath, stats) => {
6190
+ if (filePath.includes("node_modules") || filePath.includes(".faapi") || filePath.includes("dist") || filePath.includes(".git")) {
6191
+ return true;
6192
+ }
6193
+ if (!stats) return false;
6194
+ if (stats.isDirectory()) return false;
6195
+ return !filePath.endsWith(".ts");
6196
+ }
6197
+ });
6198
+ watcher.on("add", (file) => {
6199
+ pendingFiles.add(path12.resolve(rootDir, file));
6200
+ scheduleRebuild();
6201
+ });
6202
+ watcher.on("change", (file) => {
6203
+ pendingFiles.add(path12.resolve(rootDir, file));
6204
+ scheduleRebuild();
6205
+ });
6206
+ watcher.on("unlink", () => {
6207
+ scheduleRebuild();
6208
+ });
6209
+ watcher.on("error", (err) => {
6210
+ console.error("- Watcher error:", err instanceof Error ? err.message : String(err));
6211
+ });
6212
+ watcher.on("ready", () => {
6213
+ const watched = watcher.getWatched();
6214
+ const dirCount = Object.keys(watched).length;
6215
+ const fileCount = Object.values(watched).reduce((sum, files) => sum + files.length, 0);
6216
+ console.log(`- Watcher ready: ${dirCount} dir(s), ${fileCount} file(s) watched`);
6011
6217
  });
6012
- watcher.on("add", () => scheduleRebuild());
6013
- watcher.on("change", () => scheduleRebuild());
6014
- watcher.on("unlink", () => scheduleRebuild());
6015
6218
  console.log("- Watch mode enabled");
6016
6219
  }
6017
6220
  function updateServerRoutes(routes, wsRoutes) {
6018
6221
  globalThis.__FAAPI_ROUTES__ = routes;
6019
6222
  globalThis.__FAAPI_WS_ROUTES__ = wsRoutes;
6020
6223
  }
6224
+ var DEV_OUT_DIR;
6021
6225
  var init_watcher = __esm({
6022
6226
  "src/cli/watcher.ts"() {
6023
6227
  "use strict";
@@ -6026,14 +6230,15 @@ var init_watcher = __esm({
6026
6230
  init_sortRoutes();
6027
6231
  init_loadMiddlewares();
6028
6232
  init_createProgram();
6029
- init_schemaRegistry();
6030
6233
  init_generateSchema();
6234
+ init_compileRoutes();
6235
+ DEV_OUT_DIR = ".faapi/dev";
6031
6236
  }
6032
6237
  });
6033
6238
 
6034
6239
  // src/config/loadConfig.ts
6035
- import path10 from "path";
6036
- import fs7 from "fs";
6240
+ import path13 from "path";
6241
+ import fs8 from "fs";
6037
6242
  import { pathToFileURL as pathToFileURL2 } from "url";
6038
6243
  function getEnv() {
6039
6244
  return process.env.FAAPI_ENV || process.env.NODE_ENV || "development";
@@ -6059,7 +6264,7 @@ function deepMerge(base, override) {
6059
6264
  return result;
6060
6265
  }
6061
6266
  async function loadConfigFile(filePath) {
6062
- if (!fs7.existsSync(filePath)) {
6267
+ if (!fs8.existsSync(filePath)) {
6063
6268
  return null;
6064
6269
  }
6065
6270
  try {
@@ -6075,15 +6280,15 @@ async function loadConfigFile(filePath) {
6075
6280
  }
6076
6281
  async function loadConfig(rootDir, configPath) {
6077
6282
  if (configPath) {
6078
- const resolvedPath = path10.resolve(rootDir, configPath);
6079
- if (!fs7.existsSync(resolvedPath)) {
6283
+ const resolvedPath = path13.resolve(rootDir, configPath);
6284
+ if (!fs8.existsSync(resolvedPath)) {
6080
6285
  throw new Error(`Config file not found: ${configPath}`);
6081
6286
  }
6082
6287
  return loadConfigFile(resolvedPath);
6083
6288
  }
6084
6289
  let baseConfig = null;
6085
6290
  for (const fileName of BASE_CONFIG_FILES) {
6086
- const filePath = path10.join(rootDir, fileName);
6291
+ const filePath = path13.join(rootDir, fileName);
6087
6292
  baseConfig = await loadConfigFile(filePath);
6088
6293
  if (baseConfig) break;
6089
6294
  }
@@ -6093,7 +6298,7 @@ async function loadConfig(rootDir, configPath) {
6093
6298
  const env = getEnv();
6094
6299
  const envFiles = [`faapi.config.${env}.ts`, `faapi.config.${env}.js`];
6095
6300
  for (const envFile of envFiles) {
6096
- const envConfig = await loadConfigFile(path10.join(rootDir, envFile));
6301
+ const envConfig = await loadConfigFile(path13.join(rootDir, envFile));
6097
6302
  if (envConfig) {
6098
6303
  baseConfig = deepMerge(baseConfig, envConfig);
6099
6304
  break;
@@ -6178,17 +6383,17 @@ var startCommand_exports = {};
6178
6383
  __export(startCommand_exports, {
6179
6384
  startCommand: () => startCommand
6180
6385
  });
6181
- import fs8 from "fs";
6182
- import path11 from "path";
6183
- import { pathToFileURL as pathToFileURL3 } from "url";
6386
+ import fs9 from "fs";
6387
+ import path14 from "path";
6184
6388
  async function startCommand(argv) {
6185
6389
  const args = parseArgs(argv);
6186
6390
  const rootDir = process.cwd();
6187
6391
  const isProd = args.mode === "start";
6392
+ const prodDir = isProd ? PROD_OUT_DIR : DEV_OUT_DIR2;
6188
6393
  if (isProd) {
6189
- const routesPath = path11.resolve(rootDir, "dist", "faapi-routes.js");
6190
- const schemaPath = path11.resolve(rootDir, "dist", "faapi-schema.js");
6191
- if (!fs8.existsSync(routesPath) || !fs8.existsSync(schemaPath)) {
6394
+ const routesPath = path14.resolve(rootDir, PROD_OUT_DIR, "faapi-routes.js");
6395
+ const schemaPath2 = path14.resolve(rootDir, PROD_OUT_DIR, "faapi-schema.js");
6396
+ if (!fs9.existsSync(routesPath) || !fs9.existsSync(schemaPath2)) {
6192
6397
  console.error(
6193
6398
  "[faapi] dist/faapi-routes.js \u6216 dist/faapi-schema.js \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C `faapi build` \u6784\u5EFA\u751F\u4EA7\u4EA7\u7269\u3002"
6194
6399
  );
@@ -6210,15 +6415,16 @@ async function startCommand(argv) {
6210
6415
  let routes;
6211
6416
  let wsRoutes;
6212
6417
  if (isProd) {
6213
- const routesPath = path11.resolve(rootDir, "dist", "faapi-routes.js");
6214
- const serialized = await import(pathToFileURL3(routesPath).href);
6418
+ const routesPath = path14.resolve(rootDir, PROD_OUT_DIR, "faapi-routes.js");
6419
+ const serialized = await importWithCacheBust(routesPath);
6215
6420
  const hydrated = await hydrateRoutes(serialized);
6216
6421
  routes = hydrated.routes;
6217
6422
  wsRoutes = hydrated.wsRoutes;
6218
6423
  console.log(`- Routes loaded: ${routes.length} routes, ${wsRoutes.length} WS routes`);
6219
6424
  } else {
6220
- const { patterns, appDir } = { patterns: args.patterns, appDir: args.appDir };
6221
- const scanned = await scanRoutes(rootDir, patterns, appDir);
6425
+ console.log("- Compiling TypeScript...");
6426
+ await compileRoutes({ rootDir, appDir: args.appDir, outDir: DEV_OUT_DIR2 });
6427
+ const scanned = await scanRoutes(rootDir, args.patterns, args.appDir, DEV_OUT_DIR2);
6222
6428
  routes = scanned.routes;
6223
6429
  wsRoutes = scanned.wsRoutes;
6224
6430
  console.log(`- Routes scanned: ${routes.length} routes, ${wsRoutes.length} WS routes`);
@@ -6233,19 +6439,14 @@ async function startCommand(argv) {
6233
6439
  }
6234
6440
  }
6235
6441
  }
6236
- if (isProd) {
6237
- const schemaPath = path11.resolve(rootDir, "dist", "faapi-schema.js");
6238
- const manifest = await readManifestFile(schemaPath);
6239
- const remapped = remapManifestKeys(manifest, rootDir);
6240
- schemaRegistry.loadManifest(remapped);
6241
- console.log(`- Schema loaded: ${schemaPath}`);
6242
- } else {
6243
- const manifest = extractSchemasForRoutes(sorted, rootDir);
6244
- schemaRegistry.loadManifest(manifest);
6245
- console.log(`- Schema extracted: ${manifest.size} file(s)`);
6442
+ const schemaPath = path14.resolve(rootDir, prodDir, "faapi-schema.js");
6443
+ if (!isProd) {
6444
+ await generateSchemaFile(sorted, rootDir, schemaPath);
6246
6445
  }
6446
+ await loadSchemaToRegistry(schemaPath, rootDir, prodDir, isProd);
6447
+ console.log(`- Schema loaded: ${schemaPath}`);
6247
6448
  if (!isProd && args.types) {
6248
- const typesPath = path11.resolve(rootDir, args.types);
6449
+ const typesPath = path14.resolve(rootDir, args.types);
6249
6450
  await generateTypes(sorted, rootDir, typesPath);
6250
6451
  console.log(`- Types generated: ${typesPath}`);
6251
6452
  }
@@ -6309,23 +6510,7 @@ async function startCommand(argv) {
6309
6510
  function isFaapiConfigKey(key) {
6310
6511
  return FAAPI_CONFIG_KEYS.has(key);
6311
6512
  }
6312
- function remapManifestKeys(manifest, rootDir) {
6313
- const remapped = /* @__PURE__ */ new Map();
6314
- const rootPrefix = rootDir + path11.sep;
6315
- for (const [filePath, fileSchemas] of manifest) {
6316
- let rel = filePath;
6317
- if (filePath.startsWith(rootPrefix)) {
6318
- rel = filePath.slice(rootPrefix.length);
6319
- } else if (filePath.startsWith(rootDir)) {
6320
- rel = filePath.slice(rootDir.length).replace(/^[/\\]/, "");
6321
- }
6322
- const prodRel = `dist/${rel.replace(/\.ts$/, ".js")}`;
6323
- const prodAbs = path11.resolve(rootDir, prodRel);
6324
- remapped.set(prodAbs, fileSchemas);
6325
- }
6326
- return remapped;
6327
- }
6328
- var FAAPI_CONFIG_KEYS;
6513
+ var DEV_OUT_DIR2, PROD_OUT_DIR, FAAPI_CONFIG_KEYS;
6329
6514
  var init_startCommand = __esm({
6330
6515
  "src/cli/startCommand.ts"() {
6331
6516
  "use strict";
@@ -6337,10 +6522,13 @@ var init_startCommand = __esm({
6337
6522
  init_generateTypes();
6338
6523
  init_watcher();
6339
6524
  init_loadConfig();
6340
- init_schemaRegistry();
6341
6525
  init_generateSchema();
6342
6526
  init_generateRoutes();
6527
+ init_compileRoutes();
6343
6528
  init_loadPlugins();
6529
+ init_importWithCacheBust();
6530
+ DEV_OUT_DIR2 = ".faapi/dev";
6531
+ PROD_OUT_DIR = "dist";
6344
6532
  FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
6345
6533
  "port",
6346
6534
  "staticDir",
@@ -6360,25 +6548,32 @@ var init_startCommand = __esm({
6360
6548
  function isTsxPreloaded() {
6361
6549
  return process.execArgv.some((arg) => arg.includes("tsx")) || (process.env.NODE_OPTIONS ?? "").includes("tsx");
6362
6550
  }
6551
+ async function ensureTsx() {
6552
+ if (isTsxPreloaded()) return true;
6553
+ try {
6554
+ const { register } = await import("tsx/esm/api");
6555
+ register();
6556
+ return true;
6557
+ } catch {
6558
+ console.warn(
6559
+ "[faapi] tsx \u672A\u5B89\u88C5\uFF0C\u65E0\u6CD5\u52A0\u8F7D faapi.config.ts\u3002\u8BF7\u5B89\u88C5 tsx \u6216\u4F7F\u7528 faapi.config.js\u3002"
6560
+ );
6561
+ return false;
6562
+ }
6563
+ }
6363
6564
  async function main() {
6364
6565
  const argv = process.argv.slice(2);
6365
6566
  const firstArg = argv.find((a) => !a.startsWith("-"));
6567
+ const isBuildMode = firstArg === "build";
6568
+ if (!isBuildMode) {
6569
+ await ensureTsx();
6570
+ }
6366
6571
  if (firstArg === "build") {
6367
6572
  const { parseBuildArgs: parseBuildArgs2, buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_buildCommand(), buildCommand_exports));
6368
6573
  const options = parseBuildArgs2(argv.filter((a) => a !== "build"));
6369
6574
  await buildCommand2(options);
6370
6575
  return;
6371
6576
  }
6372
- const isStartMode = firstArg === "start";
6373
- if (!isStartMode && !isTsxPreloaded()) {
6374
- try {
6375
- const { register } = await import("tsx/esm/api");
6376
- register();
6377
- } catch (err) {
6378
- console.error("[faapi] tsx \u52A0\u8F7D\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u5B89\u88C5 tsx\uFF1A", err);
6379
- process.exit(1);
6380
- }
6381
- }
6382
6577
  const { startCommand: startCommand2 } = await Promise.resolve().then(() => (init_startCommand(), startCommand_exports));
6383
6578
  await startCommand2(argv);
6384
6579
  }