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

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,
@@ -969,32 +984,6 @@ function runtimeTypeToExpected(type) {
969
984
  return type.name;
970
985
  }
971
986
  }
972
- function generateValidatorSource(typeInfo, resolveType) {
973
- const ctx = new CodeGenContext(resolveType);
974
- collectNamedTypes(typeInfo.runtimeType, typeInfo.name, ctx);
975
- const resetVisited = Array.from(ctx.namedTypes.keys()).map((name) => `validate_${name}.__visited = new WeakSet();`).join("\n ");
976
- const entryBody = generateObjectValidation(typeInfo.runtimeType, "input", "issues", ctx, "''");
977
- const entryFn = `function validate(input) {
978
- const issues = [];
979
- ${resetVisited}
980
- ${entryBody}
981
- return { valid: issues.length === 0, issues, data: (typeof input === 'object' && input !== null && !Array.isArray(input)) ? input : {} };
982
- }`;
983
- const namedFns = Array.from(ctx.namedTypes.entries()).map(([name, type]) => {
984
- const body = generateObjectValidation(type, "value", "issues", ctx, "path");
985
- return `function validate_${name}(value, path, issues) {
986
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
987
- issues.push({ path, code: 'TYPE_MISMATCH', expected: 'object', received: typeof value, message: '\u671F\u671B\u5BF9\u8C61' });
988
- return;
989
- }
990
- if (!validate_${name}.__visited) validate_${name}.__visited = new WeakSet();
991
- if (validate_${name}.__visited.has(value)) return;
992
- validate_${name}.__visited.add(value);
993
- ${body}
994
- }`;
995
- });
996
- return [...namedFns, entryFn].join("\n\n");
997
- }
998
987
  function generateSchemaModule(entries, resolveType) {
999
988
  const ctx = new CodeGenContext(resolveType);
1000
989
  const validatorEntries = [];
@@ -1122,35 +1111,35 @@ function generateObjectValidation(type, varName, issuesVar, ctx, pathExpr) {
1122
1111
  return lines.join("\n");
1123
1112
  }
1124
1113
  function generateValueValidation(type, varName, issuesVar, ctx, pathExpr) {
1125
- const path12 = pathExpr ?? "''";
1114
+ const path15 = pathExpr ?? "''";
1126
1115
  switch (type.kind) {
1127
1116
  case "any":
1128
1117
  case "unknown":
1129
1118
  return "";
1130
1119
  // 不校验
1131
1120
  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} });`;
1121
+ 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
1122
  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} });`;
1123
+ 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
1124
  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} });`;
1125
+ 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
1126
  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} });`;
1127
+ 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
1128
  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} });`;
1129
+ 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
1130
  case "undefined":
1142
- return `if (${varName} !== undefined) ${issuesVar}.push({ path: ${path12}, code: 'TYPE_MISMATCH', expected: 'undefined', received: typeof ${varName}, message: '\u671F\u671B undefined' });`;
1131
+ return `if (${varName} !== undefined) ${issuesVar}.push({ path: ${path15}, code: 'TYPE_MISMATCH', expected: 'undefined', received: typeof ${varName}, message: '\u671F\u671B undefined' });`;
1143
1132
  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}) });`;
1133
+ 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
1134
  case "date":
1146
1135
  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} });`;
1136
+ 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} });
1137
+ 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
1138
  case "array": {
1150
1139
  const id = ctx.nextVarId();
1151
1140
  const itemVar = `item${id}`;
1152
1141
  const indexVar = `i${id}`;
1153
- const elemPath = `${path12} + '[' + ${indexVar} + ']'`;
1142
+ const elemPath = `${path15} + '[' + ${indexVar} + ']'`;
1154
1143
  const elemValidation = generateValueValidation(
1155
1144
  type.element,
1156
1145
  itemVar,
@@ -1158,7 +1147,7 @@ else if (!/^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?(Z|[+-]\\d{2
1158
1147
  ctx,
1159
1148
  elemPath
1160
1149
  );
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} });
1150
+ 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
1151
  else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) { const ${itemVar} = ${varName}[${indexVar}]; ${elemValidation} }`;
1163
1152
  }
1164
1153
  case "tuple": {
@@ -1170,21 +1159,21 @@ else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) {
1170
1159
  const indexVar = `ti${id}`;
1171
1160
  const lines = [];
1172
1161
  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} });`
1162
+ `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
1163
  );
1175
1164
  lines.push(`else {`);
1176
1165
  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 });`
1166
+ `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
1167
  );
1179
1168
  if (!restElement) {
1180
1169
  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 });`
1170
+ `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
1171
  );
1183
1172
  }
1184
1173
  for (let i = 0; i < type.elements.length; i++) {
1185
1174
  const elem = type.elements[i];
1186
1175
  if (elem.rest) continue;
1187
- const elemPath = `${path12} + '[' + ${i} + ']'`;
1176
+ const elemPath = `${path15} + '[' + ${i} + ']'`;
1188
1177
  const elemAccess = `${varName}[${i}]`;
1189
1178
  if (elem.optional) {
1190
1179
  lines.push(`if (${i} < ${varName}.length && ${elemAccess} !== undefined) {`);
@@ -1195,7 +1184,7 @@ else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) {
1195
1184
  lines.push(`}`);
1196
1185
  }
1197
1186
  if (restElement) {
1198
- const restPath = `${path12} + '[' + ${indexVar} + ']'`;
1187
+ const restPath = `${path15} + '[' + ${indexVar} + ']'`;
1199
1188
  const restValidation = generateValueValidation(
1200
1189
  restElement.type,
1201
1190
  itemVar,
@@ -1213,23 +1202,23 @@ else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) {
1213
1202
  case "union": {
1214
1203
  const tempVar = `tempIssues_${Math.random().toString(36).slice(2, 8)}`;
1215
1204
  const memberChecks = type.members.map((member) => {
1216
- const check = generateValueValidation(member, varName, tempVar, ctx, path12);
1205
+ const check = generateValueValidation(member, varName, tempVar, ctx, path15);
1217
1206
  return `(() => { const ${tempVar} = []; ${check}; return ${tempVar}.length === 0; })()`;
1218
1207
  });
1219
1208
  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, "\\'")}' });`;
1209
+ 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
1210
  }
1222
1211
  case "record": {
1223
- const valuePath = `${path12} + '.' + key`;
1212
+ const valuePath = `${path15} + '.' + key`;
1224
1213
  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} });
1214
+ 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
1215
  else for (const [key, val] of Object.entries(${varName})) { ${valueValidation} }`;
1227
1216
  }
1228
1217
  case "object": {
1229
- return generateInlineObjectValidation(type, varName, issuesVar, ctx, path12);
1218
+ return generateInlineObjectValidation(type, varName, issuesVar, ctx, path15);
1230
1219
  }
1231
1220
  case "ref": {
1232
- return `validate_${type.name}(${varName}, ${path12}, ${issuesVar});`;
1221
+ return `validate_${type.name}(${varName}, ${path15}, ${issuesVar});`;
1233
1222
  }
1234
1223
  }
1235
1224
  }
@@ -1289,6 +1278,75 @@ var init_generateValidatorCode = __esm({
1289
1278
  }
1290
1279
  });
1291
1280
 
1281
+ // src/validator/schemaRegistry.ts
1282
+ var SchemaRegistry, schemaRegistry;
1283
+ var init_schemaRegistry = __esm({
1284
+ "src/validator/schemaRegistry.ts"() {
1285
+ "use strict";
1286
+ SchemaRegistry = class {
1287
+ manifest = /* @__PURE__ */ new Map();
1288
+ /**
1289
+ * 批量加载 manifest
1290
+ * 覆盖已有数据
1291
+ */
1292
+ loadManifest(manifest) {
1293
+ this.manifest.clear();
1294
+ for (const [filePath, fileSchemas] of manifest) {
1295
+ const copy = /* @__PURE__ */ new Map();
1296
+ fileSchemas.forEach((value, key) => copy.set(key, value));
1297
+ this.manifest.set(filePath, copy);
1298
+ }
1299
+ }
1300
+ /**
1301
+ * 查询单条 schema
1302
+ * @returns SchemaEntry | null | undefined
1303
+ * - SchemaEntry:有类型声明
1304
+ * - null:无类型声明(跳过校验)
1305
+ * - undefined:manifest 不完整(抛错)
1306
+ */
1307
+ get(filePath, schemaName) {
1308
+ const fileSchemas = this.manifest.get(filePath);
1309
+ if (!fileSchemas) return void 0;
1310
+ return fileSchemas.get(schemaName);
1311
+ }
1312
+ /**
1313
+ * 设置单个文件的所有 schema
1314
+ * 覆盖该文件的已有数据
1315
+ */
1316
+ set(filePath, schemas) {
1317
+ const copy = /* @__PURE__ */ new Map();
1318
+ schemas.forEach((value, key) => copy.set(key, value));
1319
+ this.manifest.set(filePath, copy);
1320
+ }
1321
+ /**
1322
+ * 删除单个文件(文件被删除时)
1323
+ */
1324
+ delete(filePath) {
1325
+ this.manifest.delete(filePath);
1326
+ }
1327
+ /**
1328
+ * 判断文件是否已注册
1329
+ */
1330
+ hasFile(filePath) {
1331
+ return this.manifest.has(filePath);
1332
+ }
1333
+ /**
1334
+ * 清空(测试用 / watch 全量重建前)
1335
+ */
1336
+ clear() {
1337
+ this.manifest.clear();
1338
+ }
1339
+ /**
1340
+ * 已注册的文件数量
1341
+ */
1342
+ get size() {
1343
+ return this.manifest.size;
1344
+ }
1345
+ };
1346
+ schemaRegistry = new SchemaRegistry();
1347
+ }
1348
+ });
1349
+
1292
1350
  // src/ast/createProgram.ts
1293
1351
  import ts4 from "typescript";
1294
1352
  function invalidateProgramCache() {
@@ -1487,29 +1545,6 @@ var init_collectRouteSchemaSources = __esm({
1487
1545
  // src/cli/generateSchema.ts
1488
1546
  import fs3 from "fs/promises";
1489
1547
  import path4 from "path";
1490
- function extractSchemasForRoutes(routes, rootDir) {
1491
- const { sources, mergedAllTypes } = collectRouteSchemaSources(routes, rootDir);
1492
- const manifest = /* @__PURE__ */ new Map();
1493
- for (const { filePath, schemaName, typeInfo } of sources) {
1494
- let fileSchemas = manifest.get(filePath);
1495
- if (!fileSchemas) {
1496
- fileSchemas = /* @__PURE__ */ new Map();
1497
- manifest.set(filePath, fileSchemas);
1498
- }
1499
- fileSchemas.set(schemaName, typeInfoToSchemaEntry(typeInfo, mergedAllTypes));
1500
- }
1501
- return manifest;
1502
- }
1503
- function typeInfoToSchemaEntry(typeInfo, allTypes) {
1504
- if (typeInfo === null) return null;
1505
- const source = generateValidatorSource(typeInfo, (name) => allTypes.get(name)?.runtimeType);
1506
- const validator = new Function("input", `${source}
1507
- return validate(input);`);
1508
- return {
1509
- properties: typeInfo.properties,
1510
- validator
1511
- };
1512
- }
1513
1548
  async function writeSchemaModule(entries, allTypesMap, outputPath) {
1514
1549
  const allTypes = /* @__PURE__ */ new Map();
1515
1550
  for (const types of allTypesMap.values()) {
@@ -1521,9 +1556,17 @@ async function writeSchemaModule(entries, allTypesMap, outputPath) {
1521
1556
  await fs3.mkdir(path4.dirname(outputPath), { recursive: true });
1522
1557
  await fs3.writeFile(outputPath, source, "utf-8");
1523
1558
  }
1559
+ async function generateSchemaFile(routes, rootDir, outputPath) {
1560
+ const { sources, allTypesByFile } = collectRouteSchemaSources(routes, rootDir);
1561
+ const entries = sources.map(({ filePath, schemaName, typeInfo }) => ({
1562
+ filePath,
1563
+ schemaName,
1564
+ typeInfo
1565
+ }));
1566
+ await writeSchemaModule(entries, allTypesByFile, outputPath);
1567
+ }
1524
1568
  async function readManifestFile(inputPath) {
1525
- const fileUrl = `file://${inputPath}`;
1526
- const mod = await import(fileUrl);
1569
+ const mod = await importWithCacheBust(inputPath);
1527
1570
  const validators = mod.validators;
1528
1571
  const properties = mod.properties ?? {};
1529
1572
  const manifest = /* @__PURE__ */ new Map();
@@ -1546,27 +1589,51 @@ async function readManifestFile(inputPath) {
1546
1589
  }
1547
1590
  return manifest;
1548
1591
  }
1592
+ function remapManifestKeys(manifest, rootDir, prodDir) {
1593
+ const remapped = /* @__PURE__ */ new Map();
1594
+ const rootPrefix = rootDir + path4.sep;
1595
+ for (const [filePath, fileSchemas] of manifest) {
1596
+ let rel = filePath;
1597
+ if (filePath.startsWith(rootPrefix)) {
1598
+ rel = filePath.slice(rootPrefix.length);
1599
+ } else if (filePath.startsWith(rootDir)) {
1600
+ rel = filePath.slice(rootDir.length).replace(/^[/\\]/, "");
1601
+ }
1602
+ const prodRel = `${prodDir}/${rel.replace(/\.ts$/, ".js")}`;
1603
+ const prodAbs = path4.resolve(rootDir, prodRel);
1604
+ remapped.set(prodAbs, fileSchemas);
1605
+ }
1606
+ return remapped;
1607
+ }
1608
+ async function loadSchemaToRegistry(schemaPath, rootDir, prodDir, remap = true) {
1609
+ const manifest = await readManifestFile(schemaPath);
1610
+ const finalManifest = remap ? remapManifestKeys(manifest, rootDir, prodDir) : manifest;
1611
+ schemaRegistry.loadManifest(finalManifest);
1612
+ return finalManifest;
1613
+ }
1549
1614
  var init_generateSchema = __esm({
1550
1615
  "src/cli/generateSchema.ts"() {
1551
1616
  "use strict";
1552
1617
  init_generateValidatorCode();
1618
+ init_schemaRegistry();
1553
1619
  init_collectRouteSchemaSources();
1620
+ init_importWithCacheBust();
1554
1621
  }
1555
1622
  });
1556
1623
 
1557
1624
  // src/cli/generateRoutes.ts
1558
1625
  import fs4 from "fs";
1559
1626
  import path5 from "path";
1560
- function toProdFilePath(filePath) {
1627
+ function toProdFilePath(filePath, prodDir) {
1561
1628
  const jsPath = filePath.replace(/\.ts$/, ".js");
1562
- return jsPath.startsWith("dist/") ? jsPath : `dist/${jsPath}`;
1629
+ return jsPath.startsWith(`${prodDir}/`) ? jsPath : `${prodDir}/${jsPath}`;
1563
1630
  }
1564
- function serializeRoutes(routes, wsRoutes, rootDir) {
1631
+ function serializeRoutes(routes, wsRoutes, rootDir, prodDir = "dist") {
1565
1632
  const serialize = (route) => {
1566
- const middlewarePaths = extractMiddlewarePaths(route.filePath, rootDir);
1633
+ const middlewarePaths = extractMiddlewarePaths(route.filePath, rootDir, prodDir);
1567
1634
  const serialized = {
1568
1635
  urlPath: route.urlPath,
1569
- filePath: toProdFilePath(route.filePath),
1636
+ filePath: toProdFilePath(route.filePath, prodDir),
1570
1637
  paramNames: route.paramNames,
1571
1638
  isDynamic: route.isDynamic,
1572
1639
  isCatchAll: route.isCatchAll,
@@ -1582,21 +1649,21 @@ function serializeRoutes(routes, wsRoutes, rootDir) {
1582
1649
  wsRoutes: wsRoutes.map(serialize)
1583
1650
  };
1584
1651
  }
1585
- function extractMiddlewarePaths(routeFilePath, rootDir) {
1652
+ function extractMiddlewarePaths(routeFilePath, rootDir, prodDir) {
1586
1653
  const routeDir = path5.dirname(routeFilePath);
1587
1654
  const resolvedRoot = path5.resolve(rootDir);
1588
1655
  const paths = [];
1589
1656
  let currentDir = path5.resolve(rootDir, routeDir);
1590
1657
  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
- }
1658
+ const mwTsPath = path5.join(currentDir, "middlewares.ts");
1659
+ const mwJsPath = path5.join(currentDir, "middlewares.js");
1660
+ const absTsPath = path5.resolve(rootDir, mwTsPath);
1661
+ const absJsPath = path5.resolve(rootDir, mwJsPath);
1662
+ const absMwPath = fs4.existsSync(absTsPath) ? absTsPath : fs4.existsSync(absJsPath) ? absJsPath : null;
1663
+ if (absMwPath) {
1664
+ const relMwPath = path5.relative(rootDir, absMwPath);
1665
+ const prodAbsPath = path5.resolve(rootDir, toProdFilePath(relMwPath, prodDir));
1666
+ paths.push(prodAbsPath);
1600
1667
  }
1601
1668
  if (currentDir === resolvedRoot) break;
1602
1669
  const parentDir = path5.dirname(currentDir);
@@ -1669,23 +1736,184 @@ var init_generateRoutes = __esm({
1669
1736
  }
1670
1737
  });
1671
1738
 
1739
+ // src/utils/readTsconfig.ts
1740
+ import ts6 from "typescript";
1741
+ import path6 from "path";
1742
+ import fs5 from "fs";
1743
+ function readTsconfig(rootDir) {
1744
+ const tsconfigPath = path6.resolve(rootDir, "tsconfig.json");
1745
+ if (!fs5.existsSync(tsconfigPath)) return null;
1746
+ const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
1747
+ if (configFile.error || !configFile.config) return null;
1748
+ const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
1749
+ const baseUrl = parsed.options.baseUrl ?? rootDir;
1750
+ const rawPaths = parsed.options.paths;
1751
+ if (!rawPaths) return null;
1752
+ const paths = {};
1753
+ for (const [pattern, targets] of Object.entries(rawPaths)) {
1754
+ paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
1755
+ }
1756
+ return { baseUrl, paths };
1757
+ }
1758
+ var init_readTsconfig = __esm({
1759
+ "src/utils/readTsconfig.ts"() {
1760
+ "use strict";
1761
+ }
1762
+ });
1763
+
1764
+ // src/utils/resolveAlias.ts
1765
+ function resolveAlias(specifier, config) {
1766
+ const candidates = [];
1767
+ for (const [pattern, targets] of Object.entries(config.paths)) {
1768
+ const wildcardIndex = pattern.indexOf("*");
1769
+ if (wildcardIndex === -1) {
1770
+ if (specifier === pattern) {
1771
+ candidates.push(...targets);
1772
+ }
1773
+ continue;
1774
+ }
1775
+ const prefix = pattern.slice(0, wildcardIndex);
1776
+ const suffix = pattern.slice(wildcardIndex + 1);
1777
+ if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
1778
+ const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
1779
+ for (const target of targets) {
1780
+ candidates.push(target.replace("*", captured));
1781
+ }
1782
+ }
1783
+ }
1784
+ return candidates;
1785
+ }
1786
+ var init_resolveAlias = __esm({
1787
+ "src/utils/resolveAlias.ts"() {
1788
+ "use strict";
1789
+ }
1790
+ });
1791
+
1792
+ // src/cli/compileRoutes.ts
1793
+ import path7 from "path";
1794
+ import fs6 from "fs";
1795
+ import fg2 from "fast-glob";
1796
+ function toProdExtension(filePath) {
1797
+ if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
1798
+ if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
1799
+ if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
1800
+ return filePath;
1801
+ }
1802
+ function toProdImportPath(sourceFile, importer) {
1803
+ const importerDir = path7.dirname(importer);
1804
+ let rel = path7.relative(importerDir, sourceFile);
1805
+ rel = rel.split(path7.sep).join("/");
1806
+ if (!rel.startsWith(".")) rel = "./" + rel;
1807
+ return toProdExtension(rel);
1808
+ }
1809
+ function createAliasPlugin(config) {
1810
+ const EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1811
+ const INDEX_EXTS = [
1812
+ "/index.ts",
1813
+ "/index.tsx",
1814
+ "/index.js",
1815
+ "/index.jsx",
1816
+ "/index.mjs",
1817
+ "/index.cjs"
1818
+ ];
1819
+ const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
1820
+ return {
1821
+ name: "faapi-alias",
1822
+ setup(build) {
1823
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
1824
+ let source;
1825
+ try {
1826
+ source = fs6.readFileSync(args.path, "utf8");
1827
+ } catch {
1828
+ return void 0;
1829
+ }
1830
+ const importer = args.path;
1831
+ let modified = false;
1832
+ const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
1833
+ if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
1834
+ return full;
1835
+ }
1836
+ const candidates = resolveAlias(specifier, config);
1837
+ for (const candidate of candidates) {
1838
+ for (const ext of EXTS) {
1839
+ const file = candidate + ext;
1840
+ if (fs6.existsSync(file)) {
1841
+ modified = true;
1842
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1843
+ }
1844
+ }
1845
+ for (const indexExt of INDEX_EXTS) {
1846
+ const file = candidate + indexExt;
1847
+ if (fs6.existsSync(file)) {
1848
+ modified = true;
1849
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1850
+ }
1851
+ }
1852
+ }
1853
+ return full;
1854
+ });
1855
+ if (!modified) return void 0;
1856
+ return { contents: newSource, loader: "default" };
1857
+ });
1858
+ }
1859
+ };
1860
+ }
1861
+ async function compileRoutes(options) {
1862
+ const { rootDir, appDir, outDir, files, logLevel = "silent" } = options;
1863
+ const allFiles = files ?? await fg2([`${appDir}/**/*.ts`], {
1864
+ cwd: rootDir,
1865
+ onlyFiles: true,
1866
+ absolute: true,
1867
+ ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
1868
+ });
1869
+ if (allFiles.length === 0) {
1870
+ return { compiledFiles: [] };
1871
+ }
1872
+ const absOutDir = path7.resolve(rootDir, outDir);
1873
+ await fs6.promises.mkdir(absOutDir, { recursive: true });
1874
+ const tsconfig = readTsconfig(rootDir);
1875
+ const plugins = tsconfig ? [createAliasPlugin(tsconfig)] : [];
1876
+ const esbuild = await import("esbuild");
1877
+ await esbuild.build({
1878
+ entryPoints: allFiles,
1879
+ outdir: absOutDir,
1880
+ outbase: rootDir,
1881
+ bundle: false,
1882
+ platform: "node",
1883
+ format: "esm",
1884
+ sourcemap: true,
1885
+ packages: "external",
1886
+ plugins,
1887
+ logLevel
1888
+ });
1889
+ return { compiledFiles: allFiles };
1890
+ }
1891
+ var init_compileRoutes = __esm({
1892
+ "src/cli/compileRoutes.ts"() {
1893
+ "use strict";
1894
+ init_readTsconfig();
1895
+ init_resolveAlias();
1896
+ }
1897
+ });
1898
+
1672
1899
  // src/cli/buildCommand.ts
1673
1900
  var buildCommand_exports = {};
1674
1901
  __export(buildCommand_exports, {
1675
1902
  buildCommand: () => buildCommand,
1676
1903
  parseBuildArgs: () => parseBuildArgs
1677
1904
  });
1678
- import path6 from "path";
1679
- import fs5 from "fs";
1680
- import fg2 from "fast-glob";
1905
+ import path8 from "path";
1681
1906
  async function buildCommand(options) {
1682
1907
  const { rootDir, patterns, appDir, outdir, types } = options;
1683
1908
  console.log("faapi build started");
1684
1909
  console.log(`- Root: ${rootDir}`);
1685
1910
  console.log(`- Patterns: ${patterns.join(", ")}`);
1686
1911
  console.log(`- Output: ${outdir}`);
1687
- console.log("\n[1/5] Scanning routes...");
1688
- const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir);
1912
+ console.log("\n[1/5] Compiling TypeScript...");
1913
+ const result = await compileRoutes({ rootDir, appDir, outDir: outdir, logLevel: "silent" });
1914
+ console.log(` Compiled ${result.compiledFiles.length} file(s)`);
1915
+ console.log("\n[2/5] Scanning routes...");
1916
+ const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir, outdir);
1689
1917
  const sorted = sortRoutes(routes);
1690
1918
  console.log(` Found ${sorted.length} routes, ${wsRoutes.length} WS routes`);
1691
1919
  const conflicts = detectRouteConflicts(sorted);
@@ -1698,62 +1926,21 @@ async function buildCommand(options) {
1698
1926
  }
1699
1927
  }
1700
1928
  }
1701
- console.log("\n[2/5] Generating types...");
1702
- const typesPath = types ? path6.resolve(rootDir, types) : path6.resolve(rootDir, "faapi-types.ts");
1929
+ console.log("\n[3/5] Generating types...");
1930
+ const typesPath = types ? path8.resolve(rootDir, types) : path8.resolve(rootDir, "faapi-types.ts");
1703
1931
  await generateTypes(sorted, rootDir, typesPath);
1704
1932
  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);
1933
+ console.log("\n[4/5] Generating schema module...");
1934
+ const schemaPath = path8.resolve(rootDir, outdir, "faapi-schema.js");
1935
+ await generateSchemaFile(sorted, rootDir, schemaPath);
1709
1936
  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);
1937
+ console.log("\n[5/5] Generating routes manifest...");
1938
+ const routesPath = path8.resolve(rootDir, outdir, "faapi-routes.js");
1939
+ const serialized = serializeRoutes(sorted, wsRoutes, rootDir, outdir);
1713
1940
  await writeRoutesModule(serialized, routesPath);
1714
1941
  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
1942
  console.log("\nfaapi build completed");
1719
1943
  }
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
1944
  function parseBuildArgs(argv) {
1758
1945
  const rootDir = process.cwd();
1759
1946
  let outdir = "dist";
@@ -1790,7 +1977,7 @@ var init_buildCommand = __esm({
1790
1977
  init_generateTypes();
1791
1978
  init_generateSchema();
1792
1979
  init_generateRoutes();
1793
- init_collectRouteSchemaSources();
1980
+ init_compileRoutes();
1794
1981
  }
1795
1982
  });
1796
1983
 
@@ -2441,42 +2628,42 @@ var init_parseArgs = __esm({
2441
2628
  });
2442
2629
 
2443
2630
  // src/router/matchRoute.ts
2444
- function matchRoute(routes, method, path12) {
2631
+ function matchRoute(routes, method, path15) {
2445
2632
  for (const route of routes) {
2446
2633
  if (route.method !== method) {
2447
2634
  continue;
2448
2635
  }
2449
2636
  if (!route.isDynamic) {
2450
- if (route.urlPath === path12) {
2637
+ if (route.urlPath === path15) {
2451
2638
  return { route, params: {} };
2452
2639
  }
2453
2640
  continue;
2454
2641
  }
2455
- const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
2642
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2456
2643
  if (params !== null) {
2457
2644
  return { route, params };
2458
2645
  }
2459
2646
  }
2460
2647
  return null;
2461
2648
  }
2462
- function matchWsRoute(wsRoutes, path12) {
2649
+ function matchWsRoute(wsRoutes, path15) {
2463
2650
  for (const route of wsRoutes) {
2464
2651
  if (!route.isDynamic) {
2465
- if (route.urlPath === path12) {
2652
+ if (route.urlPath === path15) {
2466
2653
  return { route, params: {} };
2467
2654
  }
2468
2655
  continue;
2469
2656
  }
2470
- const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
2657
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
2471
2658
  if (params !== null) {
2472
2659
  return { route, params };
2473
2660
  }
2474
2661
  }
2475
2662
  return null;
2476
2663
  }
2477
- function matchDynamicPath(pattern, path12, paramNames, isCatchAll) {
2664
+ function matchDynamicPath(pattern, path15, paramNames, isCatchAll) {
2478
2665
  const patternSegments = pattern.split("/").filter(Boolean);
2479
- const pathSegments = path12.split("/").filter(Boolean);
2666
+ const pathSegments = path15.split("/").filter(Boolean);
2480
2667
  if (isCatchAll) {
2481
2668
  const nonCatchAllCount = patternSegments.length - 1;
2482
2669
  if (pathSegments.length <= nonCatchAllCount) {
@@ -2895,14 +3082,14 @@ var init_httpErrors = __esm({
2895
3082
  issues;
2896
3083
  };
2897
3084
  RouteNotFoundError = class extends FaapiError {
2898
- constructor(path12) {
2899
- super("ROUTE_NOT_FOUND", `Route not found: ${path12}`, 404);
3085
+ constructor(path15) {
3086
+ super("ROUTE_NOT_FOUND", `Route not found: ${path15}`, 404);
2900
3087
  this.name = "RouteNotFoundError";
2901
3088
  }
2902
3089
  };
2903
3090
  MethodNotAllowedError = class extends FaapiError {
2904
- constructor(method, path12, allowedMethods) {
2905
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path12}`, 405);
3091
+ constructor(method, path15, allowedMethods) {
3092
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path15}`, 405);
2906
3093
  this.allowedMethods = allowedMethods;
2907
3094
  this.name = "MethodNotAllowedError";
2908
3095
  }
@@ -3261,75 +3448,6 @@ var init_sendNodeResponse = __esm({
3261
3448
  }
3262
3449
  });
3263
3450
 
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
3451
  // src/validator/coerceInput.ts
3334
3452
  function coerceInput(input, properties) {
3335
3453
  const issues = [];
@@ -3342,16 +3460,16 @@ function coerceInput(input, properties) {
3342
3460
  }
3343
3461
  return { data, issues };
3344
3462
  }
3345
- function coerceValue(value, type, path12, issues) {
3463
+ function coerceValue(value, type, path15, issues) {
3346
3464
  switch (type.kind) {
3347
3465
  case "number":
3348
3466
  if (typeof value === "string") {
3349
- return coerceStringToNumber(value, path12, issues);
3467
+ return coerceStringToNumber(value, path15, issues);
3350
3468
  }
3351
3469
  return value;
3352
3470
  case "boolean":
3353
3471
  if (typeof value === "string") {
3354
- return coerceStringToBoolean(value, path12, issues);
3472
+ return coerceStringToBoolean(value, path15, issues);
3355
3473
  }
3356
3474
  return value;
3357
3475
  case "array":
@@ -3359,7 +3477,7 @@ function coerceValue(value, type, path12, issues) {
3359
3477
  return value;
3360
3478
  }
3361
3479
  if (Array.isArray(value)) {
3362
- return value.map((item, i) => coerceValue(item, type.element, `${path12}[${i}]`, issues));
3480
+ return value.map((item, i) => coerceValue(item, type.element, `${path15}[${i}]`, issues));
3363
3481
  }
3364
3482
  return value;
3365
3483
  case "tuple": {
@@ -3368,10 +3486,10 @@ function coerceValue(value, type, path12, issues) {
3368
3486
  return value.map((item, i) => {
3369
3487
  const elem = type.elements[i];
3370
3488
  if (elem && !elem.rest) {
3371
- return coerceValue(item, elem.type, `${path12}[${i}]`, issues);
3489
+ return coerceValue(item, elem.type, `${path15}[${i}]`, issues);
3372
3490
  }
3373
3491
  if (restElement) {
3374
- return coerceValue(item, restElement.type, `${path12}[${i}]`, issues);
3492
+ return coerceValue(item, restElement.type, `${path15}[${i}]`, issues);
3375
3493
  }
3376
3494
  return item;
3377
3495
  });
@@ -3379,7 +3497,7 @@ function coerceValue(value, type, path12, issues) {
3379
3497
  case "union":
3380
3498
  for (const member of type.members) {
3381
3499
  const tempIssues = [];
3382
- const coerced = coerceValue(value, member, path12, tempIssues);
3500
+ const coerced = coerceValue(value, member, path15, tempIssues);
3383
3501
  if (tempIssues.length === 0) {
3384
3502
  return coerced;
3385
3503
  }
@@ -3400,7 +3518,7 @@ function coerceValue(value, type, path12, issues) {
3400
3518
  result[prop.name] = coerceValue(
3401
3519
  obj[prop.name],
3402
3520
  prop.type,
3403
- `${path12}.${prop.name}`,
3521
+ `${path15}.${prop.name}`,
3404
3522
  issues
3405
3523
  );
3406
3524
  }
@@ -3412,31 +3530,31 @@ function coerceValue(value, type, path12, issues) {
3412
3530
  return value;
3413
3531
  }
3414
3532
  }
3415
- function coerceStringToNumber(value, path12, issues) {
3533
+ function coerceStringToNumber(value, path15, issues) {
3416
3534
  if (value.trim() === "") {
3417
3535
  issues.push({
3418
- path: path12,
3536
+ path: path15,
3419
3537
  code: "COERCE_FAILED",
3420
3538
  expected: "number",
3421
3539
  received: "string",
3422
- message: `\u5B57\u6BB5 "${path12}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3540
+ message: `\u5B57\u6BB5 "${path15}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3423
3541
  });
3424
3542
  return value;
3425
3543
  }
3426
3544
  const num = Number(value);
3427
3545
  if (Number.isNaN(num)) {
3428
3546
  issues.push({
3429
- path: path12,
3547
+ path: path15,
3430
3548
  code: "COERCE_FAILED",
3431
3549
  expected: "number",
3432
3550
  received: "string",
3433
- message: `\u5B57\u6BB5 "${path12}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3551
+ message: `\u5B57\u6BB5 "${path15}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
3434
3552
  });
3435
3553
  return value;
3436
3554
  }
3437
3555
  return num;
3438
3556
  }
3439
- function coerceStringToBoolean(value, path12, issues) {
3557
+ function coerceStringToBoolean(value, path15, issues) {
3440
3558
  if (value === "true" || value === "1") {
3441
3559
  return true;
3442
3560
  }
@@ -3444,11 +3562,11 @@ function coerceStringToBoolean(value, path12, issues) {
3444
3562
  return false;
3445
3563
  }
3446
3564
  issues.push({
3447
- path: path12,
3565
+ path: path15,
3448
3566
  code: "COERCE_FAILED",
3449
3567
  expected: "boolean",
3450
3568
  received: "string",
3451
- message: `\u5B57\u6BB5 "${path12}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A boolean`
3569
+ message: `\u5B57\u6BB5 "${path15}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A boolean`
3452
3570
  });
3453
3571
  return value;
3454
3572
  }
@@ -3588,26 +3706,26 @@ var init_cors = __esm({
3588
3706
  });
3589
3707
 
3590
3708
  // src/server/serveStatic.ts
3591
- import fs6 from "fs";
3592
- import path7 from "path";
3709
+ import fs7 from "fs";
3710
+ import path9 from "path";
3593
3711
  import { createReadStream } from "fs";
3594
3712
  import { Readable as Readable2 } from "stream";
3595
3713
  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)) {
3714
+ const resolved = path9.resolve(staticDir, "." + urlPath);
3715
+ const relative3 = path9.relative(staticDir, resolved);
3716
+ if (relative3.startsWith("..") || path9.isAbsolute(relative3)) {
3599
3717
  return null;
3600
3718
  }
3601
3719
  let stat4;
3602
3720
  try {
3603
- stat4 = await fs6.promises.stat(resolved);
3721
+ stat4 = await fs7.promises.stat(resolved);
3604
3722
  } catch {
3605
3723
  return null;
3606
3724
  }
3607
3725
  if (stat4.isDirectory()) {
3608
- const indexPath = path7.join(resolved, "index.html");
3726
+ const indexPath = path9.join(resolved, "index.html");
3609
3727
  try {
3610
- const indexStat = await fs6.promises.stat(indexPath);
3728
+ const indexStat = await fs7.promises.stat(indexPath);
3611
3729
  if (indexStat.isFile()) {
3612
3730
  return serveFile(indexPath, indexStat.size);
3613
3731
  }
@@ -3621,7 +3739,7 @@ async function serveStatic(urlPath, staticDir) {
3621
3739
  return serveFile(resolved, stat4.size);
3622
3740
  }
3623
3741
  function serveFile(filePath, size) {
3624
- const ext = path7.extname(filePath).toLowerCase();
3742
+ const ext = path9.extname(filePath).toLowerCase();
3625
3743
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
3626
3744
  const stream = createReadStream(filePath);
3627
3745
  const webStream = Readable2.toWeb(stream);
@@ -3821,7 +3939,7 @@ var init_wsHandler = __esm({
3821
3939
 
3822
3940
  // src/server/handleWsUpgrade.ts
3823
3941
  import { WebSocketServer, WebSocket } from "ws";
3824
- import path8 from "path";
3942
+ import path10 from "path";
3825
3943
  function getPathname(req) {
3826
3944
  const url = req.url ?? "/";
3827
3945
  const idx = url.indexOf("?");
@@ -3904,7 +4022,7 @@ function attachWebSocket(options) {
3904
4022
  const finalHandler = async () => {
3905
4023
  let handlers;
3906
4024
  try {
3907
- const absoluteFilePath = path8.resolve(rootDir, route.filePath);
4025
+ const absoluteFilePath = path10.resolve(rootDir, route.filePath);
3908
4026
  handlers = await loadWsHandler(absoluteFilePath, ctx);
3909
4027
  } catch (err) {
3910
4028
  const reason = err instanceof Error ? err.message : String(err);
@@ -3966,7 +4084,7 @@ import {
3966
4084
  createServer as createHttpServer
3967
4085
  } from "http";
3968
4086
  import { Readable as Readable3 } from "stream";
3969
- import path9 from "path";
4087
+ import path11 from "path";
3970
4088
  function toWebRequest(req) {
3971
4089
  const forwardedProto = req.headers["x-forwarded-proto"];
3972
4090
  const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
@@ -4010,15 +4128,15 @@ function limitStreamSize(stream, maxSize) {
4010
4128
  }
4011
4129
  });
4012
4130
  }
4013
- function findAllowedMethods(routes, path12) {
4131
+ function findAllowedMethods(routes, path15) {
4014
4132
  const methods = /* @__PURE__ */ new Set();
4015
4133
  for (const route of routes) {
4016
- if (route.urlPath === path12) {
4134
+ if (route.urlPath === path15) {
4017
4135
  methods.add(route.method);
4018
4136
  continue;
4019
4137
  }
4020
4138
  if (route.isDynamic) {
4021
- const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
4139
+ const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
4022
4140
  if (params !== null) {
4023
4141
  methods.add(route.method);
4024
4142
  }
@@ -4045,10 +4163,6 @@ function createServer(options) {
4045
4163
  if (wsRoutes) {
4046
4164
  globalRef.__FAAPI_WS_ROUTES__ = wsRoutes;
4047
4165
  }
4048
- if (schemaRegistry.size === 0 && routes.length > 0) {
4049
- const manifest = extractSchemasForRoutes(routes, rootDir);
4050
- schemaRegistry.loadManifest(manifest);
4051
- }
4052
4166
  const corsMiddleware = corsOption === false ? null : corsOption === true || corsOption === void 0 ? cors() : cors(corsOption);
4053
4167
  const server = createHttpServer((req, res) => {
4054
4168
  const currentRoutes = globalRef.__FAAPI_ROUTES__ ?? routes;
@@ -4085,7 +4199,7 @@ async function handleRequest(routes, rootDir, req, res, corsMiddleware, staticDi
4085
4199
  const match = matchRoute(routes, method, urlPath);
4086
4200
  if (!match) {
4087
4201
  if (staticDir) {
4088
- const absStaticDir = path9.resolve(rootDir, staticDir);
4202
+ const absStaticDir = path11.resolve(rootDir, staticDir);
4089
4203
  const staticResponse = await serveStatic(urlPath, absStaticDir);
4090
4204
  if (staticResponse) {
4091
4205
  return mergeMeta(staticResponse, meta);
@@ -4099,7 +4213,7 @@ async function handleRequest(routes, rootDir, req, res, corsMiddleware, staticDi
4099
4213
  }
4100
4214
  ctx.params = match.params;
4101
4215
  const { route } = match;
4102
- const absoluteFilePath = path9.resolve(rootDir, route.filePath);
4216
+ const absoluteFilePath = path11.resolve(rootDir, route.filePath);
4103
4217
  const routeModule = await loadRouteModule(absoluteFilePath, route.method);
4104
4218
  const input = await resolveInput(route.method, request);
4105
4219
  const inputType = getInputTypeForMethod(route.method);
@@ -4167,8 +4281,6 @@ var init_createServer = __esm({
4167
4281
  init_getClientIp();
4168
4282
  init_cors();
4169
4283
  init_serveStatic();
4170
- init_schemaRegistry();
4171
- init_generateSchema();
4172
4284
  init_handleWsUpgrade();
4173
4285
  init_serverUtils();
4174
4286
  MAX_BODY_SIZE = 10 * 1024 * 1024;
@@ -4354,7 +4466,7 @@ var init_esm = __esm({
4354
4466
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
4355
4467
  const statMethod = opts.lstat ? lstat : stat;
4356
4468
  if (wantBigintFsStats) {
4357
- this._stat = (path12) => statMethod(path12, { bigint: true });
4469
+ this._stat = (path15) => statMethod(path15, { bigint: true });
4358
4470
  } else {
4359
4471
  this._stat = statMethod;
4360
4472
  }
@@ -4379,8 +4491,8 @@ var init_esm = __esm({
4379
4491
  const par = this.parent;
4380
4492
  const fil = par && par.files;
4381
4493
  if (fil && fil.length > 0) {
4382
- const { path: path12, depth } = par;
4383
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
4494
+ const { path: path15, depth } = par;
4495
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path15));
4384
4496
  const awaited = await Promise.all(slice);
4385
4497
  for (const entry of awaited) {
4386
4498
  if (!entry)
@@ -4420,20 +4532,20 @@ var init_esm = __esm({
4420
4532
  this.reading = false;
4421
4533
  }
4422
4534
  }
4423
- async _exploreDir(path12, depth) {
4535
+ async _exploreDir(path15, depth) {
4424
4536
  let files;
4425
4537
  try {
4426
- files = await readdir(path12, this._rdOptions);
4538
+ files = await readdir(path15, this._rdOptions);
4427
4539
  } catch (error) {
4428
4540
  this._onError(error);
4429
4541
  }
4430
- return { files, depth, path: path12 };
4542
+ return { files, depth, path: path15 };
4431
4543
  }
4432
- async _formatEntry(dirent, path12) {
4544
+ async _formatEntry(dirent, path15) {
4433
4545
  let entry;
4434
4546
  const basename3 = this._isDirent ? dirent.name : dirent;
4435
4547
  try {
4436
- const fullPath = presolve(pjoin(path12, basename3));
4548
+ const fullPath = presolve(pjoin(path15, basename3));
4437
4549
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
4438
4550
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
4439
4551
  } catch (err) {
@@ -4494,16 +4606,16 @@ import { watchFile, unwatchFile, watch as fs_watch } from "fs";
4494
4606
  import { open, stat as stat2, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
4495
4607
  import * as sysPath from "path";
4496
4608
  import { type as osType } from "os";
4497
- function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
4609
+ function createFsWatchInstance(path15, options, listener, errHandler, emitRaw) {
4498
4610
  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));
4611
+ listener(path15);
4612
+ emitRaw(rawEvent, evPath, { watchedPath: path15 });
4613
+ if (evPath && path15 !== evPath) {
4614
+ fsWatchBroadcast(sysPath.resolve(path15, evPath), KEY_LISTENERS, sysPath.join(path15, evPath));
4503
4615
  }
4504
4616
  };
4505
4617
  try {
4506
- return fs_watch(path12, {
4618
+ return fs_watch(path15, {
4507
4619
  persistent: options.persistent
4508
4620
  }, handleEvent);
4509
4621
  } catch (error) {
@@ -4848,12 +4960,12 @@ var init_handler = __esm({
4848
4960
  listener(val1, val2, val3);
4849
4961
  });
4850
4962
  };
4851
- setFsWatchListener = (path12, fullPath, options, handlers) => {
4963
+ setFsWatchListener = (path15, fullPath, options, handlers) => {
4852
4964
  const { listener, errHandler, rawEmitter } = handlers;
4853
4965
  let cont = FsWatchInstances.get(fullPath);
4854
4966
  let watcher;
4855
4967
  if (!options.persistent) {
4856
- watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
4968
+ watcher = createFsWatchInstance(path15, options, listener, errHandler, rawEmitter);
4857
4969
  if (!watcher)
4858
4970
  return;
4859
4971
  return watcher.close.bind(watcher);
@@ -4864,7 +4976,7 @@ var init_handler = __esm({
4864
4976
  addAndConvert(cont, KEY_RAW, rawEmitter);
4865
4977
  } else {
4866
4978
  watcher = createFsWatchInstance(
4867
- path12,
4979
+ path15,
4868
4980
  options,
4869
4981
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
4870
4982
  errHandler,
@@ -4879,7 +4991,7 @@ var init_handler = __esm({
4879
4991
  cont.watcherUnusable = true;
4880
4992
  if (isWindows && error.code === "EPERM") {
4881
4993
  try {
4882
- const fd = await open(path12, "r");
4994
+ const fd = await open(path15, "r");
4883
4995
  await fd.close();
4884
4996
  broadcastErr(error);
4885
4997
  } catch (err) {
@@ -4910,7 +5022,7 @@ var init_handler = __esm({
4910
5022
  };
4911
5023
  };
4912
5024
  FsWatchFileInstances = /* @__PURE__ */ new Map();
4913
- setFsWatchFileListener = (path12, fullPath, options, handlers) => {
5025
+ setFsWatchFileListener = (path15, fullPath, options, handlers) => {
4914
5026
  const { listener, rawEmitter } = handlers;
4915
5027
  let cont = FsWatchFileInstances.get(fullPath);
4916
5028
  const copts = cont && cont.options;
@@ -4932,7 +5044,7 @@ var init_handler = __esm({
4932
5044
  });
4933
5045
  const currmtime = curr.mtimeMs;
4934
5046
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
4935
- foreach(cont.listeners, (listener2) => listener2(path12, curr));
5047
+ foreach(cont.listeners, (listener2) => listener2(path15, curr));
4936
5048
  }
4937
5049
  })
4938
5050
  };
@@ -4960,13 +5072,13 @@ var init_handler = __esm({
4960
5072
  * @param listener on fs change
4961
5073
  * @returns closer for the watcher instance
4962
5074
  */
4963
- _watchWithNodeFs(path12, listener) {
5075
+ _watchWithNodeFs(path15, listener) {
4964
5076
  const opts = this.fsw.options;
4965
- const directory = sysPath.dirname(path12);
4966
- const basename3 = sysPath.basename(path12);
5077
+ const directory = sysPath.dirname(path15);
5078
+ const basename3 = sysPath.basename(path15);
4967
5079
  const parent = this.fsw._getWatchedDir(directory);
4968
5080
  parent.add(basename3);
4969
- const absolutePath = sysPath.resolve(path12);
5081
+ const absolutePath = sysPath.resolve(path15);
4970
5082
  const options = {
4971
5083
  persistent: opts.persistent
4972
5084
  };
@@ -4976,12 +5088,12 @@ var init_handler = __esm({
4976
5088
  if (opts.usePolling) {
4977
5089
  const enableBin = opts.interval !== opts.binaryInterval;
4978
5090
  options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
4979
- closer = setFsWatchFileListener(path12, absolutePath, options, {
5091
+ closer = setFsWatchFileListener(path15, absolutePath, options, {
4980
5092
  listener,
4981
5093
  rawEmitter: this.fsw._emitRaw
4982
5094
  });
4983
5095
  } else {
4984
- closer = setFsWatchListener(path12, absolutePath, options, {
5096
+ closer = setFsWatchListener(path15, absolutePath, options, {
4985
5097
  listener,
4986
5098
  errHandler: this._boundHandleError,
4987
5099
  rawEmitter: this.fsw._emitRaw
@@ -5003,7 +5115,7 @@ var init_handler = __esm({
5003
5115
  let prevStats = stats;
5004
5116
  if (parent.has(basename3))
5005
5117
  return;
5006
- const listener = async (path12, newStats) => {
5118
+ const listener = async (path15, newStats) => {
5007
5119
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
5008
5120
  return;
5009
5121
  if (!newStats || newStats.mtimeMs === 0) {
@@ -5017,11 +5129,11 @@ var init_handler = __esm({
5017
5129
  this.fsw._emit(EV.CHANGE, file, newStats2);
5018
5130
  }
5019
5131
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
5020
- this.fsw._closeFile(path12);
5132
+ this.fsw._closeFile(path15);
5021
5133
  prevStats = newStats2;
5022
5134
  const closer2 = this._watchWithNodeFs(file, listener);
5023
5135
  if (closer2)
5024
- this.fsw._addPathCloser(path12, closer2);
5136
+ this.fsw._addPathCloser(path15, closer2);
5025
5137
  } else {
5026
5138
  prevStats = newStats2;
5027
5139
  }
@@ -5053,7 +5165,7 @@ var init_handler = __esm({
5053
5165
  * @param item basename of this item
5054
5166
  * @returns true if no more processing is needed for this entry.
5055
5167
  */
5056
- async _handleSymlink(entry, directory, path12, item) {
5168
+ async _handleSymlink(entry, directory, path15, item) {
5057
5169
  if (this.fsw.closed) {
5058
5170
  return;
5059
5171
  }
@@ -5063,7 +5175,7 @@ var init_handler = __esm({
5063
5175
  this.fsw._incrReadyCount();
5064
5176
  let linkPath;
5065
5177
  try {
5066
- linkPath = await fsrealpath(path12);
5178
+ linkPath = await fsrealpath(path15);
5067
5179
  } catch (e) {
5068
5180
  this.fsw._emitReady();
5069
5181
  return true;
@@ -5073,12 +5185,12 @@ var init_handler = __esm({
5073
5185
  if (dir.has(item)) {
5074
5186
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
5075
5187
  this.fsw._symlinkPaths.set(full, linkPath);
5076
- this.fsw._emit(EV.CHANGE, path12, entry.stats);
5188
+ this.fsw._emit(EV.CHANGE, path15, entry.stats);
5077
5189
  }
5078
5190
  } else {
5079
5191
  dir.add(item);
5080
5192
  this.fsw._symlinkPaths.set(full, linkPath);
5081
- this.fsw._emit(EV.ADD, path12, entry.stats);
5193
+ this.fsw._emit(EV.ADD, path15, entry.stats);
5082
5194
  }
5083
5195
  this.fsw._emitReady();
5084
5196
  return true;
@@ -5107,9 +5219,9 @@ var init_handler = __esm({
5107
5219
  return;
5108
5220
  }
5109
5221
  const item = entry.path;
5110
- let path12 = sysPath.join(directory, item);
5222
+ let path15 = sysPath.join(directory, item);
5111
5223
  current.add(item);
5112
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
5224
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path15, item)) {
5113
5225
  return;
5114
5226
  }
5115
5227
  if (this.fsw.closed) {
@@ -5118,8 +5230,8 @@ var init_handler = __esm({
5118
5230
  }
5119
5231
  if (item === target || !target && !previous.has(item)) {
5120
5232
  this.fsw._incrReadyCount();
5121
- path12 = sysPath.join(dir, sysPath.relative(dir, path12));
5122
- this._addToNodeFs(path12, initialAdd, wh, depth + 1);
5233
+ path15 = sysPath.join(dir, sysPath.relative(dir, path15));
5234
+ this._addToNodeFs(path15, initialAdd, wh, depth + 1);
5123
5235
  }
5124
5236
  }).on(EV.ERROR, this._boundHandleError);
5125
5237
  return new Promise((resolve3, reject) => {
@@ -5188,13 +5300,13 @@ var init_handler = __esm({
5188
5300
  * @param depth Child path actually targeted for watch
5189
5301
  * @param target Child path actually targeted for watch
5190
5302
  */
5191
- async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
5303
+ async _addToNodeFs(path15, initialAdd, priorWh, depth, target) {
5192
5304
  const ready = this.fsw._emitReady;
5193
- if (this.fsw._isIgnored(path12) || this.fsw.closed) {
5305
+ if (this.fsw._isIgnored(path15) || this.fsw.closed) {
5194
5306
  ready();
5195
5307
  return false;
5196
5308
  }
5197
- const wh = this.fsw._getWatchHelpers(path12);
5309
+ const wh = this.fsw._getWatchHelpers(path15);
5198
5310
  if (priorWh) {
5199
5311
  wh.filterPath = (entry) => priorWh.filterPath(entry);
5200
5312
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -5210,8 +5322,8 @@ var init_handler = __esm({
5210
5322
  const follow = this.fsw.options.followSymlinks;
5211
5323
  let closer;
5212
5324
  if (stats.isDirectory()) {
5213
- const absPath = sysPath.resolve(path12);
5214
- const targetPath = follow ? await fsrealpath(path12) : path12;
5325
+ const absPath = sysPath.resolve(path15);
5326
+ const targetPath = follow ? await fsrealpath(path15) : path15;
5215
5327
  if (this.fsw.closed)
5216
5328
  return;
5217
5329
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -5221,29 +5333,29 @@ var init_handler = __esm({
5221
5333
  this.fsw._symlinkPaths.set(absPath, targetPath);
5222
5334
  }
5223
5335
  } else if (stats.isSymbolicLink()) {
5224
- const targetPath = follow ? await fsrealpath(path12) : path12;
5336
+ const targetPath = follow ? await fsrealpath(path15) : path15;
5225
5337
  if (this.fsw.closed)
5226
5338
  return;
5227
5339
  const parent = sysPath.dirname(wh.watchPath);
5228
5340
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
5229
5341
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
5230
- closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
5342
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path15, wh, targetPath);
5231
5343
  if (this.fsw.closed)
5232
5344
  return;
5233
5345
  if (targetPath !== void 0) {
5234
- this.fsw._symlinkPaths.set(sysPath.resolve(path12), targetPath);
5346
+ this.fsw._symlinkPaths.set(sysPath.resolve(path15), targetPath);
5235
5347
  }
5236
5348
  } else {
5237
5349
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
5238
5350
  }
5239
5351
  ready();
5240
5352
  if (closer)
5241
- this.fsw._addPathCloser(path12, closer);
5353
+ this.fsw._addPathCloser(path15, closer);
5242
5354
  return false;
5243
5355
  } catch (error) {
5244
5356
  if (this.fsw._handleError(error)) {
5245
5357
  ready();
5246
- return path12;
5358
+ return path15;
5247
5359
  }
5248
5360
  }
5249
5361
  }
@@ -5282,26 +5394,26 @@ function createPattern(matcher) {
5282
5394
  }
5283
5395
  return () => false;
5284
5396
  }
5285
- function normalizePath2(path12) {
5286
- if (typeof path12 !== "string")
5397
+ function normalizePath2(path15) {
5398
+ if (typeof path15 !== "string")
5287
5399
  throw new Error("string expected");
5288
- path12 = sysPath2.normalize(path12);
5289
- path12 = path12.replace(/\\/g, "/");
5400
+ path15 = sysPath2.normalize(path15);
5401
+ path15 = path15.replace(/\\/g, "/");
5290
5402
  let prepend = false;
5291
- if (path12.startsWith("//"))
5403
+ if (path15.startsWith("//"))
5292
5404
  prepend = true;
5293
5405
  const DOUBLE_SLASH_RE2 = /\/\//;
5294
- while (path12.match(DOUBLE_SLASH_RE2))
5295
- path12 = path12.replace(DOUBLE_SLASH_RE2, "/");
5406
+ while (path15.match(DOUBLE_SLASH_RE2))
5407
+ path15 = path15.replace(DOUBLE_SLASH_RE2, "/");
5296
5408
  if (prepend)
5297
- path12 = "/" + path12;
5298
- return path12;
5409
+ path15 = "/" + path15;
5410
+ return path15;
5299
5411
  }
5300
5412
  function matchPatterns(patterns, testString, stats) {
5301
- const path12 = normalizePath2(testString);
5413
+ const path15 = normalizePath2(testString);
5302
5414
  for (let index = 0; index < patterns.length; index++) {
5303
5415
  const pattern = patterns[index];
5304
- if (pattern(path12, stats)) {
5416
+ if (pattern(path15, stats)) {
5305
5417
  return true;
5306
5418
  }
5307
5419
  }
@@ -5362,19 +5474,19 @@ var init_esm2 = __esm({
5362
5474
  }
5363
5475
  return str;
5364
5476
  };
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));
5477
+ normalizePathToUnix = (path15) => toUnix(sysPath2.normalize(toUnix(path15)));
5478
+ normalizeIgnored = (cwd = "") => (path15) => {
5479
+ if (typeof path15 === "string") {
5480
+ return normalizePathToUnix(sysPath2.isAbsolute(path15) ? path15 : sysPath2.join(cwd, path15));
5369
5481
  } else {
5370
- return path12;
5482
+ return path15;
5371
5483
  }
5372
5484
  };
5373
- getAbsolutePath = (path12, cwd) => {
5374
- if (sysPath2.isAbsolute(path12)) {
5375
- return path12;
5485
+ getAbsolutePath = (path15, cwd) => {
5486
+ if (sysPath2.isAbsolute(path15)) {
5487
+ return path15;
5376
5488
  }
5377
- return sysPath2.join(cwd, path12);
5489
+ return sysPath2.join(cwd, path15);
5378
5490
  };
5379
5491
  EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
5380
5492
  DirEntry = class {
@@ -5429,10 +5541,10 @@ var init_esm2 = __esm({
5429
5541
  STAT_METHOD_F = "stat";
5430
5542
  STAT_METHOD_L = "lstat";
5431
5543
  WatchHelper = class {
5432
- constructor(path12, follow, fsw) {
5544
+ constructor(path15, follow, fsw) {
5433
5545
  this.fsw = fsw;
5434
- const watchPath = path12;
5435
- this.path = path12 = path12.replace(REPLACER_RE, "");
5546
+ const watchPath = path15;
5547
+ this.path = path15 = path15.replace(REPLACER_RE, "");
5436
5548
  this.watchPath = watchPath;
5437
5549
  this.fullWatchPath = sysPath2.resolve(watchPath);
5438
5550
  this.dirParts = [];
@@ -5554,20 +5666,20 @@ var init_esm2 = __esm({
5554
5666
  this._closePromise = void 0;
5555
5667
  let paths = unifyPaths(paths_);
5556
5668
  if (cwd) {
5557
- paths = paths.map((path12) => {
5558
- const absPath = getAbsolutePath(path12, cwd);
5669
+ paths = paths.map((path15) => {
5670
+ const absPath = getAbsolutePath(path15, cwd);
5559
5671
  return absPath;
5560
5672
  });
5561
5673
  }
5562
- paths.forEach((path12) => {
5563
- this._removeIgnoredPath(path12);
5674
+ paths.forEach((path15) => {
5675
+ this._removeIgnoredPath(path15);
5564
5676
  });
5565
5677
  this._userIgnored = void 0;
5566
5678
  if (!this._readyCount)
5567
5679
  this._readyCount = 0;
5568
5680
  this._readyCount += paths.length;
5569
- Promise.all(paths.map(async (path12) => {
5570
- const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
5681
+ Promise.all(paths.map(async (path15) => {
5682
+ const res = await this._nodeFsHandler._addToNodeFs(path15, !_internal, void 0, 0, _origAdd);
5571
5683
  if (res)
5572
5684
  this._emitReady();
5573
5685
  return res;
@@ -5589,17 +5701,17 @@ var init_esm2 = __esm({
5589
5701
  return this;
5590
5702
  const paths = unifyPaths(paths_);
5591
5703
  const { cwd } = this.options;
5592
- paths.forEach((path12) => {
5593
- if (!sysPath2.isAbsolute(path12) && !this._closers.has(path12)) {
5704
+ paths.forEach((path15) => {
5705
+ if (!sysPath2.isAbsolute(path15) && !this._closers.has(path15)) {
5594
5706
  if (cwd)
5595
- path12 = sysPath2.join(cwd, path12);
5596
- path12 = sysPath2.resolve(path12);
5707
+ path15 = sysPath2.join(cwd, path15);
5708
+ path15 = sysPath2.resolve(path15);
5597
5709
  }
5598
- this._closePath(path12);
5599
- this._addIgnoredPath(path12);
5600
- if (this._watched.has(path12)) {
5710
+ this._closePath(path15);
5711
+ this._addIgnoredPath(path15);
5712
+ if (this._watched.has(path15)) {
5601
5713
  this._addIgnoredPath({
5602
- path: path12,
5714
+ path: path15,
5603
5715
  recursive: true
5604
5716
  });
5605
5717
  }
@@ -5663,38 +5775,38 @@ var init_esm2 = __esm({
5663
5775
  * @param stats arguments to be passed with event
5664
5776
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
5665
5777
  */
5666
- async _emit(event, path12, stats) {
5778
+ async _emit(event, path15, stats) {
5667
5779
  if (this.closed)
5668
5780
  return;
5669
5781
  const opts = this.options;
5670
5782
  if (isWindows)
5671
- path12 = sysPath2.normalize(path12);
5783
+ path15 = sysPath2.normalize(path15);
5672
5784
  if (opts.cwd)
5673
- path12 = sysPath2.relative(opts.cwd, path12);
5674
- const args = [path12];
5785
+ path15 = sysPath2.relative(opts.cwd, path15);
5786
+ const args = [path15];
5675
5787
  if (stats != null)
5676
5788
  args.push(stats);
5677
5789
  const awf = opts.awaitWriteFinish;
5678
5790
  let pw;
5679
- if (awf && (pw = this._pendingWrites.get(path12))) {
5791
+ if (awf && (pw = this._pendingWrites.get(path15))) {
5680
5792
  pw.lastChange = /* @__PURE__ */ new Date();
5681
5793
  return this;
5682
5794
  }
5683
5795
  if (opts.atomic) {
5684
5796
  if (event === EVENTS.UNLINK) {
5685
- this._pendingUnlinks.set(path12, [event, ...args]);
5797
+ this._pendingUnlinks.set(path15, [event, ...args]);
5686
5798
  setTimeout(() => {
5687
- this._pendingUnlinks.forEach((entry, path13) => {
5799
+ this._pendingUnlinks.forEach((entry, path16) => {
5688
5800
  this.emit(...entry);
5689
5801
  this.emit(EVENTS.ALL, ...entry);
5690
- this._pendingUnlinks.delete(path13);
5802
+ this._pendingUnlinks.delete(path16);
5691
5803
  });
5692
5804
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
5693
5805
  return this;
5694
5806
  }
5695
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
5807
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path15)) {
5696
5808
  event = EVENTS.CHANGE;
5697
- this._pendingUnlinks.delete(path12);
5809
+ this._pendingUnlinks.delete(path15);
5698
5810
  }
5699
5811
  }
5700
5812
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -5712,16 +5824,16 @@ var init_esm2 = __esm({
5712
5824
  this.emitWithAll(event, args);
5713
5825
  }
5714
5826
  };
5715
- this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
5827
+ this._awaitWriteFinish(path15, awf.stabilityThreshold, event, awfEmit);
5716
5828
  return this;
5717
5829
  }
5718
5830
  if (event === EVENTS.CHANGE) {
5719
- const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
5831
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path15, 50);
5720
5832
  if (isThrottled)
5721
5833
  return this;
5722
5834
  }
5723
5835
  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;
5836
+ const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path15) : path15;
5725
5837
  let stats2;
5726
5838
  try {
5727
5839
  stats2 = await stat3(fullPath);
@@ -5752,23 +5864,23 @@ var init_esm2 = __esm({
5752
5864
  * @param timeout duration of time to suppress duplicate actions
5753
5865
  * @returns tracking object or false if action should be suppressed
5754
5866
  */
5755
- _throttle(actionType, path12, timeout) {
5867
+ _throttle(actionType, path15, timeout) {
5756
5868
  if (!this._throttled.has(actionType)) {
5757
5869
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
5758
5870
  }
5759
5871
  const action = this._throttled.get(actionType);
5760
5872
  if (!action)
5761
5873
  throw new Error("invalid throttle");
5762
- const actionPath = action.get(path12);
5874
+ const actionPath = action.get(path15);
5763
5875
  if (actionPath) {
5764
5876
  actionPath.count++;
5765
5877
  return false;
5766
5878
  }
5767
5879
  let timeoutObject;
5768
5880
  const clear = () => {
5769
- const item = action.get(path12);
5881
+ const item = action.get(path15);
5770
5882
  const count = item ? item.count : 0;
5771
- action.delete(path12);
5883
+ action.delete(path15);
5772
5884
  clearTimeout(timeoutObject);
5773
5885
  if (item)
5774
5886
  clearTimeout(item.timeoutObject);
@@ -5776,7 +5888,7 @@ var init_esm2 = __esm({
5776
5888
  };
5777
5889
  timeoutObject = setTimeout(clear, timeout);
5778
5890
  const thr = { timeoutObject, clear, count: 0 };
5779
- action.set(path12, thr);
5891
+ action.set(path15, thr);
5780
5892
  return thr;
5781
5893
  }
5782
5894
  _incrReadyCount() {
@@ -5790,44 +5902,44 @@ var init_esm2 = __esm({
5790
5902
  * @param event
5791
5903
  * @param awfEmit Callback to be called when ready for event to be emitted.
5792
5904
  */
5793
- _awaitWriteFinish(path12, threshold, event, awfEmit) {
5905
+ _awaitWriteFinish(path15, threshold, event, awfEmit) {
5794
5906
  const awf = this.options.awaitWriteFinish;
5795
5907
  if (typeof awf !== "object")
5796
5908
  return;
5797
5909
  const pollInterval = awf.pollInterval;
5798
5910
  let timeoutHandler;
5799
- let fullPath = path12;
5800
- if (this.options.cwd && !sysPath2.isAbsolute(path12)) {
5801
- fullPath = sysPath2.join(this.options.cwd, path12);
5911
+ let fullPath = path15;
5912
+ if (this.options.cwd && !sysPath2.isAbsolute(path15)) {
5913
+ fullPath = sysPath2.join(this.options.cwd, path15);
5802
5914
  }
5803
5915
  const now = /* @__PURE__ */ new Date();
5804
5916
  const writes = this._pendingWrites;
5805
5917
  function awaitWriteFinishFn(prevStat) {
5806
5918
  statcb(fullPath, (err, curStat) => {
5807
- if (err || !writes.has(path12)) {
5919
+ if (err || !writes.has(path15)) {
5808
5920
  if (err && err.code !== "ENOENT")
5809
5921
  awfEmit(err);
5810
5922
  return;
5811
5923
  }
5812
5924
  const now2 = Number(/* @__PURE__ */ new Date());
5813
5925
  if (prevStat && curStat.size !== prevStat.size) {
5814
- writes.get(path12).lastChange = now2;
5926
+ writes.get(path15).lastChange = now2;
5815
5927
  }
5816
- const pw = writes.get(path12);
5928
+ const pw = writes.get(path15);
5817
5929
  const df = now2 - pw.lastChange;
5818
5930
  if (df >= threshold) {
5819
- writes.delete(path12);
5931
+ writes.delete(path15);
5820
5932
  awfEmit(void 0, curStat);
5821
5933
  } else {
5822
5934
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
5823
5935
  }
5824
5936
  });
5825
5937
  }
5826
- if (!writes.has(path12)) {
5827
- writes.set(path12, {
5938
+ if (!writes.has(path15)) {
5939
+ writes.set(path15, {
5828
5940
  lastChange: now,
5829
5941
  cancelWait: () => {
5830
- writes.delete(path12);
5942
+ writes.delete(path15);
5831
5943
  clearTimeout(timeoutHandler);
5832
5944
  return event;
5833
5945
  }
@@ -5838,8 +5950,8 @@ var init_esm2 = __esm({
5838
5950
  /**
5839
5951
  * Determines whether user has asked to ignore this path.
5840
5952
  */
5841
- _isIgnored(path12, stats) {
5842
- if (this.options.atomic && DOT_RE.test(path12))
5953
+ _isIgnored(path15, stats) {
5954
+ if (this.options.atomic && DOT_RE.test(path15))
5843
5955
  return true;
5844
5956
  if (!this._userIgnored) {
5845
5957
  const { cwd } = this.options;
@@ -5849,17 +5961,17 @@ var init_esm2 = __esm({
5849
5961
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
5850
5962
  this._userIgnored = anymatch(list, void 0);
5851
5963
  }
5852
- return this._userIgnored(path12, stats);
5964
+ return this._userIgnored(path15, stats);
5853
5965
  }
5854
- _isntIgnored(path12, stat4) {
5855
- return !this._isIgnored(path12, stat4);
5966
+ _isntIgnored(path15, stat4) {
5967
+ return !this._isIgnored(path15, stat4);
5856
5968
  }
5857
5969
  /**
5858
5970
  * Provides a set of common helpers and properties relating to symlink handling.
5859
5971
  * @param path file or directory pattern being watched
5860
5972
  */
5861
- _getWatchHelpers(path12) {
5862
- return new WatchHelper(path12, this.options.followSymlinks, this);
5973
+ _getWatchHelpers(path15) {
5974
+ return new WatchHelper(path15, this.options.followSymlinks, this);
5863
5975
  }
5864
5976
  // Directory helpers
5865
5977
  // -----------------
@@ -5891,63 +6003,63 @@ var init_esm2 = __esm({
5891
6003
  * @param item base path of item/directory
5892
6004
  */
5893
6005
  _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))
6006
+ const path15 = sysPath2.join(directory, item);
6007
+ const fullPath = sysPath2.resolve(path15);
6008
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path15) || this._watched.has(fullPath);
6009
+ if (!this._throttle("remove", path15, 100))
5898
6010
  return;
5899
6011
  if (!isDirectory && this._watched.size === 1) {
5900
6012
  this.add(directory, item, true);
5901
6013
  }
5902
- const wp = this._getWatchedDir(path12);
6014
+ const wp = this._getWatchedDir(path15);
5903
6015
  const nestedDirectoryChildren = wp.getChildren();
5904
- nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
6016
+ nestedDirectoryChildren.forEach((nested) => this._remove(path15, nested));
5905
6017
  const parent = this._getWatchedDir(directory);
5906
6018
  const wasTracked = parent.has(item);
5907
6019
  parent.remove(item);
5908
6020
  if (this._symlinkPaths.has(fullPath)) {
5909
6021
  this._symlinkPaths.delete(fullPath);
5910
6022
  }
5911
- let relPath = path12;
6023
+ let relPath = path15;
5912
6024
  if (this.options.cwd)
5913
- relPath = sysPath2.relative(this.options.cwd, path12);
6025
+ relPath = sysPath2.relative(this.options.cwd, path15);
5914
6026
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
5915
6027
  const event = this._pendingWrites.get(relPath).cancelWait();
5916
6028
  if (event === EVENTS.ADD)
5917
6029
  return;
5918
6030
  }
5919
- this._watched.delete(path12);
6031
+ this._watched.delete(path15);
5920
6032
  this._watched.delete(fullPath);
5921
6033
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
5922
- if (wasTracked && !this._isIgnored(path12))
5923
- this._emit(eventName, path12);
5924
- this._closePath(path12);
6034
+ if (wasTracked && !this._isIgnored(path15))
6035
+ this._emit(eventName, path15);
6036
+ this._closePath(path15);
5925
6037
  }
5926
6038
  /**
5927
6039
  * Closes all watchers for a path
5928
6040
  */
5929
- _closePath(path12) {
5930
- this._closeFile(path12);
5931
- const dir = sysPath2.dirname(path12);
5932
- this._getWatchedDir(dir).remove(sysPath2.basename(path12));
6041
+ _closePath(path15) {
6042
+ this._closeFile(path15);
6043
+ const dir = sysPath2.dirname(path15);
6044
+ this._getWatchedDir(dir).remove(sysPath2.basename(path15));
5933
6045
  }
5934
6046
  /**
5935
6047
  * Closes only file-specific watchers
5936
6048
  */
5937
- _closeFile(path12) {
5938
- const closers = this._closers.get(path12);
6049
+ _closeFile(path15) {
6050
+ const closers = this._closers.get(path15);
5939
6051
  if (!closers)
5940
6052
  return;
5941
6053
  closers.forEach((closer) => closer());
5942
- this._closers.delete(path12);
6054
+ this._closers.delete(path15);
5943
6055
  }
5944
- _addPathCloser(path12, closer) {
6056
+ _addPathCloser(path15, closer) {
5945
6057
  if (!closer)
5946
6058
  return;
5947
- let list = this._closers.get(path12);
6059
+ let list = this._closers.get(path15);
5948
6060
  if (!list) {
5949
6061
  list = [];
5950
- this._closers.set(path12, list);
6062
+ this._closers.set(path15, list);
5951
6063
  }
5952
6064
  list.push(closer);
5953
6065
  }
@@ -5974,23 +6086,36 @@ var init_esm2 = __esm({
5974
6086
  });
5975
6087
 
5976
6088
  // src/cli/watcher.ts
6089
+ import path12 from "path";
5977
6090
  function startWatcher(options) {
5978
6091
  const { rootDir, patterns, appDir } = options;
5979
6092
  let rebuildTimer = null;
6093
+ let pendingFiles = /* @__PURE__ */ new Set();
5980
6094
  async function rebuildRoutes() {
5981
6095
  try {
5982
6096
  const timestamp = Date.now();
5983
6097
  globalThis.__FAAPI_LOAD_TS__ = timestamp;
5984
6098
  invalidateMiddlewareCache();
5985
6099
  invalidateProgramCache();
5986
- const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir);
6100
+ const filesToCompile = Array.from(pendingFiles);
6101
+ pendingFiles = /* @__PURE__ */ new Set();
6102
+ if (filesToCompile.length > 0) {
6103
+ await compileRoutes({
6104
+ rootDir,
6105
+ appDir,
6106
+ outDir: DEV_OUT_DIR,
6107
+ files: filesToCompile
6108
+ });
6109
+ }
6110
+ const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir, DEV_OUT_DIR);
5987
6111
  const sorted = sortRoutes(routes);
5988
- const manifest = extractSchemasForRoutes(sorted, rootDir);
5989
- schemaRegistry.loadManifest(manifest);
6112
+ const schemaPath = path12.resolve(rootDir, DEV_OUT_DIR, "faapi-schema.js");
6113
+ await generateSchemaFile(sorted, rootDir, schemaPath);
6114
+ await loadSchemaToRegistry(schemaPath, rootDir, DEV_OUT_DIR, false);
5990
6115
  updateServerRoutes(sorted, wsRoutes);
5991
- const wsCount = wsRoutes.length;
6116
+ const recompiledCount = filesToCompile.length;
5992
6117
  console.log(
5993
- `- Routes rebuilt: ${sorted.length} route(s), ${wsCount} WS route(s), ${manifest.size} file(s)`
6118
+ `- Routes rebuilt: ${sorted.length} route(s), ${wsRoutes.length} WS route(s)${recompiledCount > 0 ? `, ${recompiledCount} file(s) recompiled` : ""}`
5994
6119
  );
5995
6120
  } catch (err) {
5996
6121
  console.error("- Error rebuilding routes:", err instanceof Error ? err.message : String(err));
@@ -6003,21 +6128,45 @@ function startWatcher(options) {
6003
6128
  void rebuildRoutes();
6004
6129
  }, 100);
6005
6130
  }
6006
- const watchPatterns = [...patterns, `${appDir}/**/middlewares.ts`];
6007
- const watcher = esm_default.watch(watchPatterns, {
6131
+ const watcher = esm_default.watch(appDir, {
6008
6132
  cwd: rootDir,
6009
6133
  ignoreInitial: true,
6010
- ignored: ["**/node_modules/**", "**/.faapi/**", "**/dist/**"]
6134
+ ignored: (filePath, stats) => {
6135
+ if (filePath.includes("node_modules") || filePath.includes(".faapi") || filePath.includes("dist") || filePath.includes(".git")) {
6136
+ return true;
6137
+ }
6138
+ if (!stats) return false;
6139
+ if (stats.isDirectory()) return false;
6140
+ return !filePath.endsWith(".ts");
6141
+ }
6142
+ });
6143
+ watcher.on("add", (file) => {
6144
+ pendingFiles.add(path12.resolve(rootDir, file));
6145
+ scheduleRebuild();
6146
+ });
6147
+ watcher.on("change", (file) => {
6148
+ pendingFiles.add(path12.resolve(rootDir, file));
6149
+ scheduleRebuild();
6150
+ });
6151
+ watcher.on("unlink", () => {
6152
+ scheduleRebuild();
6153
+ });
6154
+ watcher.on("error", (err) => {
6155
+ console.error("- Watcher error:", err instanceof Error ? err.message : String(err));
6156
+ });
6157
+ watcher.on("ready", () => {
6158
+ const watched = watcher.getWatched();
6159
+ const dirCount = Object.keys(watched).length;
6160
+ const fileCount = Object.values(watched).reduce((sum, files) => sum + files.length, 0);
6161
+ console.log(`- Watcher ready: ${dirCount} dir(s), ${fileCount} file(s) watched`);
6011
6162
  });
6012
- watcher.on("add", () => scheduleRebuild());
6013
- watcher.on("change", () => scheduleRebuild());
6014
- watcher.on("unlink", () => scheduleRebuild());
6015
6163
  console.log("- Watch mode enabled");
6016
6164
  }
6017
6165
  function updateServerRoutes(routes, wsRoutes) {
6018
6166
  globalThis.__FAAPI_ROUTES__ = routes;
6019
6167
  globalThis.__FAAPI_WS_ROUTES__ = wsRoutes;
6020
6168
  }
6169
+ var DEV_OUT_DIR;
6021
6170
  var init_watcher = __esm({
6022
6171
  "src/cli/watcher.ts"() {
6023
6172
  "use strict";
@@ -6026,15 +6175,16 @@ var init_watcher = __esm({
6026
6175
  init_sortRoutes();
6027
6176
  init_loadMiddlewares();
6028
6177
  init_createProgram();
6029
- init_schemaRegistry();
6030
6178
  init_generateSchema();
6179
+ init_compileRoutes();
6180
+ DEV_OUT_DIR = ".faapi/dev";
6031
6181
  }
6032
6182
  });
6033
6183
 
6034
6184
  // src/config/loadConfig.ts
6035
- import path10 from "path";
6036
- import fs7 from "fs";
6037
- import { pathToFileURL as pathToFileURL2 } from "url";
6185
+ import path13 from "path";
6186
+ import fs8 from "fs";
6187
+ import os from "os";
6038
6188
  function getEnv() {
6039
6189
  return process.env.FAAPI_ENV || process.env.NODE_ENV || "development";
6040
6190
  }
@@ -6059,12 +6209,15 @@ function deepMerge(base, override) {
6059
6209
  return result;
6060
6210
  }
6061
6211
  async function loadConfigFile(filePath) {
6062
- if (!fs7.existsSync(filePath)) {
6212
+ if (!fs8.existsSync(filePath)) {
6063
6213
  return null;
6064
6214
  }
6065
6215
  try {
6066
- const url = pathToFileURL2(filePath).href;
6067
- const module = await import(url);
6216
+ let modulePath = filePath;
6217
+ if (filePath.endsWith(".ts")) {
6218
+ modulePath = await compileConfigFile(filePath);
6219
+ }
6220
+ const module = await importWithCacheBust(modulePath);
6068
6221
  return module.default ?? {};
6069
6222
  } catch (err) {
6070
6223
  throw new Error(
@@ -6073,17 +6226,41 @@ async function loadConfigFile(filePath) {
6073
6226
  );
6074
6227
  }
6075
6228
  }
6229
+ async function compileConfigFile(tsPath) {
6230
+ const { createHash } = await import("crypto");
6231
+ const content = await fs8.promises.readFile(tsPath, "utf8");
6232
+ const hash = createHash("sha1").update(content).digest("hex").slice(0, 12);
6233
+ const tmpDir = path13.join(os.tmpdir(), "faapi-config");
6234
+ await fs8.promises.mkdir(tmpDir, { recursive: true });
6235
+ const outFile = path13.join(tmpDir, `config-${hash}.mjs`);
6236
+ if (fs8.existsSync(outFile)) {
6237
+ return outFile;
6238
+ }
6239
+ const esbuild = await import("esbuild");
6240
+ await esbuild.build({
6241
+ entryPoints: [tsPath],
6242
+ outfile: outFile,
6243
+ bundle: true,
6244
+ format: "esm",
6245
+ platform: "node",
6246
+ target: "node20",
6247
+ sourcemap: true,
6248
+ packages: "external",
6249
+ logLevel: "silent"
6250
+ });
6251
+ return outFile;
6252
+ }
6076
6253
  async function loadConfig(rootDir, configPath) {
6077
6254
  if (configPath) {
6078
- const resolvedPath = path10.resolve(rootDir, configPath);
6079
- if (!fs7.existsSync(resolvedPath)) {
6255
+ const resolvedPath = path13.resolve(rootDir, configPath);
6256
+ if (!fs8.existsSync(resolvedPath)) {
6080
6257
  throw new Error(`Config file not found: ${configPath}`);
6081
6258
  }
6082
6259
  return loadConfigFile(resolvedPath);
6083
6260
  }
6084
6261
  let baseConfig = null;
6085
6262
  for (const fileName of BASE_CONFIG_FILES) {
6086
- const filePath = path10.join(rootDir, fileName);
6263
+ const filePath = path13.join(rootDir, fileName);
6087
6264
  baseConfig = await loadConfigFile(filePath);
6088
6265
  if (baseConfig) break;
6089
6266
  }
@@ -6093,7 +6270,7 @@ async function loadConfig(rootDir, configPath) {
6093
6270
  const env = getEnv();
6094
6271
  const envFiles = [`faapi.config.${env}.ts`, `faapi.config.${env}.js`];
6095
6272
  for (const envFile of envFiles) {
6096
- const envConfig = await loadConfigFile(path10.join(rootDir, envFile));
6273
+ const envConfig = await loadConfigFile(path13.join(rootDir, envFile));
6097
6274
  if (envConfig) {
6098
6275
  baseConfig = deepMerge(baseConfig, envConfig);
6099
6276
  break;
@@ -6105,6 +6282,7 @@ var BASE_CONFIG_FILES;
6105
6282
  var init_loadConfig = __esm({
6106
6283
  "src/config/loadConfig.ts"() {
6107
6284
  "use strict";
6285
+ init_importWithCacheBust();
6108
6286
  BASE_CONFIG_FILES = ["faapi.config.ts", "faapi.config.js"];
6109
6287
  }
6110
6288
  });
@@ -6178,17 +6356,17 @@ var startCommand_exports = {};
6178
6356
  __export(startCommand_exports, {
6179
6357
  startCommand: () => startCommand
6180
6358
  });
6181
- import fs8 from "fs";
6182
- import path11 from "path";
6183
- import { pathToFileURL as pathToFileURL3 } from "url";
6359
+ import fs9 from "fs";
6360
+ import path14 from "path";
6184
6361
  async function startCommand(argv) {
6185
6362
  const args = parseArgs(argv);
6186
6363
  const rootDir = process.cwd();
6187
6364
  const isProd = args.mode === "start";
6365
+ const prodDir = isProd ? PROD_OUT_DIR : DEV_OUT_DIR2;
6188
6366
  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)) {
6367
+ const routesPath = path14.resolve(rootDir, PROD_OUT_DIR, "faapi-routes.js");
6368
+ const schemaPath2 = path14.resolve(rootDir, PROD_OUT_DIR, "faapi-schema.js");
6369
+ if (!fs9.existsSync(routesPath) || !fs9.existsSync(schemaPath2)) {
6192
6370
  console.error(
6193
6371
  "[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
6372
  );
@@ -6210,15 +6388,16 @@ async function startCommand(argv) {
6210
6388
  let routes;
6211
6389
  let wsRoutes;
6212
6390
  if (isProd) {
6213
- const routesPath = path11.resolve(rootDir, "dist", "faapi-routes.js");
6214
- const serialized = await import(pathToFileURL3(routesPath).href);
6391
+ const routesPath = path14.resolve(rootDir, PROD_OUT_DIR, "faapi-routes.js");
6392
+ const serialized = await importWithCacheBust(routesPath);
6215
6393
  const hydrated = await hydrateRoutes(serialized);
6216
6394
  routes = hydrated.routes;
6217
6395
  wsRoutes = hydrated.wsRoutes;
6218
6396
  console.log(`- Routes loaded: ${routes.length} routes, ${wsRoutes.length} WS routes`);
6219
6397
  } else {
6220
- const { patterns, appDir } = { patterns: args.patterns, appDir: args.appDir };
6221
- const scanned = await scanRoutes(rootDir, patterns, appDir);
6398
+ console.log("- Compiling TypeScript...");
6399
+ await compileRoutes({ rootDir, appDir: args.appDir, outDir: DEV_OUT_DIR2 });
6400
+ const scanned = await scanRoutes(rootDir, args.patterns, args.appDir, DEV_OUT_DIR2);
6222
6401
  routes = scanned.routes;
6223
6402
  wsRoutes = scanned.wsRoutes;
6224
6403
  console.log(`- Routes scanned: ${routes.length} routes, ${wsRoutes.length} WS routes`);
@@ -6233,19 +6412,14 @@ async function startCommand(argv) {
6233
6412
  }
6234
6413
  }
6235
6414
  }
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)`);
6415
+ const schemaPath = path14.resolve(rootDir, prodDir, "faapi-schema.js");
6416
+ if (!isProd) {
6417
+ await generateSchemaFile(sorted, rootDir, schemaPath);
6246
6418
  }
6419
+ await loadSchemaToRegistry(schemaPath, rootDir, prodDir, isProd);
6420
+ console.log(`- Schema loaded: ${schemaPath}`);
6247
6421
  if (!isProd && args.types) {
6248
- const typesPath = path11.resolve(rootDir, args.types);
6422
+ const typesPath = path14.resolve(rootDir, args.types);
6249
6423
  await generateTypes(sorted, rootDir, typesPath);
6250
6424
  console.log(`- Types generated: ${typesPath}`);
6251
6425
  }
@@ -6309,23 +6483,7 @@ async function startCommand(argv) {
6309
6483
  function isFaapiConfigKey(key) {
6310
6484
  return FAAPI_CONFIG_KEYS.has(key);
6311
6485
  }
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;
6486
+ var DEV_OUT_DIR2, PROD_OUT_DIR, FAAPI_CONFIG_KEYS;
6329
6487
  var init_startCommand = __esm({
6330
6488
  "src/cli/startCommand.ts"() {
6331
6489
  "use strict";
@@ -6337,10 +6495,13 @@ var init_startCommand = __esm({
6337
6495
  init_generateTypes();
6338
6496
  init_watcher();
6339
6497
  init_loadConfig();
6340
- init_schemaRegistry();
6341
6498
  init_generateSchema();
6342
6499
  init_generateRoutes();
6500
+ init_compileRoutes();
6343
6501
  init_loadPlugins();
6502
+ init_importWithCacheBust();
6503
+ DEV_OUT_DIR2 = ".faapi/dev";
6504
+ PROD_OUT_DIR = "dist";
6344
6505
  FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
6345
6506
  "port",
6346
6507
  "staticDir",
@@ -6357,9 +6518,6 @@ var init_startCommand = __esm({
6357
6518
  });
6358
6519
 
6359
6520
  // src/cli/index.ts
6360
- function isTsxPreloaded() {
6361
- return process.execArgv.some((arg) => arg.includes("tsx")) || (process.env.NODE_OPTIONS ?? "").includes("tsx");
6362
- }
6363
6521
  async function main() {
6364
6522
  const argv = process.argv.slice(2);
6365
6523
  const firstArg = argv.find((a) => !a.startsWith("-"));
@@ -6369,16 +6527,6 @@ async function main() {
6369
6527
  await buildCommand2(options);
6370
6528
  return;
6371
6529
  }
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
6530
  const { startCommand: startCommand2 } = await Promise.resolve().then(() => (init_startCommand(), startCommand_exports));
6383
6531
  await startCommand2(argv);
6384
6532
  }