@faapi/faapi 3.3.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/cli/index.js +2250 -1358
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +211 -288
- package/dist/index.js +1436 -663
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-FtbRkpVF.d.ts → routeTypes-DnJuuvq-.d.ts} +219 -1
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +1157 -378
- package/dist/testing.js.map +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -90,9 +90,10 @@ function createPrograms(filePaths) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
for (const { tsconfigPath, files } of groups.values()) {
|
|
93
|
-
const cacheKey = `shared::${tsconfigPath}
|
|
93
|
+
const cacheKey = `shared::${tsconfigPath}`;
|
|
94
94
|
let program = programCache.get(cacheKey);
|
|
95
|
-
|
|
95
|
+
const coversAll = program !== void 0 && files.every((f) => program.getSourceFile(f) !== void 0);
|
|
96
|
+
if (!program || !coversAll) {
|
|
96
97
|
program = buildProgram(files, tsconfigPath);
|
|
97
98
|
programCache.set(cacheKey, program);
|
|
98
99
|
}
|
|
@@ -147,7 +148,7 @@ import ts2 from "typescript";
|
|
|
147
148
|
function setProgramContext(program) {
|
|
148
149
|
currentProgram = program;
|
|
149
150
|
}
|
|
150
|
-
function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
151
|
+
function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set(), bindings = /* @__PURE__ */ new Map()) {
|
|
151
152
|
const kind = typeNode.kind;
|
|
152
153
|
switch (kind) {
|
|
153
154
|
case ts2.SyntaxKind.StringKeyword:
|
|
@@ -203,13 +204,13 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
|
|
|
203
204
|
if (ts2.isArrayTypeNode(typeNode)) {
|
|
204
205
|
return {
|
|
205
206
|
kind: "array",
|
|
206
|
-
element: resolveTypeNode(typeNode.elementType, checker, visited)
|
|
207
|
+
element: resolveTypeNode(typeNode.elementType, checker, visited, bindings)
|
|
207
208
|
};
|
|
208
209
|
}
|
|
209
210
|
if (ts2.isTupleTypeNode(typeNode)) {
|
|
210
211
|
const elements = typeNode.elements.map((e) => {
|
|
211
212
|
if (ts2.isRestTypeNode(e)) {
|
|
212
|
-
const inner = resolveTypeNode(e.type, checker, visited);
|
|
213
|
+
const inner = resolveTypeNode(e.type, checker, visited, bindings);
|
|
213
214
|
if (inner.kind === "array") {
|
|
214
215
|
return { type: inner.element, optional: false, rest: true };
|
|
215
216
|
}
|
|
@@ -217,20 +218,20 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
|
|
|
217
218
|
}
|
|
218
219
|
if (ts2.isNamedTupleMember(e)) {
|
|
219
220
|
return {
|
|
220
|
-
type: resolveTypeNode(e.type, checker, visited),
|
|
221
|
+
type: resolveTypeNode(e.type, checker, visited, bindings),
|
|
221
222
|
optional: !!e.questionToken,
|
|
222
223
|
rest: false
|
|
223
224
|
};
|
|
224
225
|
}
|
|
225
226
|
if (ts2.isOptionalTypeNode(e)) {
|
|
226
227
|
return {
|
|
227
|
-
type: resolveTypeNode(e.type, checker, visited),
|
|
228
|
+
type: resolveTypeNode(e.type, checker, visited, bindings),
|
|
228
229
|
optional: true,
|
|
229
230
|
rest: false
|
|
230
231
|
};
|
|
231
232
|
}
|
|
232
233
|
return {
|
|
233
|
-
type: resolveTypeNode(e, checker, visited),
|
|
234
|
+
type: resolveTypeNode(e, checker, visited, bindings),
|
|
234
235
|
optional: false,
|
|
235
236
|
rest: false
|
|
236
237
|
};
|
|
@@ -238,40 +239,80 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
|
|
|
238
239
|
return { kind: "tuple", elements };
|
|
239
240
|
}
|
|
240
241
|
if (ts2.isUnionTypeNode(typeNode)) {
|
|
241
|
-
const members = typeNode.types.map((t) => resolveTypeNode(t, checker, visited));
|
|
242
|
+
const members = typeNode.types.map((t) => resolveTypeNode(t, checker, visited, bindings));
|
|
242
243
|
return { kind: "union", members };
|
|
243
244
|
}
|
|
244
245
|
if (ts2.isIntersectionTypeNode(typeNode)) {
|
|
245
|
-
const
|
|
246
|
+
const propMap = /* @__PURE__ */ new Map();
|
|
246
247
|
for (const t of typeNode.types) {
|
|
247
|
-
const resolved = resolveTypeNode(t, checker, visited);
|
|
248
|
-
if (resolved.kind
|
|
249
|
-
|
|
248
|
+
const resolved = resolveTypeNode(t, checker, visited, bindings);
|
|
249
|
+
if (resolved.kind !== "object") {
|
|
250
|
+
throw new SchemaExtractionError(
|
|
251
|
+
typeNode.getText(),
|
|
252
|
+
`\u4EA4\u53C9\u7C7B\u578B\u5305\u542B\u975E object \u6210\u5458\uFF08${resolved.kind}\uFF09,\u8FD0\u884C\u65F6\u65E0\u6CD5\u6821\u9A8C\u2014\u2014branded \u7C7B\u578B\u5EFA\u8BAE\u6539\u7528\u5177\u4F53\u7C7B\u578B\u6216 unknown`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
for (const prop of resolved.properties) {
|
|
256
|
+
const existing = propMap.get(prop.name);
|
|
257
|
+
if (!existing) {
|
|
258
|
+
propMap.set(prop.name, prop);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (JSON.stringify(existing.type) !== JSON.stringify(prop.type) || existing.optional !== prop.optional) {
|
|
262
|
+
throw SchemaExtractionError.at(
|
|
263
|
+
typeNode,
|
|
264
|
+
typeNode.getText(),
|
|
265
|
+
`\u4EA4\u53C9\u7C7B\u578B\u6210\u5458\u7684\u540C\u540D\u5B57\u6BB5 "${prop.name}" \u7C7B\u578B\u51B2\u7A81\uFF08TS \u4E2D\u4E3A never\uFF09,\u8FD0\u884C\u65F6\u65E0\u6CD5\u6821\u9A8C`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (!existing.constraints?.length && prop.constraints?.length) {
|
|
269
|
+
propMap.set(prop.name, prop);
|
|
270
|
+
}
|
|
250
271
|
}
|
|
251
272
|
}
|
|
252
|
-
return { kind: "object", properties };
|
|
273
|
+
return { kind: "object", properties: [...propMap.values()] };
|
|
253
274
|
}
|
|
254
275
|
if (ts2.isTypeLiteralNode(typeNode)) {
|
|
255
|
-
return resolveTypeLiteral(typeNode, checker, visited);
|
|
276
|
+
return resolveTypeLiteral(typeNode, checker, visited, bindings);
|
|
256
277
|
}
|
|
257
278
|
if (ts2.isTypeOperatorNode(typeNode) && typeNode.operator === ts2.SyntaxKind.KeyOfKeyword) {
|
|
258
279
|
return resolveKeyOf(typeNode, checker);
|
|
259
280
|
}
|
|
260
281
|
if (ts2.isTypeOperatorNode(typeNode) && typeNode.operator === ts2.SyntaxKind.ReadonlyKeyword) {
|
|
261
|
-
return resolveTypeNode(typeNode.type, checker, visited);
|
|
282
|
+
return resolveTypeNode(typeNode.type, checker, visited, bindings);
|
|
262
283
|
}
|
|
263
284
|
if (ts2.isTypeReferenceNode(typeNode)) {
|
|
264
|
-
return resolveTypeReference(typeNode, checker, visited);
|
|
285
|
+
return resolveTypeReference(typeNode, checker, visited, bindings);
|
|
286
|
+
}
|
|
287
|
+
if (ts2.isExpressionWithTypeArguments(typeNode)) {
|
|
288
|
+
return resolveTypeReference(
|
|
289
|
+
{
|
|
290
|
+
getText: () => typeNode.getText(),
|
|
291
|
+
typeName: typeNode.expression,
|
|
292
|
+
typeArguments: typeNode.typeArguments
|
|
293
|
+
},
|
|
294
|
+
checker,
|
|
295
|
+
visited,
|
|
296
|
+
bindings
|
|
297
|
+
);
|
|
265
298
|
}
|
|
266
|
-
throw
|
|
299
|
+
throw SchemaExtractionError.at(typeNode, typeNode.getText(), "\u4E0D\u652F\u6301\u7684\u7C7B\u578B\u8BED\u6CD5");
|
|
267
300
|
}
|
|
268
|
-
function resolveTypeLiteral(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
301
|
+
function resolveTypeLiteral(typeNode, checker, visited = /* @__PURE__ */ new Set(), bindings = /* @__PURE__ */ new Map()) {
|
|
269
302
|
const properties = [];
|
|
303
|
+
let catchall;
|
|
270
304
|
for (const member of typeNode.members) {
|
|
305
|
+
if (ts2.isMethodSignature(member) || ts2.isGetAccessorDeclaration(member) || ts2.isSetAccessorDeclaration(member)) {
|
|
306
|
+
throw SchemaExtractionError.at(
|
|
307
|
+
member,
|
|
308
|
+
member.getText(),
|
|
309
|
+
"\u5BF9\u8C61\u7C7B\u578B\u542B\u65B9\u6CD5\u7B7E\u540D\u6216\u5B58\u53D6\u5668,\u8FD0\u884C\u65F6 JSON \u6570\u636E\u65E0\u6CD5\u6821\u9A8C\u65B9\u6CD5\u2014\u2014\u8BF7\u6539\u7528\u5177\u4F53\u5C5E\u6027\u7C7B\u578B"
|
|
310
|
+
);
|
|
311
|
+
}
|
|
271
312
|
if (ts2.isPropertySignature(member) && member.name) {
|
|
272
313
|
const name = member.name.getText();
|
|
273
314
|
const optional = !!member.questionToken;
|
|
274
|
-
const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
|
|
315
|
+
const type = member.type ? resolveTypeNode(member.type, checker, visited, bindings) : { kind: "any" };
|
|
275
316
|
const constraints = extractConstraintsFromJsDoc(member, name);
|
|
276
317
|
validateConstraints(constraints, type, name);
|
|
277
318
|
properties.push(
|
|
@@ -279,12 +320,10 @@ function resolveTypeLiteral(typeNode, checker, visited = /* @__PURE__ */ new Set
|
|
|
279
320
|
);
|
|
280
321
|
}
|
|
281
322
|
if (ts2.isIndexSignatureDeclaration(member)) {
|
|
282
|
-
|
|
283
|
-
const valueType = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
|
|
284
|
-
return { kind: "record", key: keyType, value: valueType };
|
|
323
|
+
catchall = member.type ? resolveTypeNode(member.type, checker, visited, bindings) : { kind: "any" };
|
|
285
324
|
}
|
|
286
325
|
}
|
|
287
|
-
return { kind: "object", properties };
|
|
326
|
+
return catchall !== void 0 ? { kind: "object", properties, catchall } : { kind: "object", properties };
|
|
288
327
|
}
|
|
289
328
|
function extractLiteralKeys(type) {
|
|
290
329
|
if (type.kind === "literal" && typeof type.value === "string") {
|
|
@@ -353,36 +392,48 @@ function resolveKeyOf(typeNode, checker) {
|
|
|
353
392
|
}
|
|
354
393
|
throw new SchemaExtractionError(typeNode.getText(), "keyof T \u7684\u7ED3\u679C\u65E0\u6CD5\u89E3\u6790\u4E3A\u5B57\u9762\u91CF\u8054\u5408");
|
|
355
394
|
}
|
|
356
|
-
function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
395
|
+
function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new Set(), bindings = /* @__PURE__ */ new Map()) {
|
|
357
396
|
const typeName = typeNode.typeName.getText();
|
|
397
|
+
const bound = bindings.get(typeName);
|
|
398
|
+
if (bound) {
|
|
399
|
+
return bound;
|
|
400
|
+
}
|
|
358
401
|
if (typeName === "Date") {
|
|
359
402
|
return { kind: "date" };
|
|
360
403
|
}
|
|
361
404
|
if ((typeName === "Array" || typeName === "ReadonlyArray") && typeNode.typeArguments?.length === 1) {
|
|
405
|
+
const [arg] = typeNode.typeArguments;
|
|
362
406
|
return {
|
|
363
407
|
kind: "array",
|
|
364
|
-
element: resolveTypeNode(
|
|
408
|
+
element: resolveTypeNode(arg, checker, visited, bindings)
|
|
365
409
|
};
|
|
366
410
|
}
|
|
367
411
|
if (typeName === "Record" && typeNode.typeArguments?.length === 2) {
|
|
412
|
+
const [keyArg, valueArg] = typeNode.typeArguments;
|
|
368
413
|
return {
|
|
369
414
|
kind: "record",
|
|
370
|
-
key: resolveTypeNode(
|
|
371
|
-
value: resolveTypeNode(
|
|
415
|
+
key: resolveTypeNode(keyArg, checker, visited, bindings),
|
|
416
|
+
value: resolveTypeNode(valueArg, checker, visited, bindings)
|
|
372
417
|
};
|
|
373
418
|
}
|
|
374
419
|
if ((typeName === "Partial" || typeName === "Required" || typeName === "Readonly") && typeNode.typeArguments?.length === 1) {
|
|
375
|
-
const inner = resolveTypeNode(typeNode.typeArguments[0], checker, visited);
|
|
420
|
+
const inner = resolveTypeNode(typeNode.typeArguments[0], checker, visited, bindings);
|
|
376
421
|
if (inner.kind === "object" && typeName === "Partial") {
|
|
377
422
|
return {
|
|
378
423
|
kind: "object",
|
|
379
424
|
properties: inner.properties.map((p) => ({ ...p, optional: true }))
|
|
380
425
|
};
|
|
381
426
|
}
|
|
427
|
+
if (inner.kind === "object" && typeName === "Required") {
|
|
428
|
+
return {
|
|
429
|
+
kind: "object",
|
|
430
|
+
properties: inner.properties.map((p) => ({ ...p, optional: false }))
|
|
431
|
+
};
|
|
432
|
+
}
|
|
382
433
|
return inner;
|
|
383
434
|
}
|
|
384
435
|
if ((typeName === "Pick" || typeName === "Omit") && typeNode.typeArguments?.length === 2) {
|
|
385
|
-
const innerType = resolveTypeNode(typeNode.typeArguments[0], checker, visited);
|
|
436
|
+
const innerType = resolveTypeNode(typeNode.typeArguments[0], checker, visited, bindings);
|
|
386
437
|
if (innerType.kind !== "object") {
|
|
387
438
|
throw new SchemaExtractionError(
|
|
388
439
|
typeNode.getText(),
|
|
@@ -390,7 +441,7 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
390
441
|
);
|
|
391
442
|
}
|
|
392
443
|
const keyTypeNode = typeNode.typeArguments[1];
|
|
393
|
-
let keys = extractLiteralKeys(resolveTypeNode(keyTypeNode, checker, visited));
|
|
444
|
+
let keys = extractLiteralKeys(resolveTypeNode(keyTypeNode, checker, visited, bindings));
|
|
394
445
|
if (keys === null) {
|
|
395
446
|
keys = extractKeysFromChecker(keyTypeNode, checker);
|
|
396
447
|
}
|
|
@@ -408,10 +459,11 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
408
459
|
"Map \u5FC5\u987B\u5E26 2 \u4E2A\u7C7B\u578B\u53C2\u6570\uFF0C\u5982 Map<K, V>\uFF0C\u88F8 Map \u4E0D\u652F\u6301"
|
|
409
460
|
);
|
|
410
461
|
}
|
|
462
|
+
const [mapKey, mapValue] = typeNode.typeArguments;
|
|
411
463
|
return {
|
|
412
464
|
kind: "map",
|
|
413
|
-
key: resolveTypeNode(
|
|
414
|
-
value: resolveTypeNode(
|
|
465
|
+
key: resolveTypeNode(mapKey, checker, visited, bindings),
|
|
466
|
+
value: resolveTypeNode(mapValue, checker, visited, bindings)
|
|
415
467
|
};
|
|
416
468
|
}
|
|
417
469
|
if (typeName === "Set") {
|
|
@@ -421,9 +473,10 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
421
473
|
"Set \u5FC5\u987B\u5E26 1 \u4E2A\u7C7B\u578B\u53C2\u6570\uFF0C\u5982 Set<T>\uFF0C\u88F8 Set \u4E0D\u652F\u6301"
|
|
422
474
|
);
|
|
423
475
|
}
|
|
476
|
+
const [setArg] = typeNode.typeArguments;
|
|
424
477
|
return {
|
|
425
478
|
kind: "set",
|
|
426
|
-
element: resolveTypeNode(
|
|
479
|
+
element: resolveTypeNode(setArg, checker, visited, bindings)
|
|
427
480
|
};
|
|
428
481
|
}
|
|
429
482
|
if (typeName === "WeakMap" || typeName === "WeakSet") {
|
|
@@ -446,21 +499,42 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
446
499
|
}
|
|
447
500
|
visited.add(typeName);
|
|
448
501
|
if (checker) {
|
|
449
|
-
const symbol = typeNode.typeName
|
|
502
|
+
const symbol = ts2.isIdentifier(typeNode.typeName) || ts2.isQualifiedName(typeNode.typeName) ? checker.getSymbolAtLocation(typeNode.typeName) : void 0;
|
|
450
503
|
if (symbol) {
|
|
451
504
|
const declaration = symbol.declarations?.[0];
|
|
452
505
|
if (declaration) {
|
|
453
506
|
if (ts2.isInterfaceDeclaration(declaration)) {
|
|
454
|
-
return resolveInterfaceDeclaration(
|
|
507
|
+
return resolveInterfaceDeclaration(
|
|
508
|
+
declaration,
|
|
509
|
+
checker,
|
|
510
|
+
visited,
|
|
511
|
+
bindings,
|
|
512
|
+
typeNode.typeArguments
|
|
513
|
+
);
|
|
455
514
|
}
|
|
456
515
|
if (ts2.isTypeAliasDeclaration(declaration)) {
|
|
457
|
-
|
|
516
|
+
const declBindings = bindTypeParameters(
|
|
517
|
+
declaration.typeParameters,
|
|
518
|
+
typeNode.typeArguments,
|
|
519
|
+
bindings,
|
|
520
|
+
checker,
|
|
521
|
+
visited,
|
|
522
|
+
typeNode
|
|
523
|
+
);
|
|
524
|
+
return resolveTypeNode(declaration.type, checker, visited, declBindings);
|
|
458
525
|
}
|
|
459
526
|
if (ts2.isEnumDeclaration(declaration)) {
|
|
460
527
|
return resolveEnumDeclaration(declaration);
|
|
461
528
|
}
|
|
462
529
|
if (ts2.isImportSpecifier(declaration) || ts2.isImportClause(declaration)) {
|
|
463
|
-
const resolved = resolveImportAlias(
|
|
530
|
+
const resolved = resolveImportAlias(
|
|
531
|
+
typeNode,
|
|
532
|
+
symbol,
|
|
533
|
+
checker,
|
|
534
|
+
visited,
|
|
535
|
+
bindings,
|
|
536
|
+
typeNode.typeArguments
|
|
537
|
+
);
|
|
464
538
|
if (resolved) return resolved;
|
|
465
539
|
}
|
|
466
540
|
}
|
|
@@ -468,17 +542,45 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
468
542
|
}
|
|
469
543
|
throw new SchemaExtractionError(typeNode.getText(), `\u65E0\u6CD5\u89E3\u6790\u7684\u5F15\u7528\u7C7B\u578B "${typeName}"`);
|
|
470
544
|
}
|
|
471
|
-
function
|
|
545
|
+
function bindTypeParameters(typeParameters, typeArguments, outerBindings, checker, visited, errorNode) {
|
|
546
|
+
if (!typeParameters || typeParameters.length === 0) return outerBindings;
|
|
547
|
+
const bindings = new Map(outerBindings);
|
|
548
|
+
for (let i = 0; i < typeParameters.length; i++) {
|
|
549
|
+
const param = typeParameters[i];
|
|
550
|
+
if (!param) continue;
|
|
551
|
+
const arg = typeArguments?.[i];
|
|
552
|
+
if (arg) {
|
|
553
|
+
bindings.set(param.name.text, resolveTypeNode(arg, checker, visited, outerBindings));
|
|
554
|
+
} else if (param.default) {
|
|
555
|
+
bindings.set(param.name.text, resolveTypeNode(param.default, checker, visited, bindings));
|
|
556
|
+
} else {
|
|
557
|
+
throw new SchemaExtractionError(
|
|
558
|
+
errorNode.getText(),
|
|
559
|
+
`\u6CDB\u578B\u53C2\u6570 "${param.name.text}" \u7F3A\u5C11\u7C7B\u578B\u5B9E\u53C2\uFF08\u4E14\u65E0\u9ED8\u8BA4\u7C7B\u578B\uFF09`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return bindings;
|
|
564
|
+
}
|
|
565
|
+
function resolveImportAlias(typeNode, symbol, checker, visited, bindings = /* @__PURE__ */ new Map(), typeArguments) {
|
|
472
566
|
const typeName = typeNode.typeName.getText();
|
|
473
567
|
try {
|
|
474
568
|
const aliased = checker.getAliasedSymbol(symbol);
|
|
475
569
|
if (aliased && aliased.declarations && aliased.declarations.length > 0) {
|
|
476
570
|
const decl = aliased.declarations[0];
|
|
477
571
|
if (ts2.isInterfaceDeclaration(decl)) {
|
|
478
|
-
return resolveInterfaceDeclaration(decl, checker, visited);
|
|
572
|
+
return resolveInterfaceDeclaration(decl, checker, visited, bindings, typeArguments);
|
|
479
573
|
}
|
|
480
574
|
if (ts2.isTypeAliasDeclaration(decl)) {
|
|
481
|
-
|
|
575
|
+
const declBindings = bindTypeParameters(
|
|
576
|
+
decl.typeParameters,
|
|
577
|
+
typeArguments,
|
|
578
|
+
bindings,
|
|
579
|
+
checker,
|
|
580
|
+
visited,
|
|
581
|
+
typeNode
|
|
582
|
+
);
|
|
583
|
+
return resolveTypeNode(decl.type, checker, visited, declBindings);
|
|
482
584
|
}
|
|
483
585
|
if (ts2.isEnumDeclaration(decl)) {
|
|
484
586
|
return resolveEnumDeclaration(decl);
|
|
@@ -496,10 +598,18 @@ function resolveImportAlias(typeNode, symbol, checker, visited) {
|
|
|
496
598
|
const found = findTopLevelDecl(sourceFile, typeName);
|
|
497
599
|
if (found) {
|
|
498
600
|
if (found.kind === "interface") {
|
|
499
|
-
return resolveInterfaceDeclaration(found.node, checker, visited);
|
|
601
|
+
return resolveInterfaceDeclaration(found.node, checker, visited, bindings, typeArguments);
|
|
500
602
|
}
|
|
501
603
|
if (found.kind === "typeAlias") {
|
|
502
|
-
|
|
604
|
+
const declBindings = bindTypeParameters(
|
|
605
|
+
found.node.typeParameters,
|
|
606
|
+
typeArguments,
|
|
607
|
+
bindings,
|
|
608
|
+
checker,
|
|
609
|
+
visited,
|
|
610
|
+
typeNode
|
|
611
|
+
);
|
|
612
|
+
return resolveTypeNode(found.node.type, checker, visited, declBindings);
|
|
503
613
|
}
|
|
504
614
|
if (found.kind === "enum") {
|
|
505
615
|
return resolveEnumDeclaration(found.node);
|
|
@@ -546,13 +656,22 @@ function resolveEnumDeclaration(node) {
|
|
|
546
656
|
}
|
|
547
657
|
return { kind: "union", members };
|
|
548
658
|
}
|
|
549
|
-
function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ new Set()) {
|
|
659
|
+
function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ new Set(), outerBindings = /* @__PURE__ */ new Map(), typeArguments) {
|
|
550
660
|
const properties = [];
|
|
551
661
|
const propMap = /* @__PURE__ */ new Map();
|
|
662
|
+
let catchall;
|
|
663
|
+
const bindings = bindTypeParameters(
|
|
664
|
+
node.typeParameters,
|
|
665
|
+
typeArguments,
|
|
666
|
+
outerBindings,
|
|
667
|
+
checker,
|
|
668
|
+
visited,
|
|
669
|
+
node
|
|
670
|
+
);
|
|
552
671
|
for (const heritageClause of node.heritageClauses ?? []) {
|
|
553
672
|
if (heritageClause.token === ts2.SyntaxKind.ExtendsKeyword) {
|
|
554
673
|
for (const expr of heritageClause.types) {
|
|
555
|
-
const parentType = resolveTypeNode(expr, checker, visited);
|
|
674
|
+
const parentType = resolveTypeNode(expr, checker, visited, bindings);
|
|
556
675
|
if (parentType.kind === "object") {
|
|
557
676
|
for (const prop of parentType.properties) {
|
|
558
677
|
propMap.set(prop.name, prop);
|
|
@@ -562,10 +681,17 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
|
|
|
562
681
|
}
|
|
563
682
|
}
|
|
564
683
|
for (const member of node.members) {
|
|
684
|
+
if (ts2.isMethodSignature(member) || ts2.isGetAccessorDeclaration(member) || ts2.isSetAccessorDeclaration(member)) {
|
|
685
|
+
throw SchemaExtractionError.at(
|
|
686
|
+
member,
|
|
687
|
+
member.getText(),
|
|
688
|
+
"\u63A5\u53E3\u542B\u65B9\u6CD5\u7B7E\u540D\u6216\u5B58\u53D6\u5668,\u8FD0\u884C\u65F6 JSON \u6570\u636E\u65E0\u6CD5\u6821\u9A8C\u65B9\u6CD5\u2014\u2014\u8BF7\u6539\u7528\u5177\u4F53\u5C5E\u6027\u7C7B\u578B"
|
|
689
|
+
);
|
|
690
|
+
}
|
|
565
691
|
if (ts2.isPropertySignature(member) && member.name) {
|
|
566
692
|
const name = member.name.getText();
|
|
567
693
|
const optional = !!member.questionToken;
|
|
568
|
-
const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
|
|
694
|
+
const type = member.type ? resolveTypeNode(member.type, checker, visited, bindings) : { kind: "any" };
|
|
569
695
|
const constraints = extractConstraintsFromJsDoc(member, name);
|
|
570
696
|
validateConstraints(constraints, type, name);
|
|
571
697
|
propMap.set(
|
|
@@ -574,15 +700,13 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
|
|
|
574
700
|
);
|
|
575
701
|
}
|
|
576
702
|
if (ts2.isIndexSignatureDeclaration(member)) {
|
|
577
|
-
|
|
578
|
-
const valueType = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
|
|
579
|
-
return { kind: "record", key: keyType, value: valueType };
|
|
703
|
+
catchall = member.type ? resolveTypeNode(member.type, checker, visited, bindings) : { kind: "any" };
|
|
580
704
|
}
|
|
581
705
|
}
|
|
582
706
|
for (const prop of propMap.values()) {
|
|
583
707
|
properties.push(prop);
|
|
584
708
|
}
|
|
585
|
-
return { kind: "object", properties };
|
|
709
|
+
return catchall !== void 0 ? { kind: "object", properties, catchall } : { kind: "object", properties };
|
|
586
710
|
}
|
|
587
711
|
function extractConstraintsFromJsDoc(node, fieldName) {
|
|
588
712
|
const jsDocs = ts2.getJSDocCommentsAndTags(node).filter((entry) => ts2.isJSDoc(entry));
|
|
@@ -705,15 +829,38 @@ var init_resolveTypeNode = __esm({
|
|
|
705
829
|
"src/ast/resolveTypeNode.ts"() {
|
|
706
830
|
"use strict";
|
|
707
831
|
currentProgram = null;
|
|
708
|
-
SchemaExtractionError = class extends Error {
|
|
709
|
-
constructor(typeText, reason, options) {
|
|
710
|
-
super(
|
|
832
|
+
SchemaExtractionError = class _SchemaExtractionError extends Error {
|
|
833
|
+
constructor(typeText, reason, options, location) {
|
|
834
|
+
super(
|
|
835
|
+
`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}` + (location ? ` (${location.file}:${location.line}:${location.column})` : ""),
|
|
836
|
+
options
|
|
837
|
+
);
|
|
711
838
|
this.typeText = typeText;
|
|
712
839
|
this.reason = reason;
|
|
840
|
+
this.location = location;
|
|
713
841
|
this.name = "SchemaExtractionError";
|
|
714
842
|
}
|
|
715
843
|
typeText;
|
|
716
844
|
reason;
|
|
845
|
+
location;
|
|
846
|
+
/**
|
|
847
|
+
* 从 AST 节点构造错误(自动携带 file:line:column)
|
|
848
|
+
*
|
|
849
|
+
* 所有抛错点应优先使用此工厂——错误无行号时,几百行的类型文件只能靠
|
|
850
|
+
* 类型名肉眼定位;解析 lib.d.ts 类型别名时还会出现错误文本与文件上下文错位
|
|
851
|
+
*/
|
|
852
|
+
static at(node, typeText, reason) {
|
|
853
|
+
const sourceFile = node.getSourceFile();
|
|
854
|
+
if (!sourceFile) {
|
|
855
|
+
return new _SchemaExtractionError(typeText, reason);
|
|
856
|
+
}
|
|
857
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
858
|
+
return new _SchemaExtractionError(typeText, reason, void 0, {
|
|
859
|
+
file: sourceFile.fileName,
|
|
860
|
+
line: line + 1,
|
|
861
|
+
column: character + 1
|
|
862
|
+
});
|
|
863
|
+
}
|
|
717
864
|
};
|
|
718
865
|
NUMBER_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
|
|
719
866
|
"max",
|
|
@@ -780,55 +927,47 @@ function extractTypeInfo(program, filePath, typeName) {
|
|
|
780
927
|
return;
|
|
781
928
|
}
|
|
782
929
|
});
|
|
783
|
-
return result;
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
function extractAllTypes(program, filePath) {
|
|
789
|
-
const sourceFile = program.getSourceFile(filePath);
|
|
790
|
-
if (!sourceFile) return /* @__PURE__ */ new Map();
|
|
791
|
-
const checker = program.getTypeChecker();
|
|
792
|
-
setProgramContext(program);
|
|
793
|
-
try {
|
|
794
|
-
const result = /* @__PURE__ */ new Map();
|
|
795
|
-
ts3.forEachChild(sourceFile, (node) => {
|
|
796
|
-
if (ts3.isInterfaceDeclaration(node)) {
|
|
797
|
-
const visited = /* @__PURE__ */ new Set();
|
|
798
|
-
visited.add(node.name.text);
|
|
799
|
-
const runtimeType = withFileContext(
|
|
800
|
-
filePath,
|
|
801
|
-
node.name.text,
|
|
802
|
-
() => resolveInterfaceDeclaration(node, checker, visited)
|
|
803
|
-
);
|
|
804
|
-
result.set(node.name.text, {
|
|
805
|
-
name: node.name.text,
|
|
806
|
-
properties: runtimeType.kind === "object" ? runtimeType.properties : [],
|
|
807
|
-
runtimeType
|
|
808
|
-
});
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
if (ts3.isTypeAliasDeclaration(node)) {
|
|
812
|
-
const visited = /* @__PURE__ */ new Set();
|
|
813
|
-
visited.add(node.name.text);
|
|
814
|
-
const runtimeType = withFileContext(
|
|
815
|
-
filePath,
|
|
816
|
-
node.name.text,
|
|
817
|
-
() => resolveTypeNode(node.type, checker, visited)
|
|
818
|
-
);
|
|
819
|
-
result.set(node.name.text, {
|
|
820
|
-
name: node.name.text,
|
|
821
|
-
properties: runtimeType.kind === "object" ? runtimeType.properties : [],
|
|
822
|
-
runtimeType
|
|
823
|
-
});
|
|
824
|
-
return;
|
|
930
|
+
if (result) return result;
|
|
931
|
+
for (const sf of program.getSourceFiles()) {
|
|
932
|
+
if (sf === sourceFile) continue;
|
|
933
|
+
if (sf.fileName.includes("/node_modules/") || sf.fileName.includes("typescript/lib/")) {
|
|
934
|
+
continue;
|
|
825
935
|
}
|
|
826
|
-
|
|
827
|
-
|
|
936
|
+
const found = findTopLevelDecl(sf, typeName);
|
|
937
|
+
if (!found) continue;
|
|
938
|
+
const visited = /* @__PURE__ */ new Set();
|
|
939
|
+
visited.add(typeName);
|
|
940
|
+
const runtimeType = withFileContext(filePath, typeName, () => {
|
|
941
|
+
if (found.kind === "interface") {
|
|
942
|
+
return resolveInterfaceDeclaration(found.node, checker, visited);
|
|
943
|
+
}
|
|
944
|
+
if (found.kind === "typeAlias") {
|
|
945
|
+
return resolveTypeNode(found.node.type, checker, visited);
|
|
946
|
+
}
|
|
947
|
+
return resolveEnumDeclaration(found.node);
|
|
948
|
+
});
|
|
949
|
+
return {
|
|
950
|
+
name: typeName,
|
|
951
|
+
properties: runtimeType.kind === "object" ? runtimeType.properties : [],
|
|
952
|
+
runtimeType
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
return null;
|
|
828
956
|
} finally {
|
|
829
957
|
setProgramContext(null);
|
|
830
958
|
}
|
|
831
959
|
}
|
|
960
|
+
function createLazyTypeResolver(program, filePath) {
|
|
961
|
+
const cache = /* @__PURE__ */ new Map();
|
|
962
|
+
return {
|
|
963
|
+
resolve(name) {
|
|
964
|
+
if (cache.has(name)) return cache.get(name);
|
|
965
|
+
const info = extractTypeInfo(program, filePath, name);
|
|
966
|
+
cache.set(name, info);
|
|
967
|
+
return info;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
}
|
|
832
971
|
function withFileContext(filePath, typeName, fn) {
|
|
833
972
|
try {
|
|
834
973
|
return fn();
|
|
@@ -838,7 +977,8 @@ function withFileContext(filePath, typeName, fn) {
|
|
|
838
977
|
const enriched = new SchemaExtractionError(
|
|
839
978
|
err.typeText,
|
|
840
979
|
`${err.reason}\uFF08\u6587\u4EF6: ${fileName}, \u7C7B\u578B: ${typeName}\uFF09`,
|
|
841
|
-
{ cause: err }
|
|
980
|
+
{ cause: err },
|
|
981
|
+
err.location
|
|
842
982
|
);
|
|
843
983
|
throw enriched;
|
|
844
984
|
}
|
|
@@ -883,9 +1023,13 @@ var init_schemaName = __esm({
|
|
|
883
1023
|
// src/injection/resolveInjection.ts
|
|
884
1024
|
import ts4 from "typescript";
|
|
885
1025
|
function resolveInjection(fn) {
|
|
1026
|
+
const cached = injectionCache.get(fn);
|
|
1027
|
+
if (cached) {
|
|
1028
|
+
return cached;
|
|
1029
|
+
}
|
|
886
1030
|
const fnStr = fn.toString();
|
|
887
1031
|
const params = extractParamsWithAst(fnStr);
|
|
888
|
-
|
|
1032
|
+
const items = params.map((param) => {
|
|
889
1033
|
const type = PARAM_TYPE_MAP[param.name] || "unknown";
|
|
890
1034
|
return {
|
|
891
1035
|
name: param.name,
|
|
@@ -894,6 +1038,8 @@ function resolveInjection(fn) {
|
|
|
894
1038
|
// 运行时类型已擦除
|
|
895
1039
|
};
|
|
896
1040
|
});
|
|
1041
|
+
injectionCache.set(fn, items);
|
|
1042
|
+
return items;
|
|
897
1043
|
}
|
|
898
1044
|
function extractParamsWithAst(fnStr) {
|
|
899
1045
|
const sourceFile = ts4.createSourceFile(
|
|
@@ -950,7 +1096,7 @@ function extractParamName(param, names) {
|
|
|
950
1096
|
return;
|
|
951
1097
|
}
|
|
952
1098
|
}
|
|
953
|
-
var PARAM_TYPE_MAP;
|
|
1099
|
+
var PARAM_TYPE_MAP, injectionCache;
|
|
954
1100
|
var init_resolveInjection = __esm({
|
|
955
1101
|
"src/injection/resolveInjection.ts"() {
|
|
956
1102
|
"use strict";
|
|
@@ -973,13 +1119,13 @@ var init_resolveInjection = __esm({
|
|
|
973
1119
|
agents: "agents"
|
|
974
1120
|
// Phase 2.3
|
|
975
1121
|
};
|
|
1122
|
+
injectionCache = /* @__PURE__ */ new WeakMap();
|
|
976
1123
|
}
|
|
977
1124
|
});
|
|
978
1125
|
|
|
979
1126
|
// src/injection/analyzeInjection.ts
|
|
980
1127
|
import ts5 from "typescript";
|
|
981
|
-
function
|
|
982
|
-
const sourceFile = ts5.createSourceFile("temp.ts", code, ts5.ScriptTarget.Latest, true);
|
|
1128
|
+
function analyzeInjectionInSourceFile(sourceFile, functionName) {
|
|
983
1129
|
const params = [];
|
|
984
1130
|
ts5.forEachChild(sourceFile, (node) => {
|
|
985
1131
|
if (ts5.isFunctionDeclaration(node) && node.name?.text === functionName) {
|
|
@@ -1041,28 +1187,23 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
1041
1187
|
entry.methods.add(route.method);
|
|
1042
1188
|
}
|
|
1043
1189
|
const programByFile = createPrograms([...methodsByFile.keys()]);
|
|
1044
|
-
const
|
|
1045
|
-
const mergedAllTypes = /* @__PURE__ */ new Map();
|
|
1190
|
+
const resolversByFile = /* @__PURE__ */ new Map();
|
|
1046
1191
|
for (const filePath of methodsByFile.keys()) {
|
|
1047
|
-
|
|
1048
|
-
const allTypes = extractAllTypes(program, filePath);
|
|
1049
|
-
allTypesByFile.set(filePath, allTypes);
|
|
1050
|
-
for (const [name, info] of allTypes) {
|
|
1051
|
-
mergedAllTypes.set(name, info);
|
|
1052
|
-
}
|
|
1192
|
+
resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
|
|
1053
1193
|
}
|
|
1054
1194
|
const sources = [];
|
|
1055
1195
|
for (const [filePath, entry] of methodsByFile) {
|
|
1056
1196
|
const program = programByFile.get(filePath);
|
|
1057
1197
|
const sourceFile = program.getSourceFile(filePath);
|
|
1058
|
-
|
|
1198
|
+
if (!sourceFile) continue;
|
|
1199
|
+
const resolver = resolversByFile.get(filePath);
|
|
1059
1200
|
for (const method of entry.methods) {
|
|
1060
1201
|
const inputType = getInputTypeForMethod(method);
|
|
1061
1202
|
const schemaName = getSchemaName(method, inputType);
|
|
1062
|
-
const meta =
|
|
1203
|
+
const meta = analyzeInjectionInSourceFile(sourceFile, method);
|
|
1063
1204
|
const param = meta.params.find((p) => p.type === inputType) ?? (inputType === "body" ? meta.params.find((p) => p.type === "form") : void 0);
|
|
1064
1205
|
const isForm = param?.type === "form";
|
|
1065
|
-
const typeInfo = param?.typeName ?
|
|
1206
|
+
const typeInfo = param?.typeName ? resolver.resolve(param.typeName) : null;
|
|
1066
1207
|
sources.push({
|
|
1067
1208
|
urlPath: entry.urlPath,
|
|
1068
1209
|
filePath,
|
|
@@ -1072,7 +1213,7 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
1072
1213
|
});
|
|
1073
1214
|
}
|
|
1074
1215
|
}
|
|
1075
|
-
return { sources,
|
|
1216
|
+
return { sources, resolversByFile };
|
|
1076
1217
|
}
|
|
1077
1218
|
var init_collectRouteSchemaSources = __esm({
|
|
1078
1219
|
"src/cli/collectRouteSchemaSources.ts"() {
|
|
@@ -1085,6 +1226,21 @@ var init_collectRouteSchemaSources = __esm({
|
|
|
1085
1226
|
}
|
|
1086
1227
|
});
|
|
1087
1228
|
|
|
1229
|
+
// src/utils/atomicWrite.ts
|
|
1230
|
+
import path7 from "path";
|
|
1231
|
+
import fs6 from "fs";
|
|
1232
|
+
async function atomicWriteFile(outputPath, content) {
|
|
1233
|
+
await fs6.promises.mkdir(path7.dirname(outputPath), { recursive: true });
|
|
1234
|
+
const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1235
|
+
await fs6.promises.writeFile(tmp, content, "utf-8");
|
|
1236
|
+
await fs6.promises.rename(tmp, outputPath);
|
|
1237
|
+
}
|
|
1238
|
+
var init_atomicWrite = __esm({
|
|
1239
|
+
"src/utils/atomicWrite.ts"() {
|
|
1240
|
+
"use strict";
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1088
1244
|
// src/ast/generateZodSchema.ts
|
|
1089
1245
|
function collectNamedTypes(type, ctx) {
|
|
1090
1246
|
switch (type.kind) {
|
|
@@ -1135,8 +1291,12 @@ function collectNamedTypes(type, ctx) {
|
|
|
1135
1291
|
if (resolved) {
|
|
1136
1292
|
ctx.namedTypes.set(type.name, resolved);
|
|
1137
1293
|
collectNamedTypes(resolved, ctx);
|
|
1294
|
+
return;
|
|
1138
1295
|
}
|
|
1139
|
-
|
|
1296
|
+
throw new SchemaExtractionError(
|
|
1297
|
+
type.name,
|
|
1298
|
+
`\u65E0\u6CD5\u89E3\u6790\u547D\u540D\u7C7B\u578B\u5F15\u7528 "${type.name}"\uFF08handler \u6587\u4EF6\u4E0E program \u6E90\u6587\u4EF6\u4E2D\u5747\u672A\u627E\u5230\u540C\u540D\u9876\u5C42\u58F0\u660E\uFF09`
|
|
1299
|
+
);
|
|
1140
1300
|
}
|
|
1141
1301
|
}
|
|
1142
1302
|
}
|
|
@@ -1146,6 +1306,12 @@ function runtimeTypeToZodExpression(type, ctx, constraints) {
|
|
|
1146
1306
|
if (ctx.coerce && (type.kind === "number" || type.kind === "boolean")) {
|
|
1147
1307
|
return wrapCoercePreprocess(type.kind, withConstraints);
|
|
1148
1308
|
}
|
|
1309
|
+
if (ctx.coerce && type.kind === "literal" && (typeof type.value === "number" || typeof type.value === "boolean")) {
|
|
1310
|
+
return wrapCoercePreprocess(
|
|
1311
|
+
typeof type.value === "number" ? "number" : "boolean",
|
|
1312
|
+
withConstraints
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1149
1315
|
return withConstraints;
|
|
1150
1316
|
}
|
|
1151
1317
|
function applyConstraints(baseExpr, constraints, typeKind) {
|
|
@@ -1210,7 +1376,7 @@ function baseExpression(type, ctx) {
|
|
|
1210
1376
|
case "tuple":
|
|
1211
1377
|
return generateTupleExpression(type.elements, ctx);
|
|
1212
1378
|
case "object":
|
|
1213
|
-
return generateObjectExpression(type
|
|
1379
|
+
return generateObjectExpression(type, ctx);
|
|
1214
1380
|
case "union":
|
|
1215
1381
|
return generateUnionExpression(type.members, ctx);
|
|
1216
1382
|
case "date":
|
|
@@ -1262,7 +1428,10 @@ function generateTupleExpression(elements, ctx) {
|
|
|
1262
1428
|
}
|
|
1263
1429
|
}
|
|
1264
1430
|
if (restExpression) {
|
|
1265
|
-
|
|
1431
|
+
const fixedWithOptional = fixedExprs.map(
|
|
1432
|
+
(expr, i) => fixedOptional[i] ? `${expr}.optional()` : expr
|
|
1433
|
+
);
|
|
1434
|
+
return `z.tuple([${fixedWithOptional.join(", ")}]).rest(${restExpression})`;
|
|
1266
1435
|
}
|
|
1267
1436
|
const hasOptional = fixedOptional.some((o) => o);
|
|
1268
1437
|
if (!hasOptional) {
|
|
@@ -1283,13 +1452,14 @@ function generateTupleExpression(elements, ctx) {
|
|
|
1283
1452
|
}
|
|
1284
1453
|
return `z.union([${variants.join(", ")}])`;
|
|
1285
1454
|
}
|
|
1286
|
-
function generateObjectExpression(
|
|
1287
|
-
const fields = properties.map((prop) => {
|
|
1455
|
+
function generateObjectExpression(type, ctx) {
|
|
1456
|
+
const fields = type.properties.map((prop) => {
|
|
1288
1457
|
const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);
|
|
1289
1458
|
const finalExpr = prop.optional ? `${expr}.optional()` : expr;
|
|
1290
1459
|
return `${JSON.stringify(prop.name)}: ${finalExpr}`;
|
|
1291
1460
|
});
|
|
1292
|
-
|
|
1461
|
+
const objectExpr = `z.object({ ${fields.join(", ")} })`;
|
|
1462
|
+
return type.catchall !== void 0 ? `${objectExpr}.catchall(${runtimeTypeToZodExpression(type.catchall, ctx)})` : objectExpr;
|
|
1293
1463
|
}
|
|
1294
1464
|
function generateUnionExpression(members, ctx) {
|
|
1295
1465
|
const hasNull = members.some((m) => m.kind === "null");
|
|
@@ -1335,6 +1505,23 @@ function containsRef(type, visited) {
|
|
|
1335
1505
|
}
|
|
1336
1506
|
}
|
|
1337
1507
|
function generateZodSchemaSource(typeInfo, resolveType, exportName, coerce = false) {
|
|
1508
|
+
const { namedTypeDeclarations, entryDeclaration } = generateZodSchemaSourceParts(
|
|
1509
|
+
typeInfo,
|
|
1510
|
+
resolveType,
|
|
1511
|
+
exportName,
|
|
1512
|
+
coerce
|
|
1513
|
+
);
|
|
1514
|
+
const lines = [];
|
|
1515
|
+
lines.push("import { z } from 'zod';");
|
|
1516
|
+
lines.push("");
|
|
1517
|
+
for (const { declaration } of namedTypeDeclarations) {
|
|
1518
|
+
lines.push(declaration);
|
|
1519
|
+
}
|
|
1520
|
+
if (namedTypeDeclarations.length > 0) lines.push("");
|
|
1521
|
+
lines.push(entryDeclaration);
|
|
1522
|
+
return lines.join("\n");
|
|
1523
|
+
}
|
|
1524
|
+
function generateZodSchemaSourceParts(typeInfo, resolveType, exportName, coerce = false) {
|
|
1338
1525
|
const ctx = new CodeGenContext(resolveType);
|
|
1339
1526
|
const name = exportName ?? typeInfo.name;
|
|
1340
1527
|
ctx.entryTypeName = typeInfo.name;
|
|
@@ -1342,26 +1529,20 @@ function generateZodSchemaSource(typeInfo, resolveType, exportName, coerce = fal
|
|
|
1342
1529
|
ctx.coerce = coerce;
|
|
1343
1530
|
collectNamedTypes(typeInfo.runtimeType, ctx);
|
|
1344
1531
|
ctx.namedTypes.delete(typeInfo.name);
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
lines.push(generateNamedTypeDeclaration(n, type, ctx));
|
|
1350
|
-
}
|
|
1351
|
-
if (ctx.namedTypes.size > 0) lines.push("");
|
|
1532
|
+
const namedTypeDeclarations = [...ctx.namedTypes].map(([n, type]) => ({
|
|
1533
|
+
name: n,
|
|
1534
|
+
declaration: generateNamedTypeDeclaration(n, type, ctx)
|
|
1535
|
+
}));
|
|
1352
1536
|
const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);
|
|
1353
1537
|
const hasSelfRef = containsRef(typeInfo.runtimeType, /* @__PURE__ */ new Set([typeInfo.name]));
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
} else {
|
|
1357
|
-
lines.push(`export const ${name}Schema = ${entryExpr};`);
|
|
1358
|
-
}
|
|
1359
|
-
return lines.join("\n");
|
|
1538
|
+
const entryDeclaration = hasSelfRef ? `export const ${name}Schema = z.lazy(() => ${entryExpr});` : `export const ${name}Schema = ${entryExpr};`;
|
|
1539
|
+
return { namedTypeDeclarations, entryDeclaration };
|
|
1360
1540
|
}
|
|
1361
1541
|
var CodeGenContext, COERCE_NUMBER_HELPER, COERCE_BOOLEAN_HELPER, COERCE_MAP_HELPER, COERCE_SET_HELPER, HELPERS_FILENAME;
|
|
1362
1542
|
var init_generateZodSchema = __esm({
|
|
1363
1543
|
"src/ast/generateZodSchema.ts"() {
|
|
1364
1544
|
"use strict";
|
|
1545
|
+
init_resolveTypeNode();
|
|
1365
1546
|
CodeGenContext = class {
|
|
1366
1547
|
/** 命名类型集合:name → RuntimeType */
|
|
1367
1548
|
namedTypes = /* @__PURE__ */ new Map();
|
|
@@ -1383,7 +1564,7 @@ var init_generateZodSchema = __esm({
|
|
|
1383
1564
|
}
|
|
1384
1565
|
};
|
|
1385
1566
|
COERCE_NUMBER_HELPER = 'export const coerceNumber = (v) => typeof v === "string" && v.trim() !== "" && !isNaN(Number(v)) ? Number(v) : v;';
|
|
1386
|
-
COERCE_BOOLEAN_HELPER = 'export const coerceBoolean = (v) => v === "true" ||
|
|
1567
|
+
COERCE_BOOLEAN_HELPER = 'export const coerceBoolean = (v) => {\n const lower = typeof v === "string" ? v.toLowerCase() : v;\n return lower === "true" || lower === "1" ? true : lower === "false" || lower === "0" ? false : v;\n};';
|
|
1387
1568
|
COERCE_MAP_HELPER = 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === "object" ? new Map(Object.entries(v)) : v);';
|
|
1388
1569
|
COERCE_SET_HELPER = "export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);";
|
|
1389
1570
|
HELPERS_FILENAME = "faapi-helpers.js";
|
|
@@ -1399,8 +1580,7 @@ __export(generateSchemaFiles_exports, {
|
|
|
1399
1580
|
getRuntimeSchemaPath: () => getRuntimeSchemaPath,
|
|
1400
1581
|
getSchemaOutputPath: () => getSchemaOutputPath
|
|
1401
1582
|
});
|
|
1402
|
-
import
|
|
1403
|
-
import fs5 from "fs/promises";
|
|
1583
|
+
import path8 from "path";
|
|
1404
1584
|
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1405
1585
|
let rel = sourceFile.replace(/\\/g, "/");
|
|
1406
1586
|
if (rel.startsWith("src/")) {
|
|
@@ -1408,7 +1588,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
1408
1588
|
}
|
|
1409
1589
|
const idx = rel.lastIndexOf("/");
|
|
1410
1590
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1411
|
-
return
|
|
1591
|
+
return path8.resolve(rootDir, dist, relDir, "zod.js");
|
|
1412
1592
|
}
|
|
1413
1593
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
1414
1594
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -1419,16 +1599,17 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
1419
1599
|
}
|
|
1420
1600
|
const idx = rel.lastIndexOf("/");
|
|
1421
1601
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1422
|
-
return
|
|
1602
|
+
return path8.resolve(rootDir, dist, relDir, "zod.js");
|
|
1423
1603
|
}
|
|
1424
1604
|
function getHelpersImportPath(relDir) {
|
|
1425
1605
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
1426
1606
|
const depth = relDir.split("/").filter(Boolean).length;
|
|
1427
1607
|
return `${"../".repeat(depth)}${HELPERS_FILENAME}`;
|
|
1428
1608
|
}
|
|
1429
|
-
function generateSchemaFileSource(sources,
|
|
1430
|
-
const resolveType = (name) => allTypes.get(name)?.runtimeType;
|
|
1609
|
+
function generateSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
1431
1610
|
const lines = ["import { z } from 'zod';"];
|
|
1611
|
+
const namedTypeDeclarations = [];
|
|
1612
|
+
const seenNamedTypes = /* @__PURE__ */ new Set();
|
|
1432
1613
|
const schemaBlocks = [];
|
|
1433
1614
|
for (const source of sources) {
|
|
1434
1615
|
const { schemaName, typeInfo } = source;
|
|
@@ -1436,16 +1617,24 @@ function generateSchemaFileSource(sources, allTypes, helpersImportPath) {
|
|
|
1436
1617
|
continue;
|
|
1437
1618
|
}
|
|
1438
1619
|
const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1620
|
+
const { namedTypeDeclarations: decls, entryDeclaration } = generateZodSchemaSourceParts(
|
|
1621
|
+
typeInfo,
|
|
1622
|
+
resolveType,
|
|
1623
|
+
schemaName,
|
|
1624
|
+
coerce
|
|
1443
1625
|
);
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1626
|
+
for (const { name, declaration } of decls) {
|
|
1627
|
+
if (seenNamedTypes.has(name)) continue;
|
|
1628
|
+
seenNamedTypes.add(name);
|
|
1629
|
+
namedTypeDeclarations.push(declaration);
|
|
1630
|
+
}
|
|
1631
|
+
schemaBlocks.push([`// ${schemaName}`, entryDeclaration, ""].join("\n"));
|
|
1447
1632
|
}
|
|
1448
|
-
|
|
1633
|
+
if (namedTypeDeclarations.length > 0) {
|
|
1634
|
+
lines.push(...namedTypeDeclarations);
|
|
1635
|
+
lines.push("");
|
|
1636
|
+
}
|
|
1637
|
+
const allSchemaCode = [...namedTypeDeclarations, ...schemaBlocks].join("\n");
|
|
1449
1638
|
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
1450
1639
|
lines.push(
|
|
1451
1640
|
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
@@ -1457,7 +1646,7 @@ function generateSchemaFileSource(sources, allTypes, helpersImportPath) {
|
|
|
1457
1646
|
}
|
|
1458
1647
|
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
1459
1648
|
if (routes.length === 0) return;
|
|
1460
|
-
const { sources,
|
|
1649
|
+
const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);
|
|
1461
1650
|
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
1462
1651
|
for (const source of sources) {
|
|
1463
1652
|
let list = sourcesByFile.get(source.filePath);
|
|
@@ -1469,9 +1658,8 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1469
1658
|
}
|
|
1470
1659
|
const fileEntries = [];
|
|
1471
1660
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1472
|
-
const relFile =
|
|
1661
|
+
const relFile = path8.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1473
1662
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1474
|
-
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
1475
1663
|
let relForDir = relFile;
|
|
1476
1664
|
if (relForDir.startsWith("src/")) {
|
|
1477
1665
|
relForDir = relForDir.slice(4);
|
|
@@ -1479,12 +1667,17 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1479
1667
|
const dirIdx = relForDir.lastIndexOf("/");
|
|
1480
1668
|
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
1481
1669
|
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
1482
|
-
const
|
|
1670
|
+
const resolver = resolversByFile.get(filePath);
|
|
1671
|
+
const source = generateSchemaFileSource(
|
|
1672
|
+
fileSources,
|
|
1673
|
+
(name) => resolver?.resolve(name)?.runtimeType,
|
|
1674
|
+
helpersImportPath
|
|
1675
|
+
);
|
|
1483
1676
|
fileEntries.push({ outputPath, source });
|
|
1484
1677
|
}
|
|
1485
1678
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1486
1679
|
if (usesCoerceHelpers(allSourceCode)) {
|
|
1487
|
-
const helpersPath =
|
|
1680
|
+
const helpersPath = path8.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1488
1681
|
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
1489
1682
|
}
|
|
1490
1683
|
await Promise.all(
|
|
@@ -1492,12 +1685,12 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1492
1685
|
);
|
|
1493
1686
|
}
|
|
1494
1687
|
async function writeSchemaFile(outputPath, source) {
|
|
1495
|
-
await
|
|
1496
|
-
await fs5.writeFile(outputPath, source, "utf-8");
|
|
1688
|
+
await atomicWriteFile(outputPath, source);
|
|
1497
1689
|
}
|
|
1498
1690
|
var init_generateSchemaFiles = __esm({
|
|
1499
1691
|
"src/cli/generateSchemaFiles.ts"() {
|
|
1500
1692
|
"use strict";
|
|
1693
|
+
init_atomicWrite();
|
|
1501
1694
|
init_collectRouteSchemaSources();
|
|
1502
1695
|
init_generateZodSchema();
|
|
1503
1696
|
}
|
|
@@ -1510,95 +1703,178 @@ init_resolveTypeNode();
|
|
|
1510
1703
|
init_inputType();
|
|
1511
1704
|
init_collectRouteSchemaSources();
|
|
1512
1705
|
|
|
1513
|
-
// src/injection/
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1706
|
+
// src/injection/registries.ts
|
|
1707
|
+
function createToolRegistry() {
|
|
1708
|
+
let registry = /* @__PURE__ */ new Map();
|
|
1709
|
+
return {
|
|
1710
|
+
hydrate(tools) {
|
|
1711
|
+
const next = /* @__PURE__ */ new Map();
|
|
1712
|
+
for (const tool of tools) {
|
|
1713
|
+
next.set(tool.name, tool);
|
|
1714
|
+
}
|
|
1715
|
+
registry = next;
|
|
1716
|
+
},
|
|
1717
|
+
get(name) {
|
|
1718
|
+
return registry.get(name);
|
|
1719
|
+
},
|
|
1720
|
+
list() {
|
|
1721
|
+
return Array.from(registry.values());
|
|
1722
|
+
},
|
|
1723
|
+
clear() {
|
|
1724
|
+
registry = /* @__PURE__ */ new Map();
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1521
1727
|
}
|
|
1522
|
-
function
|
|
1523
|
-
registry = /* @__PURE__ */ new Map();
|
|
1728
|
+
function createAgentRegistry(tool) {
|
|
1729
|
+
let registry = /* @__PURE__ */ new Map();
|
|
1730
|
+
const getAgent2 = (name) => registry.get(name);
|
|
1731
|
+
return {
|
|
1732
|
+
hydrate(agents) {
|
|
1733
|
+
const next = /* @__PURE__ */ new Map();
|
|
1734
|
+
for (const agent of agents) {
|
|
1735
|
+
next.set(agent.name, agent);
|
|
1736
|
+
}
|
|
1737
|
+
registry = next;
|
|
1738
|
+
},
|
|
1739
|
+
getAgent: getAgent2,
|
|
1740
|
+
getAgentEntry(name) {
|
|
1741
|
+
return registry.get(name);
|
|
1742
|
+
},
|
|
1743
|
+
listAgents() {
|
|
1744
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1745
|
+
for (const agent of registry.values()) merged.set(agent.name, agent);
|
|
1746
|
+
return Array.from(merged.values());
|
|
1747
|
+
},
|
|
1748
|
+
asTool(name) {
|
|
1749
|
+
const agent = getAgent2(name);
|
|
1750
|
+
if (!agent) return void 0;
|
|
1751
|
+
return {
|
|
1752
|
+
kind: "agent",
|
|
1753
|
+
name: `agent.${agent.name}`,
|
|
1754
|
+
agentName: agent.name,
|
|
1755
|
+
description: agent.description,
|
|
1756
|
+
metadata: agent
|
|
1757
|
+
};
|
|
1758
|
+
},
|
|
1759
|
+
resolveAgentTools(name) {
|
|
1760
|
+
const agent = getAgent2(name);
|
|
1761
|
+
if (!agent) return [];
|
|
1762
|
+
const result = /* @__PURE__ */ new Map();
|
|
1763
|
+
if (agent.tools) {
|
|
1764
|
+
for (const toolName of agent.tools) {
|
|
1765
|
+
const resolved = tool.get(toolName);
|
|
1766
|
+
if (resolved) result.set(resolved.name, resolved);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return Array.from(result.values());
|
|
1770
|
+
},
|
|
1771
|
+
resolveSubAgents(name) {
|
|
1772
|
+
const agent = getAgent2(name);
|
|
1773
|
+
if (!agent || !agent.agents) return [];
|
|
1774
|
+
const result = [];
|
|
1775
|
+
for (const subName of agent.agents) {
|
|
1776
|
+
const sub = getAgent2(subName);
|
|
1777
|
+
if (sub) result.push(sub);
|
|
1778
|
+
}
|
|
1779
|
+
return result;
|
|
1780
|
+
},
|
|
1781
|
+
clear() {
|
|
1782
|
+
registry = /* @__PURE__ */ new Map();
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1524
1785
|
}
|
|
1525
|
-
function
|
|
1526
|
-
|
|
1786
|
+
function createSkillRegistry() {
|
|
1787
|
+
let registry = /* @__PURE__ */ new Map();
|
|
1788
|
+
return {
|
|
1789
|
+
hydrate(skills) {
|
|
1790
|
+
const next = /* @__PURE__ */ new Map();
|
|
1791
|
+
for (const skill of skills) {
|
|
1792
|
+
next.set(skill.name, skill);
|
|
1793
|
+
}
|
|
1794
|
+
registry = next;
|
|
1795
|
+
},
|
|
1796
|
+
upsert(skill) {
|
|
1797
|
+
registry.set(skill.name, skill);
|
|
1798
|
+
},
|
|
1799
|
+
remove(name) {
|
|
1800
|
+
registry.delete(name);
|
|
1801
|
+
},
|
|
1802
|
+
get(name) {
|
|
1803
|
+
return registry.get(name);
|
|
1804
|
+
},
|
|
1805
|
+
list() {
|
|
1806
|
+
return Array.from(registry.values());
|
|
1807
|
+
},
|
|
1808
|
+
clear() {
|
|
1809
|
+
registry = /* @__PURE__ */ new Map();
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1527
1812
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1813
|
+
function createAgentHandleStore() {
|
|
1814
|
+
let currentFactory = null;
|
|
1815
|
+
return {
|
|
1816
|
+
register(factory) {
|
|
1817
|
+
currentFactory = factory;
|
|
1818
|
+
},
|
|
1819
|
+
get(ctx) {
|
|
1820
|
+
if (currentFactory === null) return void 0;
|
|
1821
|
+
return currentFactory(ctx);
|
|
1822
|
+
},
|
|
1823
|
+
clear() {
|
|
1824
|
+
currentFactory = null;
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1537
1827
|
}
|
|
1538
|
-
function
|
|
1539
|
-
|
|
1828
|
+
function createAppRegistries() {
|
|
1829
|
+
const tool = createToolRegistry();
|
|
1830
|
+
const agent = createAgentRegistry(tool);
|
|
1831
|
+
const skill = createSkillRegistry();
|
|
1832
|
+
const agentHandle = createAgentHandleStore();
|
|
1833
|
+
return { tool, agent, skill, agentHandle };
|
|
1540
1834
|
}
|
|
1835
|
+
var defaultRegistries = createAppRegistries();
|
|
1836
|
+
|
|
1837
|
+
// src/injection/agentRegistry.ts
|
|
1541
1838
|
function getAgent(name) {
|
|
1542
|
-
return
|
|
1839
|
+
return defaultRegistries.agent.getAgent(name);
|
|
1543
1840
|
}
|
|
1544
1841
|
function getAgentEntry(name) {
|
|
1545
|
-
return
|
|
1842
|
+
return defaultRegistries.agent.getAgentEntry(name);
|
|
1546
1843
|
}
|
|
1547
1844
|
function listAgents() {
|
|
1548
|
-
|
|
1549
|
-
for (const agent of registry2.values()) merged.set(agent.name, agent);
|
|
1550
|
-
return Array.from(merged.values());
|
|
1845
|
+
return defaultRegistries.agent.listAgents();
|
|
1551
1846
|
}
|
|
1552
1847
|
function resolveAgentTools(name) {
|
|
1553
|
-
|
|
1554
|
-
if (!agent) return [];
|
|
1555
|
-
const result = /* @__PURE__ */ new Map();
|
|
1556
|
-
if (agent.tools) {
|
|
1557
|
-
for (const toolName of agent.tools) {
|
|
1558
|
-
const tool = getTool(toolName);
|
|
1559
|
-
if (tool) result.set(tool.name, tool);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
return Array.from(result.values());
|
|
1848
|
+
return defaultRegistries.agent.resolveAgentTools(name);
|
|
1563
1849
|
}
|
|
1564
1850
|
function resolveSubAgents(name) {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
}
|
|
1572
|
-
return result;
|
|
1851
|
+
return defaultRegistries.agent.resolveSubAgents(name);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/injection/toolRegistry.ts
|
|
1855
|
+
function getTool(name) {
|
|
1856
|
+
return defaultRegistries.tool.get(name);
|
|
1573
1857
|
}
|
|
1574
1858
|
|
|
1575
1859
|
// src/injection/skillRegistry.ts
|
|
1576
|
-
var registry3 = /* @__PURE__ */ new Map();
|
|
1577
1860
|
function hydrateSkillRegistry(skills) {
|
|
1578
|
-
|
|
1579
|
-
for (const skill of skills) {
|
|
1580
|
-
next.set(skill.name, skill);
|
|
1581
|
-
}
|
|
1582
|
-
registry3 = next;
|
|
1583
|
-
}
|
|
1584
|
-
function clearSkillRegistry() {
|
|
1585
|
-
registry3 = /* @__PURE__ */ new Map();
|
|
1861
|
+
defaultRegistries.skill.hydrate(skills);
|
|
1586
1862
|
}
|
|
1587
1863
|
function upsertSkill(core) {
|
|
1588
|
-
|
|
1864
|
+
defaultRegistries.skill.upsert(core);
|
|
1589
1865
|
}
|
|
1590
1866
|
function removeSkill(name) {
|
|
1591
|
-
|
|
1867
|
+
defaultRegistries.skill.remove(name);
|
|
1592
1868
|
}
|
|
1593
1869
|
function getSkill(name) {
|
|
1594
|
-
return
|
|
1870
|
+
return defaultRegistries.skill.get(name);
|
|
1595
1871
|
}
|
|
1596
1872
|
function listSkills() {
|
|
1597
|
-
return
|
|
1873
|
+
return defaultRegistries.skill.list();
|
|
1598
1874
|
}
|
|
1599
1875
|
|
|
1600
1876
|
// src/loader/loadAgentModule.ts
|
|
1601
|
-
import
|
|
1877
|
+
import fs9 from "fs";
|
|
1602
1878
|
|
|
1603
1879
|
// src/loader/resolveExports.ts
|
|
1604
1880
|
function resolveExport(module, exportName) {
|
|
@@ -1618,8 +1894,8 @@ function resolveExport(module, exportName) {
|
|
|
1618
1894
|
// src/utils/importWithCacheBust.ts
|
|
1619
1895
|
import { pathToFileURL } from "url";
|
|
1620
1896
|
var loadTs;
|
|
1621
|
-
function setLoadTimestamp(
|
|
1622
|
-
loadTs =
|
|
1897
|
+
function setLoadTimestamp(ts10) {
|
|
1898
|
+
loadTs = ts10;
|
|
1623
1899
|
}
|
|
1624
1900
|
function getVitestImportActual() {
|
|
1625
1901
|
const vi = globalThis.vi;
|
|
@@ -1644,17 +1920,17 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
|
1644
1920
|
}
|
|
1645
1921
|
|
|
1646
1922
|
// src/cli/compileOnDemand.ts
|
|
1647
|
-
import
|
|
1648
|
-
import
|
|
1923
|
+
import path10 from "path";
|
|
1924
|
+
import fs8 from "fs";
|
|
1649
1925
|
|
|
1650
|
-
// src/cli/
|
|
1651
|
-
import
|
|
1652
|
-
import
|
|
1926
|
+
// src/cli/compileSourceFiles.ts
|
|
1927
|
+
import path6 from "path";
|
|
1928
|
+
import fs5 from "fs";
|
|
1653
1929
|
import fg from "fast-glob";
|
|
1654
1930
|
|
|
1655
1931
|
// src/cli/aliasPlugin.ts
|
|
1656
|
-
import
|
|
1657
|
-
import
|
|
1932
|
+
import path5 from "path";
|
|
1933
|
+
import fs4 from "fs";
|
|
1658
1934
|
|
|
1659
1935
|
// src/utils/resolveAlias.ts
|
|
1660
1936
|
function resolveAlias(specifier, config) {
|
|
@@ -1679,57 +1955,84 @@ function resolveAlias(specifier, config) {
|
|
|
1679
1955
|
return candidates;
|
|
1680
1956
|
}
|
|
1681
1957
|
|
|
1682
|
-
// src/utils/
|
|
1683
|
-
import ts6 from "typescript";
|
|
1958
|
+
// src/utils/prodPaths.ts
|
|
1684
1959
|
import path3 from "path";
|
|
1685
1960
|
import fs2 from "fs";
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
if (
|
|
1691
|
-
|
|
1692
|
-
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
1693
|
-
const rawPaths = parsed.options.paths;
|
|
1694
|
-
if (!rawPaths) return null;
|
|
1695
|
-
const paths = {};
|
|
1696
|
-
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
1697
|
-
paths[pattern] = targets.map((t) => path3.resolve(baseUrl, t));
|
|
1961
|
+
var APP_DIR = "src";
|
|
1962
|
+
var ROUTE_PATTERNS = ["src/api/**/*.ts"];
|
|
1963
|
+
function toProdFilePath(filePath, dist) {
|
|
1964
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
1965
|
+
if (rel.startsWith("src/")) {
|
|
1966
|
+
rel = rel.slice(4);
|
|
1698
1967
|
}
|
|
1699
|
-
|
|
1968
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
1969
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
1700
1970
|
}
|
|
1701
|
-
|
|
1702
|
-
// src/cli/aliasPlugin.ts
|
|
1703
1971
|
function toProdExtension(filePath) {
|
|
1704
1972
|
if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
|
|
1705
1973
|
if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
|
|
1706
1974
|
if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
|
|
1707
1975
|
return filePath;
|
|
1708
1976
|
}
|
|
1709
|
-
function toProdImportPath(sourceFile, importer) {
|
|
1710
|
-
const importerDir = path4.dirname(importer);
|
|
1711
|
-
let rel = path4.relative(importerDir, sourceFile);
|
|
1712
|
-
rel = rel.split(path4.sep).join("/");
|
|
1713
|
-
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1714
|
-
return toProdExtension(rel);
|
|
1715
|
-
}
|
|
1716
1977
|
function toRealPath(p) {
|
|
1717
1978
|
try {
|
|
1718
|
-
return
|
|
1979
|
+
return fs2.realpathSync(p);
|
|
1719
1980
|
} catch {
|
|
1720
1981
|
return p;
|
|
1721
1982
|
}
|
|
1722
1983
|
}
|
|
1723
1984
|
function isInsideDir(filePath, dir) {
|
|
1724
|
-
const rel =
|
|
1725
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
1985
|
+
const rel = path3.relative(dir, filePath);
|
|
1986
|
+
return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// src/utils/readTsconfig.ts
|
|
1990
|
+
import ts6 from "typescript";
|
|
1991
|
+
import path4 from "path";
|
|
1992
|
+
import fs3 from "fs";
|
|
1993
|
+
var tsconfigCache = /* @__PURE__ */ new Map();
|
|
1994
|
+
function readTsconfig(rootDir) {
|
|
1995
|
+
const tsconfigPath = path4.resolve(rootDir, "tsconfig.json");
|
|
1996
|
+
if (!fs3.existsSync(tsconfigPath)) return null;
|
|
1997
|
+
let mtimeMs;
|
|
1998
|
+
try {
|
|
1999
|
+
mtimeMs = fs3.statSync(tsconfigPath).mtimeMs;
|
|
2000
|
+
} catch {
|
|
2001
|
+
return null;
|
|
2002
|
+
}
|
|
2003
|
+
const cached = tsconfigCache.get(tsconfigPath);
|
|
2004
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
2005
|
+
return cached.config;
|
|
2006
|
+
}
|
|
2007
|
+
const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
|
|
2008
|
+
if (configFile.error || !configFile.config) return null;
|
|
2009
|
+
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
2010
|
+
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
2011
|
+
const rawPaths = parsed.options.paths;
|
|
2012
|
+
const config = rawPaths ? (() => {
|
|
2013
|
+
const paths = {};
|
|
2014
|
+
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
2015
|
+
paths[pattern] = targets.map((t) => path4.resolve(baseUrl, t));
|
|
2016
|
+
}
|
|
2017
|
+
return { baseUrl, paths };
|
|
2018
|
+
})() : null;
|
|
2019
|
+
tsconfigCache.set(tsconfigPath, { mtimeMs, config });
|
|
2020
|
+
return config;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// src/cli/aliasPlugin.ts
|
|
2024
|
+
function toProdImportPath(sourceFile, importer) {
|
|
2025
|
+
const importerDir = path5.dirname(importer);
|
|
2026
|
+
let rel = path5.relative(importerDir, sourceFile);
|
|
2027
|
+
rel = rel.split(path5.sep).join("/");
|
|
2028
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2029
|
+
return toProdExtension(rel);
|
|
1726
2030
|
}
|
|
1727
|
-
var APP_DIR = "src";
|
|
1728
2031
|
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
1729
|
-
const appDirAbs = toRealPath(
|
|
2032
|
+
const appDirAbs = toRealPath(path5.resolve(rootDir, APP_DIR));
|
|
1730
2033
|
const sourceReal = toRealPath(sourceFile);
|
|
1731
|
-
let rel =
|
|
1732
|
-
rel = rel.split(
|
|
2034
|
+
let rel = path5.relative(appDirAbs, sourceReal);
|
|
2035
|
+
rel = rel.split(path5.sep).join("/");
|
|
1733
2036
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1734
2037
|
return toProdExtension(rel);
|
|
1735
2038
|
}
|
|
@@ -1744,41 +2047,41 @@ var INDEX_EXTS = [
|
|
|
1744
2047
|
"/index.cjs"
|
|
1745
2048
|
];
|
|
1746
2049
|
function resolveRelativeSpecifier(importer, specifier) {
|
|
1747
|
-
const importerDir =
|
|
1748
|
-
const base =
|
|
2050
|
+
const importerDir = path5.dirname(importer);
|
|
2051
|
+
const base = path5.resolve(importerDir, specifier);
|
|
1749
2052
|
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1750
|
-
return
|
|
2053
|
+
return fs4.existsSync(base) ? base : null;
|
|
1751
2054
|
}
|
|
1752
2055
|
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
1753
|
-
return
|
|
2056
|
+
return fs4.existsSync(base) ? base : null;
|
|
1754
2057
|
}
|
|
1755
2058
|
for (const ext of SOURCE_EXTS) {
|
|
1756
2059
|
const file = base + ext;
|
|
1757
|
-
if (
|
|
2060
|
+
if (fs4.existsSync(file)) return file;
|
|
1758
2061
|
}
|
|
1759
2062
|
for (const indexExt of INDEX_EXTS) {
|
|
1760
2063
|
const file = base + indexExt;
|
|
1761
|
-
if (
|
|
2064
|
+
if (fs4.existsSync(file)) return file;
|
|
1762
2065
|
}
|
|
1763
2066
|
return null;
|
|
1764
2067
|
}
|
|
1765
2068
|
function createAliasPlugin(config, options) {
|
|
1766
|
-
const
|
|
1767
|
-
const appDirAbs = options?.rootDir ? toRealPath(
|
|
2069
|
+
const SPEC_RE2 = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
2070
|
+
const appDirAbs = options?.rootDir ? toRealPath(path5.resolve(options.rootDir, APP_DIR)) : null;
|
|
1768
2071
|
return {
|
|
1769
2072
|
name: "faapi-alias",
|
|
1770
2073
|
setup(build) {
|
|
1771
2074
|
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
1772
2075
|
let source;
|
|
1773
2076
|
try {
|
|
1774
|
-
source =
|
|
2077
|
+
source = fs4.readFileSync(args.path, "utf8");
|
|
1775
2078
|
} catch {
|
|
1776
2079
|
return void 0;
|
|
1777
2080
|
}
|
|
1778
2081
|
const importer = args.path;
|
|
1779
2082
|
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
1780
2083
|
let modified = false;
|
|
1781
|
-
const newSource = source.replace(
|
|
2084
|
+
const newSource = source.replace(SPEC_RE2, (full, prefix, quote, specifier) => {
|
|
1782
2085
|
if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
|
|
1783
2086
|
return full;
|
|
1784
2087
|
}
|
|
@@ -1804,7 +2107,7 @@ function createAliasPlugin(config, options) {
|
|
|
1804
2107
|
for (const candidate of candidates) {
|
|
1805
2108
|
for (const ext of SOURCE_EXTS) {
|
|
1806
2109
|
const file = candidate + ext;
|
|
1807
|
-
if (
|
|
2110
|
+
if (fs4.existsSync(file)) {
|
|
1808
2111
|
modified = true;
|
|
1809
2112
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
1810
2113
|
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
@@ -1817,7 +2120,7 @@ function createAliasPlugin(config, options) {
|
|
|
1817
2120
|
}
|
|
1818
2121
|
for (const indexExt of INDEX_EXTS) {
|
|
1819
2122
|
const file = candidate + indexExt;
|
|
1820
|
-
if (
|
|
2123
|
+
if (fs4.existsSync(file)) {
|
|
1821
2124
|
modified = true;
|
|
1822
2125
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
1823
2126
|
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
@@ -1842,11 +2145,10 @@ function buildAliasPlugins(rootDir) {
|
|
|
1842
2145
|
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
1843
2146
|
}
|
|
1844
2147
|
|
|
1845
|
-
// src/cli/
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
const
|
|
1849
|
-
const entryPoints = files ?? await fg([`${APP_DIR2}/**/*.ts`], {
|
|
2148
|
+
// src/cli/compileSourceFiles.ts
|
|
2149
|
+
async function compileSourceFiles(options) {
|
|
2150
|
+
const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
|
|
2151
|
+
const entryPoints = files ?? await fg([`${APP_DIR}/**/*.ts`], {
|
|
1850
2152
|
cwd: rootDir,
|
|
1851
2153
|
onlyFiles: true,
|
|
1852
2154
|
absolute: true,
|
|
@@ -1855,11 +2157,11 @@ async function compileDevRoutes(options) {
|
|
|
1855
2157
|
if (entryPoints.length === 0) {
|
|
1856
2158
|
return { compiledFiles: [] };
|
|
1857
2159
|
}
|
|
1858
|
-
const absDist =
|
|
1859
|
-
await
|
|
2160
|
+
const absDist = path6.resolve(rootDir, dist);
|
|
2161
|
+
await fs5.promises.mkdir(absDist, { recursive: true });
|
|
1860
2162
|
const plugins = buildAliasPlugins(rootDir);
|
|
1861
2163
|
const esbuild = await import("esbuild");
|
|
1862
|
-
const outbase =
|
|
2164
|
+
const outbase = path6.resolve(rootDir, APP_DIR);
|
|
1863
2165
|
const result = await esbuild.build({
|
|
1864
2166
|
entryPoints,
|
|
1865
2167
|
outdir: absDist,
|
|
@@ -1870,29 +2172,106 @@ async function compileDevRoutes(options) {
|
|
|
1870
2172
|
sourcemap: true,
|
|
1871
2173
|
packages: "external",
|
|
1872
2174
|
plugins,
|
|
2175
|
+
// build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)
|
|
2176
|
+
...production ? { define: { "process.env.NODE_ENV": '"production"' }, minifySyntax: true } : {},
|
|
1873
2177
|
logLevel,
|
|
1874
|
-
|
|
2178
|
+
// dev 语义:esbuild 返回内存内容,由下方原子写落盘
|
|
2179
|
+
...atomicWrite ? { write: false } : {}
|
|
1875
2180
|
});
|
|
1876
|
-
if (result.outputFiles) {
|
|
2181
|
+
if (atomicWrite && result.outputFiles) {
|
|
1877
2182
|
await Promise.all(
|
|
1878
2183
|
result.outputFiles.map(async (file) => {
|
|
1879
|
-
await
|
|
2184
|
+
await fs5.promises.mkdir(path6.dirname(file.path), { recursive: true });
|
|
1880
2185
|
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1881
|
-
await
|
|
1882
|
-
await
|
|
2186
|
+
await fs5.promises.writeFile(tmp, file.contents);
|
|
2187
|
+
await fs5.promises.rename(tmp, file.path);
|
|
1883
2188
|
})
|
|
1884
2189
|
);
|
|
1885
2190
|
}
|
|
1886
2191
|
return { compiledFiles: entryPoints };
|
|
1887
2192
|
}
|
|
1888
2193
|
|
|
2194
|
+
// src/cli/compileDevRoutes.ts
|
|
2195
|
+
async function compileDevRoutes(options) {
|
|
2196
|
+
return compileSourceFiles({ ...options, atomicWrite: true });
|
|
2197
|
+
}
|
|
2198
|
+
|
|
1889
2199
|
// src/cli/compileOnDemand.ts
|
|
1890
2200
|
init_generateSchemaFiles();
|
|
1891
2201
|
init_generateSchemaFiles();
|
|
2202
|
+
|
|
2203
|
+
// src/cli/collectImports.ts
|
|
2204
|
+
import path9 from "path";
|
|
2205
|
+
import fs7 from "fs";
|
|
2206
|
+
var SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
2207
|
+
function extractImportSpecifiers(source) {
|
|
2208
|
+
const specifiers = [];
|
|
2209
|
+
let match;
|
|
2210
|
+
SPEC_RE.lastIndex = 0;
|
|
2211
|
+
while ((match = SPEC_RE.exec(source)) !== null) {
|
|
2212
|
+
specifiers.push(match[3]);
|
|
2213
|
+
}
|
|
2214
|
+
return specifiers;
|
|
2215
|
+
}
|
|
2216
|
+
function resolveSpecifierFromDir(dir, specifier) {
|
|
2217
|
+
return resolveRelativeSpecifier(path9.join(dir, "__faapi_probe__.ts"), specifier);
|
|
2218
|
+
}
|
|
2219
|
+
async function collectRelativeImports(entryFiles, rootDir) {
|
|
2220
|
+
const appDirAbs = toRealPath(path9.resolve(rootDir, "src"));
|
|
2221
|
+
const tsconfig = readTsconfig(rootDir);
|
|
2222
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2223
|
+
const insideFiles = /* @__PURE__ */ new Set();
|
|
2224
|
+
const outsideFiles = /* @__PURE__ */ new Set();
|
|
2225
|
+
async function collect(filePath) {
|
|
2226
|
+
if (visited.has(filePath)) return;
|
|
2227
|
+
visited.add(filePath);
|
|
2228
|
+
let source;
|
|
2229
|
+
try {
|
|
2230
|
+
source = await fs7.promises.readFile(filePath, "utf8");
|
|
2231
|
+
} catch {
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
for (const specifier of extractImportSpecifiers(source)) {
|
|
2235
|
+
if (/\.(js|mjs|cjs)$/.test(specifier)) continue;
|
|
2236
|
+
let resolved;
|
|
2237
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2238
|
+
resolved = resolveRelativeSpecifier(filePath, specifier);
|
|
2239
|
+
} else if (tsconfig) {
|
|
2240
|
+
resolved = null;
|
|
2241
|
+
for (const candidate of resolveAlias(specifier, tsconfig)) {
|
|
2242
|
+
const probed = resolveSpecifierFromDir(rootDir, candidate);
|
|
2243
|
+
if (probed) {
|
|
2244
|
+
resolved = probed;
|
|
2245
|
+
break;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
} else {
|
|
2249
|
+
resolved = null;
|
|
2250
|
+
}
|
|
2251
|
+
if (!resolved) continue;
|
|
2252
|
+
if (!isInsideDir(toRealPath(resolved), toRealPath(path9.resolve(rootDir)))) continue;
|
|
2253
|
+
if (isInsideDir(toRealPath(resolved), appDirAbs)) {
|
|
2254
|
+
insideFiles.add(resolved);
|
|
2255
|
+
} else {
|
|
2256
|
+
outsideFiles.add(resolved);
|
|
2257
|
+
}
|
|
2258
|
+
await collect(resolved);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
for (const entry of entryFiles) {
|
|
2262
|
+
await collect(entry);
|
|
2263
|
+
}
|
|
2264
|
+
return {
|
|
2265
|
+
insideFiles: Array.from(insideFiles),
|
|
2266
|
+
outsideFiles: Array.from(outsideFiles)
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
// src/cli/compileOnDemand.ts
|
|
1892
2271
|
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
1893
2272
|
try {
|
|
1894
|
-
const srcStat =
|
|
1895
|
-
const prodStat =
|
|
2273
|
+
const srcStat = fs8.statSync(sourceAbsPath);
|
|
2274
|
+
const prodStat = fs8.statSync(productAbsPath);
|
|
1896
2275
|
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
1897
2276
|
} catch {
|
|
1898
2277
|
return false;
|
|
@@ -1915,16 +2294,16 @@ function clearCompiledFiles() {
|
|
|
1915
2294
|
sourcePathCache.clear();
|
|
1916
2295
|
}
|
|
1917
2296
|
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
1918
|
-
const
|
|
1919
|
-
if (
|
|
1920
|
-
await
|
|
2297
|
+
const inFlight2 = state.inFlightCompilations.get(sourceAbsPath);
|
|
2298
|
+
if (inFlight2) {
|
|
2299
|
+
await inFlight2.catch(() => {
|
|
1921
2300
|
});
|
|
1922
2301
|
return false;
|
|
1923
2302
|
}
|
|
1924
2303
|
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
1925
2304
|
return false;
|
|
1926
2305
|
}
|
|
1927
|
-
if (!
|
|
2306
|
+
if (!fs8.existsSync(sourceAbsPath)) {
|
|
1928
2307
|
return false;
|
|
1929
2308
|
}
|
|
1930
2309
|
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
@@ -1933,12 +2312,23 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
|
1933
2312
|
return false;
|
|
1934
2313
|
}
|
|
1935
2314
|
const compilePromise = (async () => {
|
|
2315
|
+
const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);
|
|
2316
|
+
const files = [sourceAbsPath];
|
|
2317
|
+
for (const dep of insideFiles) {
|
|
2318
|
+
if (state.compiledFiles.has(dep)) continue;
|
|
2319
|
+
const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);
|
|
2320
|
+
if (depProduct && isProductFresh(dep, depProduct)) continue;
|
|
2321
|
+
files.push(dep);
|
|
2322
|
+
}
|
|
1936
2323
|
await compileDevRoutes({
|
|
1937
2324
|
rootDir,
|
|
1938
2325
|
dist,
|
|
1939
|
-
files
|
|
2326
|
+
files,
|
|
1940
2327
|
logLevel: "silent"
|
|
1941
2328
|
});
|
|
2329
|
+
for (const file of files) {
|
|
2330
|
+
state.compiledFiles.add(file);
|
|
2331
|
+
}
|
|
1942
2332
|
state.compiledFiles.add(sourceAbsPath);
|
|
1943
2333
|
})();
|
|
1944
2334
|
state.inFlightCompilations.set(sourceAbsPath, compilePromise);
|
|
@@ -1949,30 +2339,43 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
|
1949
2339
|
state.inFlightCompilations.delete(sourceAbsPath);
|
|
1950
2340
|
}
|
|
1951
2341
|
}
|
|
2342
|
+
async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
|
|
2343
|
+
if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;
|
|
2344
|
+
const dist = getDevDist();
|
|
2345
|
+
if (!dist) return;
|
|
2346
|
+
for (const mwPath of middlewarePaths) {
|
|
2347
|
+
const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);
|
|
2348
|
+
try {
|
|
2349
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2350
|
+
} catch (err) {
|
|
2351
|
+
console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
1952
2355
|
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
1953
|
-
const rel =
|
|
2356
|
+
const rel = path10.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1954
2357
|
if (!rel.startsWith("src/")) return null;
|
|
1955
2358
|
const relWithoutSrc = rel.slice(4);
|
|
1956
2359
|
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
1957
|
-
return
|
|
2360
|
+
return path10.resolve(rootDir, dist, jsRel);
|
|
1958
2361
|
}
|
|
1959
2362
|
function clearGeneratedSchemas() {
|
|
1960
2363
|
state.generatedSchemas.clear();
|
|
1961
2364
|
state.inFlightSchemaGenerations.clear();
|
|
1962
2365
|
}
|
|
1963
2366
|
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
1964
|
-
const
|
|
1965
|
-
if (
|
|
1966
|
-
await
|
|
2367
|
+
const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
|
|
2368
|
+
if (inFlight2) {
|
|
2369
|
+
await inFlight2.catch(() => {
|
|
1967
2370
|
});
|
|
1968
2371
|
return false;
|
|
1969
2372
|
}
|
|
1970
2373
|
if (state.generatedSchemas.has(schemaPath)) {
|
|
1971
2374
|
return false;
|
|
1972
2375
|
}
|
|
1973
|
-
const prodAbsPath =
|
|
2376
|
+
const prodAbsPath = path10.resolve(rootDir, routeFilePath);
|
|
1974
2377
|
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
1975
|
-
if (!
|
|
2378
|
+
if (!fs8.existsSync(sourceAbsPath)) {
|
|
1976
2379
|
return false;
|
|
1977
2380
|
}
|
|
1978
2381
|
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
@@ -1983,7 +2386,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
1983
2386
|
if (fileRoutes.length === 0) {
|
|
1984
2387
|
return false;
|
|
1985
2388
|
}
|
|
1986
|
-
const sourceRelPath =
|
|
2389
|
+
const sourceRelPath = path10.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1987
2390
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
1988
2391
|
const generatePromise = (async () => {
|
|
1989
2392
|
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
@@ -2004,7 +2407,7 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
|
2004
2407
|
if (deleted.has(schemaPath)) continue;
|
|
2005
2408
|
deleted.add(schemaPath);
|
|
2006
2409
|
try {
|
|
2007
|
-
await
|
|
2410
|
+
await fs8.promises.unlink(schemaPath);
|
|
2008
2411
|
} catch {
|
|
2009
2412
|
}
|
|
2010
2413
|
}
|
|
@@ -2013,19 +2416,19 @@ var sourcePathCache = /* @__PURE__ */ new Map();
|
|
|
2013
2416
|
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2014
2417
|
const cached = sourcePathCache.get(prodAbsPath);
|
|
2015
2418
|
if (cached) return cached;
|
|
2016
|
-
const rel =
|
|
2419
|
+
const rel = path10.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
2017
2420
|
let relWithoutDist = rel;
|
|
2018
2421
|
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
2019
2422
|
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
2020
2423
|
}
|
|
2021
2424
|
const srcRel = `src/${relWithoutDist}`;
|
|
2022
2425
|
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2023
|
-
const tsAbs =
|
|
2426
|
+
const tsAbs = path10.resolve(rootDir, tsRel);
|
|
2024
2427
|
let result;
|
|
2025
|
-
if (
|
|
2428
|
+
if (fs8.existsSync(tsAbs)) {
|
|
2026
2429
|
result = tsAbs;
|
|
2027
2430
|
} else {
|
|
2028
|
-
result =
|
|
2431
|
+
result = path10.resolve(rootDir, srcRel);
|
|
2029
2432
|
}
|
|
2030
2433
|
sourcePathCache.set(prodAbsPath, result);
|
|
2031
2434
|
return result;
|
|
@@ -2043,7 +2446,7 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
|
2043
2446
|
const dist = getDevDist();
|
|
2044
2447
|
if (dist) {
|
|
2045
2448
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2046
|
-
if (sourcePath &&
|
|
2449
|
+
if (sourcePath && fs9.existsSync(sourcePath)) {
|
|
2047
2450
|
try {
|
|
2048
2451
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2049
2452
|
} catch (compileErr) {
|
|
@@ -2076,13 +2479,13 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
|
2076
2479
|
}
|
|
2077
2480
|
|
|
2078
2481
|
// src/loader/loadToolModule.ts
|
|
2079
|
-
import
|
|
2482
|
+
import fs10 from "fs";
|
|
2080
2483
|
async function loadToolModule(filePath, functionName, rootDir) {
|
|
2081
2484
|
if (isDevOnDemandEnabled() && rootDir) {
|
|
2082
2485
|
const dist = getDevDist();
|
|
2083
2486
|
if (dist) {
|
|
2084
2487
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2085
|
-
if (sourcePath &&
|
|
2488
|
+
if (sourcePath && fs10.existsSync(sourcePath)) {
|
|
2086
2489
|
try {
|
|
2087
2490
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2088
2491
|
} catch (compileErr) {
|
|
@@ -2114,12 +2517,46 @@ async function loadToolModule(filePath, functionName, rootDir) {
|
|
|
2114
2517
|
import { existsSync as existsSync2 } from "fs";
|
|
2115
2518
|
|
|
2116
2519
|
// src/cli/generateToolArtifacts.ts
|
|
2117
|
-
import
|
|
2118
|
-
import fs9 from "fs/promises";
|
|
2520
|
+
import path11 from "path";
|
|
2119
2521
|
import { existsSync } from "fs";
|
|
2120
2522
|
|
|
2121
2523
|
// src/ast/extractToolMetadata.ts
|
|
2524
|
+
import ts8 from "typescript";
|
|
2525
|
+
|
|
2526
|
+
// src/ast/jsDocMetadata.ts
|
|
2122
2527
|
import ts7 from "typescript";
|
|
2528
|
+
function hasExportModifier(node) {
|
|
2529
|
+
if (!ts7.canHaveModifiers(node)) return false;
|
|
2530
|
+
const modifiers = ts7.getModifiers(node);
|
|
2531
|
+
return !!modifiers?.some((m) => m.kind === ts7.SyntaxKind.ExportKeyword);
|
|
2532
|
+
}
|
|
2533
|
+
function getJSDocFromNode(node) {
|
|
2534
|
+
const apiDocs = ts7.getJSDocCommentsAndTags(node).filter((entry) => ts7.isJSDoc(entry));
|
|
2535
|
+
if (apiDocs.length > 0) return apiDocs[0];
|
|
2536
|
+
const directDocs = node.jsDoc;
|
|
2537
|
+
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
2538
|
+
return void 0;
|
|
2539
|
+
}
|
|
2540
|
+
function extractDescription(jsDoc) {
|
|
2541
|
+
if (!jsDoc) return void 0;
|
|
2542
|
+
if (typeof jsDoc.comment !== "string") return void 0;
|
|
2543
|
+
const trimmed = jsDoc.comment.trim();
|
|
2544
|
+
return trimmed || void 0;
|
|
2545
|
+
}
|
|
2546
|
+
function extractJSDocTagValue(jsDoc, tagName) {
|
|
2547
|
+
if (!jsDoc || !jsDoc.tags) return void 0;
|
|
2548
|
+
for (const tag of jsDoc.tags) {
|
|
2549
|
+
if (tag.tagName.text !== tagName) continue;
|
|
2550
|
+
if (typeof tag.comment !== "string") return void 0;
|
|
2551
|
+
const text = tag.comment.trim();
|
|
2552
|
+
if (!text) return void 0;
|
|
2553
|
+
const cleaned = text.replace(/^\{|\}$/g, "").trim();
|
|
2554
|
+
return cleaned || void 0;
|
|
2555
|
+
}
|
|
2556
|
+
return void 0;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// src/ast/extractToolMetadata.ts
|
|
2123
2560
|
function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
2124
2561
|
const sourceFile = program.getSourceFile(filePath);
|
|
2125
2562
|
if (!sourceFile) return null;
|
|
@@ -2128,7 +2565,7 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
|
2128
2565
|
const { fn, jsDocOwner } = found;
|
|
2129
2566
|
const jsDoc = getJSDocFromNode(jsDocOwner);
|
|
2130
2567
|
const description = extractDescription(jsDoc);
|
|
2131
|
-
const toolNameOverride =
|
|
2568
|
+
const toolNameOverride = extractJSDocTagValue(jsDoc, "tool");
|
|
2132
2569
|
const inputTypeName = getFirstParamTypeName(fn, sourceFile);
|
|
2133
2570
|
return {
|
|
2134
2571
|
name: toolNameOverride ?? pathMeta.name,
|
|
@@ -2140,18 +2577,18 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
|
2140
2577
|
}
|
|
2141
2578
|
function findExportedFunction(sourceFile, functionName) {
|
|
2142
2579
|
let result = null;
|
|
2143
|
-
|
|
2580
|
+
ts8.forEachChild(sourceFile, (node) => {
|
|
2144
2581
|
if (result) return;
|
|
2145
|
-
if (
|
|
2582
|
+
if (ts8.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
|
|
2146
2583
|
result = { fn: node, jsDocOwner: node };
|
|
2147
2584
|
return;
|
|
2148
2585
|
}
|
|
2149
|
-
if (
|
|
2586
|
+
if (ts8.isVariableStatement(node) && hasExportModifier(node)) {
|
|
2150
2587
|
for (const decl of node.declarationList.declarations) {
|
|
2151
2588
|
if (result) break;
|
|
2152
|
-
const nameText =
|
|
2589
|
+
const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
|
|
2153
2590
|
if (nameText !== functionName || !decl.initializer) continue;
|
|
2154
|
-
if (
|
|
2591
|
+
if (ts8.isArrowFunction(decl.initializer) || ts8.isFunctionExpression(decl.initializer)) {
|
|
2155
2592
|
result = { fn: decl.initializer, jsDocOwner: node };
|
|
2156
2593
|
}
|
|
2157
2594
|
}
|
|
@@ -2159,78 +2596,23 @@ function findExportedFunction(sourceFile, functionName) {
|
|
|
2159
2596
|
});
|
|
2160
2597
|
return result;
|
|
2161
2598
|
}
|
|
2162
|
-
function hasExportModifier(node) {
|
|
2163
|
-
if (!ts7.canHaveModifiers(node)) return false;
|
|
2164
|
-
const modifiers = ts7.getModifiers(node);
|
|
2165
|
-
return !!modifiers?.some((m) => m.kind === ts7.SyntaxKind.ExportKeyword);
|
|
2166
|
-
}
|
|
2167
|
-
function getJSDocFromNode(node) {
|
|
2168
|
-
const apiDocs = ts7.getJSDocCommentsAndTags(node).filter((entry) => ts7.isJSDoc(entry));
|
|
2169
|
-
if (apiDocs.length > 0) return apiDocs[0];
|
|
2170
|
-
const directDocs = node.jsDoc;
|
|
2171
|
-
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
2172
|
-
return void 0;
|
|
2173
|
-
}
|
|
2174
|
-
function extractDescription(jsDoc) {
|
|
2175
|
-
if (!jsDoc) return void 0;
|
|
2176
|
-
if (typeof jsDoc.comment !== "string") return void 0;
|
|
2177
|
-
const trimmed = jsDoc.comment.trim();
|
|
2178
|
-
return trimmed || void 0;
|
|
2179
|
-
}
|
|
2180
|
-
function extractToolTagValue(jsDoc) {
|
|
2181
|
-
if (!jsDoc || !jsDoc.tags) return void 0;
|
|
2182
|
-
for (const tag of jsDoc.tags) {
|
|
2183
|
-
if (tag.tagName.text !== "tool") continue;
|
|
2184
|
-
if (typeof tag.comment !== "string") return void 0;
|
|
2185
|
-
const text = tag.comment.trim();
|
|
2186
|
-
if (!text) return void 0;
|
|
2187
|
-
const cleaned = text.replace(/^\{|\}$/g, "").trim();
|
|
2188
|
-
return cleaned || void 0;
|
|
2189
|
-
}
|
|
2190
|
-
return void 0;
|
|
2191
|
-
}
|
|
2192
2599
|
function getFirstParamTypeName(fn, sourceFile) {
|
|
2193
2600
|
const firstParam = fn.parameters[0];
|
|
2194
2601
|
if (!firstParam) return void 0;
|
|
2195
2602
|
if (!firstParam.type) return void 0;
|
|
2196
|
-
if (!
|
|
2603
|
+
if (!ts8.isTypeReferenceNode(firstParam.type)) return void 0;
|
|
2197
2604
|
return firstParam.type.typeName.getText(sourceFile);
|
|
2198
2605
|
}
|
|
2199
|
-
|
|
2200
|
-
// src/cli/generateToolArtifacts.ts
|
|
2201
|
-
init_createProgram();
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
rel = rel.slice(4);
|
|
2210
|
-
}
|
|
2211
|
-
const idx = rel.lastIndexOf("/");
|
|
2212
|
-
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2213
|
-
return path8.resolve(rootDir, dist, relDir, "zod.js");
|
|
2214
|
-
}
|
|
2215
|
-
function getRuntimeToolSchemaPath(filePath, dist, rootDir) {
|
|
2216
|
-
let rel = filePath.replace(/\\/g, "/");
|
|
2217
|
-
if (rel.startsWith("src/")) {
|
|
2218
|
-
rel = rel.slice(4);
|
|
2219
|
-
} else if (rel.startsWith(`${dist}/`)) {
|
|
2220
|
-
rel = rel.slice(dist.length + 1);
|
|
2221
|
-
}
|
|
2222
|
-
const idx = rel.lastIndexOf("/");
|
|
2223
|
-
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2224
|
-
return path8.resolve(rootDir, dist, relDir, "zod.js");
|
|
2225
|
-
}
|
|
2226
|
-
function toProdFilePath(filePath, dist) {
|
|
2227
|
-
let rel = filePath.replace(/\\/g, "/");
|
|
2228
|
-
if (rel.startsWith("src/")) {
|
|
2229
|
-
rel = rel.slice(4);
|
|
2230
|
-
}
|
|
2231
|
-
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
2232
|
-
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
2233
|
-
}
|
|
2606
|
+
|
|
2607
|
+
// src/cli/generateToolArtifacts.ts
|
|
2608
|
+
init_createProgram();
|
|
2609
|
+
init_atomicWrite();
|
|
2610
|
+
init_generateSchemaFiles();
|
|
2611
|
+
init_extractHandlerTypes();
|
|
2612
|
+
init_generateZodSchema();
|
|
2613
|
+
init_generateSchemaFiles();
|
|
2614
|
+
init_generateSchemaFiles();
|
|
2615
|
+
var TOOLS_FILE = "faapi-tools.js";
|
|
2234
2616
|
function serializeTools(tools, dist = "dist") {
|
|
2235
2617
|
return tools.map((t) => ({
|
|
2236
2618
|
name: t.name,
|
|
@@ -2241,12 +2623,10 @@ function serializeTools(tools, dist = "dist") {
|
|
|
2241
2623
|
}));
|
|
2242
2624
|
}
|
|
2243
2625
|
async function writeToolsModule(manifest, outputPath) {
|
|
2244
|
-
const dir = path8.dirname(outputPath);
|
|
2245
|
-
await fs9.mkdir(dir, { recursive: true });
|
|
2246
2626
|
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
2247
2627
|
export const tools = ${JSON.stringify(manifest, null, 2)};
|
|
2248
2628
|
`;
|
|
2249
|
-
await
|
|
2629
|
+
await atomicWriteFile(outputPath, content);
|
|
2250
2630
|
}
|
|
2251
2631
|
function hydrateTools(manifest) {
|
|
2252
2632
|
return manifest.map((t) => ({
|
|
@@ -2261,7 +2641,7 @@ function collectToolSchemaSources(tools, rootDir) {
|
|
|
2261
2641
|
const toolsByFile = /* @__PURE__ */ new Map();
|
|
2262
2642
|
for (const tool of tools) {
|
|
2263
2643
|
if (!tool.inputTypeName) continue;
|
|
2264
|
-
const absPath =
|
|
2644
|
+
const absPath = path11.resolve(rootDir, tool.filePath);
|
|
2265
2645
|
let list = toolsByFile.get(absPath);
|
|
2266
2646
|
if (!list) {
|
|
2267
2647
|
list = [];
|
|
@@ -2270,11 +2650,9 @@ function collectToolSchemaSources(tools, rootDir) {
|
|
|
2270
2650
|
list.push(tool);
|
|
2271
2651
|
}
|
|
2272
2652
|
const programByFile = createPrograms([...toolsByFile.keys()]);
|
|
2273
|
-
const
|
|
2653
|
+
const resolversByFile = /* @__PURE__ */ new Map();
|
|
2274
2654
|
for (const filePath of toolsByFile.keys()) {
|
|
2275
|
-
|
|
2276
|
-
const allTypes = extractAllTypes(program, filePath);
|
|
2277
|
-
allTypesByFile.set(filePath, allTypes);
|
|
2655
|
+
resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
|
|
2278
2656
|
}
|
|
2279
2657
|
const sources = [];
|
|
2280
2658
|
for (const [filePath, fileTools] of toolsByFile) {
|
|
@@ -2291,10 +2669,9 @@ function collectToolSchemaSources(tools, rootDir) {
|
|
|
2291
2669
|
});
|
|
2292
2670
|
}
|
|
2293
2671
|
}
|
|
2294
|
-
return { sources,
|
|
2672
|
+
return { sources, resolversByFile };
|
|
2295
2673
|
}
|
|
2296
|
-
function generateToolSchemaFileSource(sources,
|
|
2297
|
-
const resolveType = (name) => allTypes.get(name)?.runtimeType;
|
|
2674
|
+
function generateToolSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
2298
2675
|
const lines = ["import { z } from 'zod';"];
|
|
2299
2676
|
const schemaBlocks = [];
|
|
2300
2677
|
for (const source of sources) {
|
|
@@ -2324,16 +2701,15 @@ function generateToolSchemaFileSource(sources, allTypes, helpersImportPath) {
|
|
|
2324
2701
|
}
|
|
2325
2702
|
async function maybeGenerateHelpers(allSourceCode, distDir) {
|
|
2326
2703
|
if (!usesCoerceHelpers(allSourceCode)) return;
|
|
2327
|
-
const helpersPath =
|
|
2704
|
+
const helpersPath = path11.resolve(distDir, HELPERS_FILENAME);
|
|
2328
2705
|
if (existsSync(helpersPath)) return;
|
|
2329
|
-
await
|
|
2330
|
-
await fs9.writeFile(helpersPath, generateHelpersFileSource(), "utf-8");
|
|
2706
|
+
await atomicWriteFile(helpersPath, generateHelpersFileSource());
|
|
2331
2707
|
}
|
|
2332
2708
|
async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
2333
2709
|
const metadata = [];
|
|
2334
|
-
const programByFile = createPrograms(tools.map((m) =>
|
|
2710
|
+
const programByFile = createPrograms(tools.map((m) => path11.resolve(rootDir, m.filePath)));
|
|
2335
2711
|
for (const manifest of tools) {
|
|
2336
|
-
const absPath =
|
|
2712
|
+
const absPath = path11.resolve(rootDir, manifest.filePath);
|
|
2337
2713
|
const program = programByFile.get(absPath);
|
|
2338
2714
|
const result = extractToolMetadata(program, absPath, manifest.functionName, {
|
|
2339
2715
|
name: manifest.name,
|
|
@@ -2344,7 +2720,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2344
2720
|
}
|
|
2345
2721
|
}
|
|
2346
2722
|
const serialized = serializeTools(metadata, dist);
|
|
2347
|
-
const toolsPath =
|
|
2723
|
+
const toolsPath = path11.resolve(rootDir, dist, TOOLS_FILE);
|
|
2348
2724
|
await writeToolsModule(serialized, toolsPath);
|
|
2349
2725
|
if (options?.skipSchema) {
|
|
2350
2726
|
return metadata;
|
|
@@ -2352,7 +2728,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2352
2728
|
if (metadata.length === 0) {
|
|
2353
2729
|
return metadata;
|
|
2354
2730
|
}
|
|
2355
|
-
const { sources,
|
|
2731
|
+
const { sources, resolversByFile } = collectToolSchemaSources(metadata, rootDir);
|
|
2356
2732
|
if (sources.length === 0) {
|
|
2357
2733
|
return metadata;
|
|
2358
2734
|
}
|
|
@@ -2367,9 +2743,9 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2367
2743
|
}
|
|
2368
2744
|
const fileEntries = [];
|
|
2369
2745
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
2370
|
-
const relFile =
|
|
2371
|
-
const outputPath =
|
|
2372
|
-
const
|
|
2746
|
+
const relFile = path11.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2747
|
+
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
2748
|
+
const resolver = resolversByFile.get(filePath);
|
|
2373
2749
|
let relForDir = relFile;
|
|
2374
2750
|
if (relForDir.startsWith("src/")) {
|
|
2375
2751
|
relForDir = relForDir.slice(4);
|
|
@@ -2377,11 +2753,15 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2377
2753
|
const dirIdx = relForDir.lastIndexOf("/");
|
|
2378
2754
|
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
2379
2755
|
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
2380
|
-
const source = generateToolSchemaFileSource(
|
|
2756
|
+
const source = generateToolSchemaFileSource(
|
|
2757
|
+
fileSources,
|
|
2758
|
+
(name) => resolver?.resolve(name)?.runtimeType,
|
|
2759
|
+
helpersImportPath
|
|
2760
|
+
);
|
|
2381
2761
|
fileEntries.push({ outputPath, source });
|
|
2382
2762
|
}
|
|
2383
2763
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2384
|
-
const distDir =
|
|
2764
|
+
const distDir = path11.resolve(rootDir, dist);
|
|
2385
2765
|
await maybeGenerateHelpers(allSourceCode, distDir);
|
|
2386
2766
|
await Promise.all(
|
|
2387
2767
|
fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
|
|
@@ -2389,8 +2769,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2389
2769
|
return metadata;
|
|
2390
2770
|
}
|
|
2391
2771
|
async function writeToolSchemaFile(outputPath, source) {
|
|
2392
|
-
await
|
|
2393
|
-
await fs9.writeFile(outputPath, source, "utf-8");
|
|
2772
|
+
await atomicWriteFile(outputPath, source);
|
|
2394
2773
|
}
|
|
2395
2774
|
|
|
2396
2775
|
// src/loader/loadToolSchema.ts
|
|
@@ -2402,7 +2781,7 @@ function getDist() {
|
|
|
2402
2781
|
}
|
|
2403
2782
|
function getToolSchemaPath(tool, rootDir) {
|
|
2404
2783
|
const dist = getDist();
|
|
2405
|
-
return
|
|
2784
|
+
return getRuntimeSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
|
|
2406
2785
|
}
|
|
2407
2786
|
async function loadToolSchema(tool, rootDir) {
|
|
2408
2787
|
if (!tool.inputTypeName) return void 0;
|
|
@@ -2420,16 +2799,14 @@ async function loadToolSchema(tool, rootDir) {
|
|
|
2420
2799
|
}
|
|
2421
2800
|
|
|
2422
2801
|
// src/injection/agentHandle.ts
|
|
2423
|
-
var currentFactory = null;
|
|
2424
2802
|
function registerAgentHandleFactory(factory) {
|
|
2425
|
-
|
|
2803
|
+
defaultRegistries.agentHandle.register(factory);
|
|
2426
2804
|
}
|
|
2427
2805
|
function getAgentHandle(ctx) {
|
|
2428
|
-
|
|
2429
|
-
return currentFactory(ctx);
|
|
2806
|
+
return defaultRegistries.agentHandle.get(ctx);
|
|
2430
2807
|
}
|
|
2431
2808
|
function clearAgentHandleFactory() {
|
|
2432
|
-
|
|
2809
|
+
defaultRegistries.agentHandle.clear();
|
|
2433
2810
|
}
|
|
2434
2811
|
|
|
2435
2812
|
// src/middleware/cors.ts
|
|
@@ -2457,11 +2834,6 @@ function cors(options = {}) {
|
|
|
2457
2834
|
} else if (Array.isArray(origin)) {
|
|
2458
2835
|
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
2459
2836
|
}
|
|
2460
|
-
if (!allowOrigin) {
|
|
2461
|
-
await next();
|
|
2462
|
-
return;
|
|
2463
|
-
}
|
|
2464
|
-
ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
2465
2837
|
if (origin === true || Array.isArray(origin)) {
|
|
2466
2838
|
const existingVary = ctx.headers.get("vary");
|
|
2467
2839
|
if (existingVary) {
|
|
@@ -2472,6 +2844,11 @@ function cors(options = {}) {
|
|
|
2472
2844
|
ctx.setHeader("Vary", "Origin");
|
|
2473
2845
|
}
|
|
2474
2846
|
}
|
|
2847
|
+
if (!allowOrigin) {
|
|
2848
|
+
await next();
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
2475
2852
|
ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
2476
2853
|
if (allowedHeaders) {
|
|
2477
2854
|
ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
@@ -2593,16 +2970,16 @@ function helmet(options = {}) {
|
|
|
2593
2970
|
}
|
|
2594
2971
|
|
|
2595
2972
|
// src/config/loadConfig.ts
|
|
2596
|
-
import
|
|
2597
|
-
import
|
|
2973
|
+
import path12 from "path";
|
|
2974
|
+
import fs11 from "fs";
|
|
2598
2975
|
var CONFIG_PRODUCT_FILE = "faapi-config.js";
|
|
2599
2976
|
async function loadConfig(rootDir, dist) {
|
|
2600
|
-
const configProductPath =
|
|
2601
|
-
if (
|
|
2977
|
+
const configProductPath = path12.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
|
|
2978
|
+
if (fs11.existsSync(configProductPath)) {
|
|
2602
2979
|
const module = await importWithCacheBust(configProductPath);
|
|
2603
2980
|
return module.default ?? {};
|
|
2604
2981
|
}
|
|
2605
|
-
const hasSourceConfig =
|
|
2982
|
+
const hasSourceConfig = fs11.existsSync(path12.join(rootDir, "faapi.config.ts")) || fs11.existsSync(path12.join(rootDir, "faapi.config.js"));
|
|
2606
2983
|
if (hasSourceConfig) {
|
|
2607
2984
|
throw new Error(
|
|
2608
2985
|
`[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
@@ -2612,8 +2989,8 @@ async function loadConfig(rootDir, dist) {
|
|
|
2612
2989
|
}
|
|
2613
2990
|
|
|
2614
2991
|
// src/cli/loadEnv.ts
|
|
2615
|
-
import
|
|
2616
|
-
import
|
|
2992
|
+
import fs12 from "fs";
|
|
2993
|
+
import path13 from "path";
|
|
2617
2994
|
function resolveEnv() {
|
|
2618
2995
|
return process.env.NODE_ENV || "development";
|
|
2619
2996
|
}
|
|
@@ -2628,7 +3005,8 @@ function parseEnvFile(content, fileVars) {
|
|
|
2628
3005
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2629
3006
|
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
2630
3007
|
if (!match) continue;
|
|
2631
|
-
const
|
|
3008
|
+
const key = match[1];
|
|
3009
|
+
const rawValue = match[2];
|
|
2632
3010
|
const value = parseValue(rawValue, { ...fileVars, ...result });
|
|
2633
3011
|
result[key] = value;
|
|
2634
3012
|
}
|
|
@@ -2679,9 +3057,9 @@ function loadEnv(rootDir) {
|
|
|
2679
3057
|
const files = getEnvFiles(env);
|
|
2680
3058
|
const merged = {};
|
|
2681
3059
|
for (const file of files) {
|
|
2682
|
-
const filePath =
|
|
2683
|
-
if (!
|
|
2684
|
-
const content =
|
|
3060
|
+
const filePath = path13.join(rootDir, file);
|
|
3061
|
+
if (!fs12.existsSync(filePath)) continue;
|
|
3062
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
2685
3063
|
const parsed = parseEnvFile(content, merged);
|
|
2686
3064
|
Object.assign(merged, parsed);
|
|
2687
3065
|
}
|
|
@@ -2718,14 +3096,14 @@ var ValidationError = class extends FaapiError {
|
|
|
2718
3096
|
issues;
|
|
2719
3097
|
};
|
|
2720
3098
|
var RouteNotFoundError = class extends FaapiError {
|
|
2721
|
-
constructor(
|
|
2722
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
3099
|
+
constructor(path23) {
|
|
3100
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path23}`, 404);
|
|
2723
3101
|
this.name = "RouteNotFoundError";
|
|
2724
3102
|
}
|
|
2725
3103
|
};
|
|
2726
3104
|
var MethodNotAllowedError = class extends FaapiError {
|
|
2727
|
-
constructor(method,
|
|
2728
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
3105
|
+
constructor(method, path23, allowedMethods) {
|
|
3106
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path23}`, 405);
|
|
2729
3107
|
this.allowedMethods = allowedMethods;
|
|
2730
3108
|
this.name = "MethodNotAllowedError";
|
|
2731
3109
|
}
|
|
@@ -2752,7 +3130,7 @@ var PayloadTooLargeError = class extends FaapiError {
|
|
|
2752
3130
|
|
|
2753
3131
|
// src/cli/createAppCore.ts
|
|
2754
3132
|
import fs15 from "fs";
|
|
2755
|
-
import
|
|
3133
|
+
import path19 from "path";
|
|
2756
3134
|
import { PassThrough, Readable as Readable3 } from "stream";
|
|
2757
3135
|
|
|
2758
3136
|
// src/router/sortRoutes.ts
|
|
@@ -2805,7 +3183,7 @@ import {
|
|
|
2805
3183
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
2806
3184
|
import { readFileSync } from "fs";
|
|
2807
3185
|
import { Readable as Readable2 } from "stream";
|
|
2808
|
-
import
|
|
3186
|
+
import path15 from "path";
|
|
2809
3187
|
|
|
2810
3188
|
// src/router/matchRoute.ts
|
|
2811
3189
|
var httpIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2816,7 +3194,10 @@ function getHttpIndex(routes) {
|
|
|
2816
3194
|
index = { static: /* @__PURE__ */ new Map(), methodsByStaticPath: /* @__PURE__ */ new Map(), dynamics: [] };
|
|
2817
3195
|
for (const route of routes) {
|
|
2818
3196
|
if (route.isDynamic) {
|
|
2819
|
-
index.dynamics.push(
|
|
3197
|
+
index.dynamics.push({
|
|
3198
|
+
route,
|
|
3199
|
+
segments: route.urlPath.split("/").filter(Boolean)
|
|
3200
|
+
});
|
|
2820
3201
|
} else {
|
|
2821
3202
|
index.static.set(`${route.method}|${route.urlPath}`, route);
|
|
2822
3203
|
let methods = index.methodsByStaticPath.get(route.urlPath);
|
|
@@ -2844,57 +3225,77 @@ function getWsIndex(routes) {
|
|
|
2844
3225
|
wsIndexCache.set(routes, index);
|
|
2845
3226
|
return index;
|
|
2846
3227
|
}
|
|
2847
|
-
function matchRoute(routes, method,
|
|
3228
|
+
function matchRoute(routes, method, path23) {
|
|
2848
3229
|
const index = getHttpIndex(routes);
|
|
2849
|
-
const
|
|
3230
|
+
const upper = method.toUpperCase();
|
|
3231
|
+
const hit = matchByMethod(index, upper, path23);
|
|
3232
|
+
if (hit) return hit;
|
|
3233
|
+
if (upper === "HEAD") {
|
|
3234
|
+
return matchByMethod(index, "GET", path23);
|
|
3235
|
+
}
|
|
3236
|
+
return null;
|
|
3237
|
+
}
|
|
3238
|
+
function matchByMethod(index, method, path23) {
|
|
3239
|
+
const staticHit = index.static.get(`${method}|${path23}`);
|
|
2850
3240
|
if (staticHit) {
|
|
2851
3241
|
return { route: staticHit, params: {} };
|
|
2852
3242
|
}
|
|
2853
|
-
for (const
|
|
3243
|
+
for (const entry of index.dynamics) {
|
|
3244
|
+
const route = entry.route;
|
|
2854
3245
|
if (route.method !== method) {
|
|
2855
3246
|
continue;
|
|
2856
3247
|
}
|
|
2857
|
-
const params =
|
|
3248
|
+
const params = matchSegments(entry.segments, path23, route.paramNames, route.isCatchAll);
|
|
2858
3249
|
if (params !== null) {
|
|
2859
3250
|
return { route, params };
|
|
2860
3251
|
}
|
|
2861
3252
|
}
|
|
2862
3253
|
return null;
|
|
2863
3254
|
}
|
|
2864
|
-
function matchWsRoute(wsRoutes,
|
|
3255
|
+
function matchWsRoute(wsRoutes, path23) {
|
|
2865
3256
|
const index = getWsIndex(wsRoutes);
|
|
2866
|
-
const staticHit = index.static.get(
|
|
3257
|
+
const staticHit = index.static.get(path23);
|
|
2867
3258
|
if (staticHit) {
|
|
2868
3259
|
return { route: staticHit, params: {} };
|
|
2869
3260
|
}
|
|
2870
3261
|
for (const route of index.dynamics) {
|
|
2871
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3262
|
+
const params = matchDynamicPath(route.urlPath, path23, route.paramNames, route.isCatchAll);
|
|
2872
3263
|
if (params !== null) {
|
|
2873
3264
|
return { route, params };
|
|
2874
3265
|
}
|
|
2875
3266
|
}
|
|
2876
3267
|
return null;
|
|
2877
3268
|
}
|
|
2878
|
-
function findAllowedMethods(routes,
|
|
3269
|
+
function findAllowedMethods(routes, path23) {
|
|
2879
3270
|
const index = getHttpIndex(routes);
|
|
2880
3271
|
const methods = /* @__PURE__ */ new Set();
|
|
2881
|
-
const staticMethods = index.methodsByStaticPath.get(
|
|
3272
|
+
const staticMethods = index.methodsByStaticPath.get(path23);
|
|
2882
3273
|
if (staticMethods) {
|
|
2883
3274
|
for (const method of staticMethods) {
|
|
2884
3275
|
methods.add(method);
|
|
2885
3276
|
}
|
|
2886
3277
|
}
|
|
2887
|
-
for (const
|
|
2888
|
-
const params =
|
|
3278
|
+
for (const entry of index.dynamics) {
|
|
3279
|
+
const params = matchSegments(
|
|
3280
|
+
entry.segments,
|
|
3281
|
+
path23,
|
|
3282
|
+
entry.route.paramNames,
|
|
3283
|
+
entry.route.isCatchAll
|
|
3284
|
+
);
|
|
2889
3285
|
if (params !== null) {
|
|
2890
|
-
methods.add(route.method);
|
|
3286
|
+
methods.add(entry.route.method);
|
|
2891
3287
|
}
|
|
2892
3288
|
}
|
|
3289
|
+
if (methods.has("GET")) {
|
|
3290
|
+
methods.add("HEAD");
|
|
3291
|
+
}
|
|
2893
3292
|
return Array.from(methods);
|
|
2894
3293
|
}
|
|
2895
|
-
function matchDynamicPath(pattern,
|
|
2896
|
-
|
|
2897
|
-
|
|
3294
|
+
function matchDynamicPath(pattern, path23, paramNames, isCatchAll) {
|
|
3295
|
+
return matchSegments(pattern.split("/").filter(Boolean), path23, paramNames, isCatchAll);
|
|
3296
|
+
}
|
|
3297
|
+
function matchSegments(patternSegments, path23, paramNames, isCatchAll) {
|
|
3298
|
+
const pathSegments = path23.split("/").filter(Boolean);
|
|
2898
3299
|
if (isCatchAll) {
|
|
2899
3300
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
2900
3301
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -3133,10 +3534,14 @@ function formatErrorResponse(error, config) {
|
|
|
3133
3534
|
code: error.code,
|
|
3134
3535
|
message: error.message
|
|
3135
3536
|
});
|
|
3136
|
-
const bodyObj = typeof body2 === "object" && body2 !== null ? body2 : { error: body2 };
|
|
3137
|
-
const
|
|
3138
|
-
if (
|
|
3139
|
-
|
|
3537
|
+
const bodyObj = typeof body2 === "object" && body2 !== null ? { ...body2 } : { error: body2 };
|
|
3538
|
+
const existingError = bodyObj.error;
|
|
3539
|
+
if (existingError && typeof existingError === "object") {
|
|
3540
|
+
bodyObj.error = { ...existingError, issues: error.issues };
|
|
3541
|
+
} else if (typeof body2 === "object" && body2 !== null) {
|
|
3542
|
+
bodyObj.issues = error.issues;
|
|
3543
|
+
} else {
|
|
3544
|
+
bodyObj.error = { issues: error.issues };
|
|
3140
3545
|
}
|
|
3141
3546
|
return jsonOk(bodyObj, error.statusCode);
|
|
3142
3547
|
}
|
|
@@ -3193,10 +3598,10 @@ function formatSetCookie(name, value, options) {
|
|
|
3193
3598
|
if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
3194
3599
|
return cookie;
|
|
3195
3600
|
}
|
|
3196
|
-
function createContext(request, params, config = {}, ip = "") {
|
|
3197
|
-
return createContextFromUrl(request, new URL(request.url), params, config, ip);
|
|
3601
|
+
function createContext(request, params, config = {}, ip = "", registries) {
|
|
3602
|
+
return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);
|
|
3198
3603
|
}
|
|
3199
|
-
function createContextFromUrl(request, url, params, config = {}, ip = "") {
|
|
3604
|
+
function createContextFromUrl(request, url, params, config = {}, ip = "", registries) {
|
|
3200
3605
|
const meta = { headers: {}, setCookies: [] };
|
|
3201
3606
|
const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
|
|
3202
3607
|
const cookiesObj = {};
|
|
@@ -3293,6 +3698,9 @@ function createContextFromUrl(request, url, params, config = {}, ip = "") {
|
|
|
3293
3698
|
return formatFailResponse(options, config);
|
|
3294
3699
|
}
|
|
3295
3700
|
};
|
|
3701
|
+
if (registries) {
|
|
3702
|
+
ctx.registries = registries;
|
|
3703
|
+
}
|
|
3296
3704
|
const extend = config?.extendContext;
|
|
3297
3705
|
if (typeof extend === "function") {
|
|
3298
3706
|
extend(ctx);
|
|
@@ -3304,7 +3712,14 @@ function createContextFromUrl(request, url, params, config = {}, ip = "") {
|
|
|
3304
3712
|
function queryToObject(params) {
|
|
3305
3713
|
const result = {};
|
|
3306
3714
|
for (const [key, value] of params) {
|
|
3307
|
-
result[key]
|
|
3715
|
+
const existing = result[key];
|
|
3716
|
+
if (existing === void 0) {
|
|
3717
|
+
result[key] = value;
|
|
3718
|
+
} else if (Array.isArray(existing)) {
|
|
3719
|
+
existing.push(value);
|
|
3720
|
+
} else {
|
|
3721
|
+
result[key] = [existing, value];
|
|
3722
|
+
}
|
|
3308
3723
|
}
|
|
3309
3724
|
return result;
|
|
3310
3725
|
}
|
|
@@ -3360,7 +3775,7 @@ async function resolveInputFromUrl(method, request, url) {
|
|
|
3360
3775
|
}
|
|
3361
3776
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
3362
3777
|
const text2 = await request.text();
|
|
3363
|
-
if (text2
|
|
3778
|
+
if (isBlankText(text2)) return null;
|
|
3364
3779
|
const params = new URLSearchParams(text2);
|
|
3365
3780
|
const obj = {};
|
|
3366
3781
|
for (const [key, value] of params) {
|
|
@@ -3369,7 +3784,7 @@ async function resolveInputFromUrl(method, request, url) {
|
|
|
3369
3784
|
return obj;
|
|
3370
3785
|
}
|
|
3371
3786
|
const text = await request.text();
|
|
3372
|
-
if (text
|
|
3787
|
+
if (isBlankText(text)) {
|
|
3373
3788
|
return null;
|
|
3374
3789
|
}
|
|
3375
3790
|
const result = parseJsonBody(text);
|
|
@@ -3388,6 +3803,26 @@ async function resolveInputFromUrl(method, request, url) {
|
|
|
3388
3803
|
}
|
|
3389
3804
|
return queryToObject(url.searchParams);
|
|
3390
3805
|
}
|
|
3806
|
+
function isBlankText(text) {
|
|
3807
|
+
return text.length === 0 || !/\S/.test(text);
|
|
3808
|
+
}
|
|
3809
|
+
async function resolveBodyForQueryMethod(request) {
|
|
3810
|
+
const text = await request.text();
|
|
3811
|
+
if (isBlankText(text)) return void 0;
|
|
3812
|
+
const result = parseJsonBody(text);
|
|
3813
|
+
if (!result.success) {
|
|
3814
|
+
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
3815
|
+
{
|
|
3816
|
+
path: "body",
|
|
3817
|
+
code: "INVALID_FORMAT",
|
|
3818
|
+
expected: "JSON",
|
|
3819
|
+
received: "text",
|
|
3820
|
+
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
3821
|
+
}
|
|
3822
|
+
]);
|
|
3823
|
+
}
|
|
3824
|
+
return result.data;
|
|
3825
|
+
}
|
|
3391
3826
|
|
|
3392
3827
|
// src/utils/isPlainObject.ts
|
|
3393
3828
|
function isPlainObject(value) {
|
|
@@ -3401,6 +3836,25 @@ function isPlainObject(value) {
|
|
|
3401
3836
|
return proto === null || proto === Object.prototype;
|
|
3402
3837
|
}
|
|
3403
3838
|
|
|
3839
|
+
// src/response/pendingMeta.ts
|
|
3840
|
+
var pending = /* @__PURE__ */ new WeakMap();
|
|
3841
|
+
function deferMetaHeaders(response, headers) {
|
|
3842
|
+
const existing = pending.get(response);
|
|
3843
|
+
if (existing) {
|
|
3844
|
+
Object.assign(existing, headers);
|
|
3845
|
+
return;
|
|
3846
|
+
}
|
|
3847
|
+
pending.set(response, { ...headers });
|
|
3848
|
+
}
|
|
3849
|
+
function consumePendingMetaHeaders(response) {
|
|
3850
|
+
const headers = pending.get(response);
|
|
3851
|
+
if (headers) pending.delete(response);
|
|
3852
|
+
return headers;
|
|
3853
|
+
}
|
|
3854
|
+
function isHeadersOnlyMeta(meta) {
|
|
3855
|
+
return meta.status === void 0 && meta.setCookies.length === 0;
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3404
3858
|
// src/response/toResponse.ts
|
|
3405
3859
|
async function toResponse(value, meta) {
|
|
3406
3860
|
if (value instanceof Promise) {
|
|
@@ -3417,6 +3871,10 @@ async function toResponse(value, meta) {
|
|
|
3417
3871
|
};
|
|
3418
3872
|
if (value instanceof Response) {
|
|
3419
3873
|
if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
|
|
3874
|
+
if (isHeadersOnlyMeta(meta)) {
|
|
3875
|
+
deferMetaHeaders(value, meta.headers);
|
|
3876
|
+
return value;
|
|
3877
|
+
}
|
|
3420
3878
|
const headers2 = new Headers(value.headers);
|
|
3421
3879
|
applyMeta(headers2);
|
|
3422
3880
|
return new Response(value.body, {
|
|
@@ -3525,11 +3983,12 @@ function getBuiltinInjectionValue(type, ctx, body) {
|
|
|
3525
3983
|
}
|
|
3526
3984
|
return {};
|
|
3527
3985
|
// Phase 2.3:注入所有已注册 agent 元数据列表
|
|
3986
|
+
// 方案 A:优先读 app 实例注册表,无实例(编程式直调 ctx)回退默认全局实例
|
|
3528
3987
|
case "agents":
|
|
3529
|
-
return listAgents();
|
|
3988
|
+
return ctx.registries ? ctx.registries.agent.listAgents() : listAgents();
|
|
3530
3989
|
// Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
|
|
3531
3990
|
case "agent":
|
|
3532
|
-
return getAgentHandle(ctx);
|
|
3991
|
+
return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);
|
|
3533
3992
|
default:
|
|
3534
3993
|
return void 0;
|
|
3535
3994
|
}
|
|
@@ -3560,6 +4019,10 @@ function wrapResult(result, ctx) {
|
|
|
3560
4019
|
function mergeMeta(response, meta) {
|
|
3561
4020
|
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
3562
4021
|
if (!hasMeta) return response;
|
|
4022
|
+
if (isHeadersOnlyMeta(meta)) {
|
|
4023
|
+
deferMetaHeaders(response, meta.headers);
|
|
4024
|
+
return response;
|
|
4025
|
+
}
|
|
3563
4026
|
const headers = new Headers(response.headers);
|
|
3564
4027
|
for (const [key, value] of Object.entries(meta.headers)) {
|
|
3565
4028
|
headers.set(key, value);
|
|
@@ -3652,11 +4115,24 @@ async function sendNodeResponse(response, res) {
|
|
|
3652
4115
|
res.setHeader(key, value);
|
|
3653
4116
|
}
|
|
3654
4117
|
}
|
|
4118
|
+
const deferred = consumePendingMetaHeaders(response);
|
|
4119
|
+
if (deferred) {
|
|
4120
|
+
for (const [key, value] of Object.entries(deferred)) {
|
|
4121
|
+
res.setHeader(key, value);
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
3655
4124
|
if (response.body) {
|
|
3656
4125
|
const nodeStream = Readable.fromWeb(response.body);
|
|
3657
4126
|
await new Promise((resolve, reject) => {
|
|
3658
4127
|
nodeStream.on("error", reject);
|
|
3659
|
-
|
|
4128
|
+
const abort = () => {
|
|
4129
|
+
nodeStream.destroy();
|
|
4130
|
+
resolve();
|
|
4131
|
+
};
|
|
4132
|
+
res.on("error", abort);
|
|
4133
|
+
res.on("close", () => {
|
|
4134
|
+
if (!res.writableEnded) abort();
|
|
4135
|
+
});
|
|
3660
4136
|
res.on("finish", resolve);
|
|
3661
4137
|
nodeStream.pipe(res);
|
|
3662
4138
|
});
|
|
@@ -3691,28 +4167,25 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
3691
4167
|
}
|
|
3692
4168
|
const schema = mod[schemaKey];
|
|
3693
4169
|
if (schema === void 0 || schema === null) {
|
|
3694
|
-
|
|
3695
|
-
return { valid: true, issues: [], data };
|
|
4170
|
+
return { valid: true, issues: [], data: input };
|
|
3696
4171
|
}
|
|
3697
4172
|
if (typeof schema !== "object" || typeof schema.safeParse !== "function") {
|
|
3698
4173
|
throw new InternalError(`Schema \u4E0D\u662F\u6709\u6548\u7684 zod schema: ${schemaPath}#${schemaName}`);
|
|
3699
4174
|
}
|
|
3700
|
-
const inputObj = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
|
|
3701
4175
|
const zodSchema = schema;
|
|
3702
|
-
const result = zodSchema.safeParse(
|
|
4176
|
+
const result = zodSchema.safeParse(input);
|
|
3703
4177
|
if (result.success) {
|
|
3704
|
-
|
|
3705
|
-
return { valid: true, issues: [], data };
|
|
4178
|
+
return { valid: true, issues: [], data: result.data };
|
|
3706
4179
|
}
|
|
3707
4180
|
const issues = mapZodIssues(result.error);
|
|
3708
|
-
return { valid: false, issues, data:
|
|
4181
|
+
return { valid: false, issues, data: input };
|
|
3709
4182
|
}
|
|
3710
4183
|
function mapZodIssues(error) {
|
|
3711
4184
|
return error.issues.map((issue) => {
|
|
3712
|
-
const code = mapZodCode(issue
|
|
3713
|
-
const
|
|
4185
|
+
const code = mapZodCode(issue);
|
|
4186
|
+
const path23 = issue.path.map(String).join(".") || "";
|
|
3714
4187
|
return {
|
|
3715
|
-
path:
|
|
4188
|
+
path: path23,
|
|
3716
4189
|
code,
|
|
3717
4190
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
3718
4191
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -3720,27 +4193,29 @@ function mapZodIssues(error) {
|
|
|
3720
4193
|
};
|
|
3721
4194
|
});
|
|
3722
4195
|
}
|
|
3723
|
-
function mapZodCode(
|
|
3724
|
-
switch (
|
|
4196
|
+
function mapZodCode(issue) {
|
|
4197
|
+
switch (issue.code) {
|
|
3725
4198
|
case "invalid_type":
|
|
4199
|
+
if (issue.received === "undefined" || /received undefined/i.test(issue.message)) {
|
|
4200
|
+
return "MISSING_FIELD";
|
|
4201
|
+
}
|
|
4202
|
+
return "TYPE_MISMATCH";
|
|
3726
4203
|
case "invalid_union":
|
|
3727
4204
|
case "invalid_union_discriminator":
|
|
3728
4205
|
return "TYPE_MISMATCH";
|
|
3729
4206
|
case "unrecognized_keys":
|
|
3730
4207
|
return "INVALID_FORMAT";
|
|
3731
4208
|
case "invalid_value":
|
|
3732
|
-
case "
|
|
4209
|
+
case "invalid_format":
|
|
4210
|
+
case "invalid_key":
|
|
4211
|
+
case "invalid_element":
|
|
3733
4212
|
case "too_small":
|
|
3734
4213
|
case "too_big":
|
|
3735
4214
|
case "invalid_intersection_types":
|
|
3736
4215
|
case "not_multiple_of":
|
|
3737
|
-
return "INVALID_VALUE";
|
|
3738
4216
|
case "custom":
|
|
3739
4217
|
return "INVALID_VALUE";
|
|
3740
4218
|
default:
|
|
3741
|
-
if (message.includes("Required") || message.includes("required")) {
|
|
3742
|
-
return "MISSING_FIELD";
|
|
3743
|
-
}
|
|
3744
4219
|
return "INVALID_VALUE";
|
|
3745
4220
|
}
|
|
3746
4221
|
}
|
|
@@ -3775,10 +4250,176 @@ function getClientIp(req, trustedProxy = false) {
|
|
|
3775
4250
|
return "";
|
|
3776
4251
|
}
|
|
3777
4252
|
|
|
4253
|
+
// src/middleware/compression.ts
|
|
4254
|
+
import zlib from "zlib";
|
|
4255
|
+
import { promisify } from "util";
|
|
4256
|
+
var gzipAsync = promisify(zlib.gzip);
|
|
4257
|
+
var deflateAsync = promisify(zlib.deflate);
|
|
4258
|
+
var brotliAsync = promisify(zlib.brotliCompress);
|
|
4259
|
+
var DEFAULT_THRESHOLD = 1024;
|
|
4260
|
+
var COMPRESSIBLE_CHECKS = [
|
|
4261
|
+
(ct) => ct === "application/json",
|
|
4262
|
+
(ct) => ct === "application/javascript",
|
|
4263
|
+
(ct) => ct === "image/svg+xml",
|
|
4264
|
+
(ct) => ct.startsWith("text/") && ct !== "text/event-stream"
|
|
4265
|
+
];
|
|
4266
|
+
function isCompressible(contentType) {
|
|
4267
|
+
const ct = contentType.split(";")[0].trim().toLowerCase();
|
|
4268
|
+
if (ct === "") return false;
|
|
4269
|
+
return COMPRESSIBLE_CHECKS.some((check) => check(ct));
|
|
4270
|
+
}
|
|
4271
|
+
function parseAcceptEncoding(header) {
|
|
4272
|
+
return header.split(",").map((part) => part.trim()).filter((part) => part !== "").map((part) => {
|
|
4273
|
+
const [encoding, ...params] = part.split(";");
|
|
4274
|
+
let quality = 1;
|
|
4275
|
+
for (const param of params) {
|
|
4276
|
+
const trimmed = param.trim();
|
|
4277
|
+
if (trimmed.startsWith("q=")) {
|
|
4278
|
+
const q = Number(trimmed.slice(2));
|
|
4279
|
+
if (Number.isFinite(q)) quality = q;
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
return { encoding: (encoding ?? "").trim().toLowerCase(), quality };
|
|
4283
|
+
}).filter((e) => e.encoding !== "" && e.quality > 0);
|
|
4284
|
+
}
|
|
4285
|
+
function selectEncoding(accepted) {
|
|
4286
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4287
|
+
for (const { encoding, quality } of accepted) {
|
|
4288
|
+
byName.set(encoding, Math.max(byName.get(encoding) ?? 0, quality));
|
|
4289
|
+
}
|
|
4290
|
+
for (const [encoding, quality] of byName) {
|
|
4291
|
+
if (quality === 0) byName.delete(encoding);
|
|
4292
|
+
}
|
|
4293
|
+
for (const candidate of ["br", "gzip", "deflate"]) {
|
|
4294
|
+
if ((byName.get(candidate) ?? 0) > 0) return candidate;
|
|
4295
|
+
}
|
|
4296
|
+
if ((byName.get("*") ?? 0) > 0) return "gzip";
|
|
4297
|
+
return null;
|
|
4298
|
+
}
|
|
4299
|
+
async function compressBody(encoding, body) {
|
|
4300
|
+
const buf = Buffer.from(body, "utf-8");
|
|
4301
|
+
switch (encoding) {
|
|
4302
|
+
case "br":
|
|
4303
|
+
return brotliAsync(buf);
|
|
4304
|
+
case "gzip":
|
|
4305
|
+
return gzipAsync(buf);
|
|
4306
|
+
case "deflate":
|
|
4307
|
+
return deflateAsync(buf);
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
function mergeVary(meta, value) {
|
|
4311
|
+
const existing = meta.headers["Vary"] ?? meta.headers["vary"];
|
|
4312
|
+
if (!existing) {
|
|
4313
|
+
meta.headers["Vary"] = value;
|
|
4314
|
+
return;
|
|
4315
|
+
}
|
|
4316
|
+
if (!existing.toLowerCase().includes(value.toLowerCase())) {
|
|
4317
|
+
const key = meta.headers["Vary"] !== void 0 ? "Vary" : "vary";
|
|
4318
|
+
meta.headers[key] = `${existing}, ${value}`;
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
function compression(options = {}) {
|
|
4322
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
4323
|
+
return async (ctx, next) => {
|
|
4324
|
+
const meta = ctx.meta;
|
|
4325
|
+
const response = await next();
|
|
4326
|
+
if (!response) return response;
|
|
4327
|
+
mergeVary(meta, "Accept-Encoding");
|
|
4328
|
+
const acceptEncoding = ctx.request.headers.get("accept-encoding") ?? "";
|
|
4329
|
+
const encoding = selectEncoding(parseAcceptEncoding(acceptEncoding));
|
|
4330
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
4331
|
+
if (!encoding || !isCompressible(contentType) || response.headers.has("content-encoding") || (response.headers.get("cache-control") ?? "").includes("no-transform") || response.status === 204 || response.status === 304 || response.body === null) {
|
|
4332
|
+
return response;
|
|
4333
|
+
}
|
|
4334
|
+
const deferredHeaders = consumePendingMetaHeaders(response);
|
|
4335
|
+
const bodyText = await response.text();
|
|
4336
|
+
if (bodyText.length < threshold) {
|
|
4337
|
+
const headers2 = new Headers(response.headers);
|
|
4338
|
+
for (const [key, value] of Object.entries(deferredHeaders ?? {})) {
|
|
4339
|
+
headers2.set(key, value);
|
|
4340
|
+
}
|
|
4341
|
+
return new Response(bodyText, {
|
|
4342
|
+
status: response.status,
|
|
4343
|
+
statusText: response.statusText,
|
|
4344
|
+
headers: headers2
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
const compressed = await compressBody(encoding, bodyText);
|
|
4348
|
+
const headers = new Headers(response.headers);
|
|
4349
|
+
for (const [key, value] of Object.entries(deferredHeaders ?? {})) {
|
|
4350
|
+
headers.set(key, value);
|
|
4351
|
+
}
|
|
4352
|
+
headers.set("Content-Encoding", encoding);
|
|
4353
|
+
headers.delete("Content-Length");
|
|
4354
|
+
return new Response(compressed, {
|
|
4355
|
+
status: response.status,
|
|
4356
|
+
statusText: response.statusText,
|
|
4357
|
+
headers
|
|
4358
|
+
});
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
// src/middleware/etag.ts
|
|
4363
|
+
import { createHash } from "crypto";
|
|
4364
|
+
var DEFAULTS2 = { weak: true };
|
|
4365
|
+
function computeEtag(body, weak) {
|
|
4366
|
+
const hash = createHash("sha1").update(body).digest("base64");
|
|
4367
|
+
return weak ? `W/"${hash}"` : `"${hash}"`;
|
|
4368
|
+
}
|
|
4369
|
+
function ifNoneMatchMatches(ifNoneMatch, etag2, weak) {
|
|
4370
|
+
const normalize = (tag) => tag.trim().replace(/^W\//i, "");
|
|
4371
|
+
if (weak) {
|
|
4372
|
+
const target = normalize(etag2);
|
|
4373
|
+
return ifNoneMatch.split(",").some((tag) => {
|
|
4374
|
+
const trimmed = tag.trim();
|
|
4375
|
+
return trimmed === "*" || normalize(trimmed) === target;
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
return ifNoneMatch.split(",").some((tag) => tag.trim() === etag2 || tag.trim() === "*");
|
|
4379
|
+
}
|
|
4380
|
+
function etag(options = {}) {
|
|
4381
|
+
const opts = { ...DEFAULTS2, ...options };
|
|
4382
|
+
return async (ctx, next) => {
|
|
4383
|
+
const meta = ctx.meta;
|
|
4384
|
+
const response = await next();
|
|
4385
|
+
if (!response) return response;
|
|
4386
|
+
const method = ctx.request.method.toUpperCase();
|
|
4387
|
+
if (method !== "GET" && method !== "HEAD") return response;
|
|
4388
|
+
if (response.status < 200 || response.status >= 300) return response;
|
|
4389
|
+
if (meta.headers["etag"] !== void 0 || response.headers.has("etag")) {
|
|
4390
|
+
return response;
|
|
4391
|
+
}
|
|
4392
|
+
if (response.body === null || (response.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
4393
|
+
return response;
|
|
4394
|
+
}
|
|
4395
|
+
const bodyText = await response.text();
|
|
4396
|
+
const etagValue = computeEtag(bodyText, opts.weak);
|
|
4397
|
+
const ifNoneMatch = ctx.request.headers.get("if-none-match");
|
|
4398
|
+
if (ifNoneMatch && ifNoneMatchMatches(ifNoneMatch, etagValue, opts.weak)) {
|
|
4399
|
+
return new Response(null, {
|
|
4400
|
+
status: 304,
|
|
4401
|
+
statusText: response.statusText,
|
|
4402
|
+
headers: { ETag: etagValue }
|
|
4403
|
+
});
|
|
4404
|
+
}
|
|
4405
|
+
meta.headers["etag"] = etagValue;
|
|
4406
|
+
const headers = new Headers(response.headers);
|
|
4407
|
+
const deferredHeaders = consumePendingMetaHeaders(response);
|
|
4408
|
+
for (const [key, value] of Object.entries(deferredHeaders ?? {})) {
|
|
4409
|
+
headers.set(key, value);
|
|
4410
|
+
}
|
|
4411
|
+
return new Response(bodyText, {
|
|
4412
|
+
status: response.status,
|
|
4413
|
+
statusText: response.statusText,
|
|
4414
|
+
headers
|
|
4415
|
+
});
|
|
4416
|
+
};
|
|
4417
|
+
}
|
|
4418
|
+
|
|
3778
4419
|
// src/server/handleWsUpgrade.ts
|
|
3779
|
-
import
|
|
4420
|
+
import fs13 from "fs";
|
|
3780
4421
|
import { WebSocketServer, WebSocket } from "ws";
|
|
3781
|
-
import
|
|
4422
|
+
import path14 from "path";
|
|
3782
4423
|
|
|
3783
4424
|
// src/server/serverUtils.ts
|
|
3784
4425
|
function nodeHttpToWebHeaders(req) {
|
|
@@ -3809,8 +4450,10 @@ function buildErrorResponse(err, config) {
|
|
|
3809
4450
|
|
|
3810
4451
|
// src/middleware/loadMiddlewares.ts
|
|
3811
4452
|
var middlewareCache = /* @__PURE__ */ new Map();
|
|
4453
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
3812
4454
|
function invalidateMiddlewareCache() {
|
|
3813
4455
|
middlewareCache.clear();
|
|
4456
|
+
inFlight.clear();
|
|
3814
4457
|
}
|
|
3815
4458
|
function getCachedMiddlewares(absPath) {
|
|
3816
4459
|
return middlewareCache.get(absPath);
|
|
@@ -3863,8 +4506,15 @@ async function loadMergedMiddlewares(middlewarePaths) {
|
|
|
3863
4506
|
for (const absMwPath of middlewarePaths) {
|
|
3864
4507
|
let bundle = getCachedMiddlewares(absMwPath);
|
|
3865
4508
|
if (bundle === void 0) {
|
|
3866
|
-
|
|
3867
|
-
|
|
4509
|
+
let loading = inFlight.get(absMwPath);
|
|
4510
|
+
if (!loading) {
|
|
4511
|
+
loading = loadMiddlewaresFile(absMwPath).then((result) => {
|
|
4512
|
+
setCachedMiddlewares(absMwPath, result);
|
|
4513
|
+
return result;
|
|
4514
|
+
});
|
|
4515
|
+
inFlight.set(absMwPath, loading);
|
|
4516
|
+
}
|
|
4517
|
+
bundle = await loading;
|
|
3868
4518
|
}
|
|
3869
4519
|
mergedMiddlewares.push(...bundle.middlewares);
|
|
3870
4520
|
for (const [name, injector] of Object.entries(bundle.injectors)) {
|
|
@@ -3904,7 +4554,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
3904
4554
|
const dist = getDevDist();
|
|
3905
4555
|
if (dist) {
|
|
3906
4556
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3907
|
-
if (sourcePath &&
|
|
4557
|
+
if (sourcePath && fs13.existsSync(sourcePath)) {
|
|
3908
4558
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3909
4559
|
}
|
|
3910
4560
|
}
|
|
@@ -3927,9 +4577,9 @@ function bindEvents(rawSocket, handlers) {
|
|
|
3927
4577
|
}
|
|
3928
4578
|
}
|
|
3929
4579
|
if (handlers.onMessage) {
|
|
3930
|
-
rawSocket.on("message", (data) => {
|
|
3931
|
-
const buf =
|
|
3932
|
-
handlers.onMessage(ws, buf.toString("utf8"));
|
|
4580
|
+
rawSocket.on("message", (data, isBinary) => {
|
|
4581
|
+
const buf = toWsBuffer(data);
|
|
4582
|
+
handlers.onMessage(ws, isBinary ? buf : buf.toString("utf8"));
|
|
3933
4583
|
});
|
|
3934
4584
|
}
|
|
3935
4585
|
if (handlers.onClose) {
|
|
@@ -3943,12 +4593,22 @@ function bindEvents(rawSocket, handlers) {
|
|
|
3943
4593
|
});
|
|
3944
4594
|
}
|
|
3945
4595
|
}
|
|
4596
|
+
function toWsBuffer(data) {
|
|
4597
|
+
if (Buffer.isBuffer(data)) return data;
|
|
4598
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
4599
|
+
return Buffer.from(data);
|
|
4600
|
+
}
|
|
3946
4601
|
async function sendResponseToSocket(socket, response) {
|
|
3947
4602
|
const body = await response.text().catch(() => "");
|
|
3948
4603
|
const statusLine = `HTTP/1.1 ${response.status} ${response.statusText || ""}\r
|
|
3949
4604
|
`;
|
|
3950
4605
|
const headerLines = [];
|
|
3951
4606
|
let hasContentLength = false;
|
|
4607
|
+
const deferredHeaders = consumePendingMetaHeaders(response);
|
|
4608
|
+
for (const [key, value] of Object.entries(deferredHeaders ?? {})) {
|
|
4609
|
+
if (key.toLowerCase() === "content-length") hasContentLength = true;
|
|
4610
|
+
headerLines.push(`${key}: ${value}`);
|
|
4611
|
+
}
|
|
3952
4612
|
for (const [key, value] of response.headers) {
|
|
3953
4613
|
if (key.toLowerCase() === "content-length") {
|
|
3954
4614
|
hasContentLength = true;
|
|
@@ -3962,9 +4622,33 @@ async function sendResponseToSocket(socket, response) {
|
|
|
3962
4622
|
socket.destroy();
|
|
3963
4623
|
}
|
|
3964
4624
|
function attachWebSocket(options) {
|
|
3965
|
-
const {
|
|
4625
|
+
const {
|
|
4626
|
+
server,
|
|
4627
|
+
routesRef,
|
|
4628
|
+
rootDir,
|
|
4629
|
+
config,
|
|
4630
|
+
globalMiddlewares,
|
|
4631
|
+
trustedProxy = false,
|
|
4632
|
+
registries
|
|
4633
|
+
} = options;
|
|
3966
4634
|
const wss = new WebSocketServer({ noServer: true });
|
|
3967
4635
|
server.on("upgrade", async (req, socket, head) => {
|
|
4636
|
+
try {
|
|
4637
|
+
await handleUpgradeRequest(req, socket, head);
|
|
4638
|
+
} catch (err) {
|
|
4639
|
+
console.error("[faapi] WS upgrade \u5904\u7406\u5931\u8D25:", err);
|
|
4640
|
+
if (!socket.destroyed) {
|
|
4641
|
+
try {
|
|
4642
|
+
if (!socket.writableEnded) {
|
|
4643
|
+
socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
|
4644
|
+
}
|
|
4645
|
+
} catch {
|
|
4646
|
+
}
|
|
4647
|
+
socket.destroy();
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
});
|
|
4651
|
+
async function handleUpgradeRequest(req, socket, head) {
|
|
3968
4652
|
const currentWsRoutes = routesRef.wsCurrent;
|
|
3969
4653
|
const pathname = getPathname(req);
|
|
3970
4654
|
const match = matchWsRoute(currentWsRoutes, pathname);
|
|
@@ -3978,13 +4662,13 @@ function attachWebSocket(options) {
|
|
|
3978
4662
|
const host = req.headers.host ?? "localhost";
|
|
3979
4663
|
const url = `http://${host}${req.url ?? "/"}`;
|
|
3980
4664
|
const request = new Request(url, { method: "GET", headers });
|
|
3981
|
-
const ctx = createContext(request, params, config, getClientIp(req, trustedProxy));
|
|
4665
|
+
const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
|
|
3982
4666
|
const meta = ctx.meta;
|
|
3983
4667
|
let upgraded = false;
|
|
3984
4668
|
const finalHandler = async () => {
|
|
3985
4669
|
let handlers;
|
|
3986
4670
|
try {
|
|
3987
|
-
const absoluteFilePath =
|
|
4671
|
+
const absoluteFilePath = path14.resolve(rootDir, route.filePath);
|
|
3988
4672
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
3989
4673
|
} catch (err) {
|
|
3990
4674
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -4008,6 +4692,7 @@ function attachWebSocket(options) {
|
|
|
4008
4692
|
let response;
|
|
4009
4693
|
try {
|
|
4010
4694
|
if (route.middlewares === void 0 && route.middlewarePaths) {
|
|
4695
|
+
await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);
|
|
4011
4696
|
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
4012
4697
|
if (bundle) {
|
|
4013
4698
|
route.middlewares = bundle.middlewares;
|
|
@@ -4033,14 +4718,14 @@ function attachWebSocket(options) {
|
|
|
4033
4718
|
return;
|
|
4034
4719
|
}
|
|
4035
4720
|
await sendResponseToSocket(socket, mergeMeta(response, meta));
|
|
4036
|
-
}
|
|
4721
|
+
}
|
|
4037
4722
|
return wss;
|
|
4038
4723
|
}
|
|
4039
4724
|
|
|
4040
4725
|
// src/server/createServer.ts
|
|
4041
4726
|
init_generateSchemaFiles();
|
|
4042
4727
|
var DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;
|
|
4043
|
-
function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
4728
|
+
function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT, requestSignal) {
|
|
4044
4729
|
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
4045
4730
|
const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
|
|
4046
4731
|
const host = req.headers.host ?? "localhost";
|
|
@@ -4048,7 +4733,10 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
4048
4733
|
const headers = nodeHttpToWebHeaders(req);
|
|
4049
4734
|
const method = req.method ?? "GET";
|
|
4050
4735
|
if (method === "GET" || method === "HEAD") {
|
|
4051
|
-
return {
|
|
4736
|
+
return {
|
|
4737
|
+
request: new Request(url.toString(), { method, headers, signal: requestSignal }),
|
|
4738
|
+
url
|
|
4739
|
+
};
|
|
4052
4740
|
}
|
|
4053
4741
|
const contentLength = req.headers["content-length"];
|
|
4054
4742
|
if (contentLength !== void 0) {
|
|
@@ -4064,7 +4752,8 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
4064
4752
|
method,
|
|
4065
4753
|
headers,
|
|
4066
4754
|
body: limitedStream,
|
|
4067
|
-
duplex: "half"
|
|
4755
|
+
duplex: "half",
|
|
4756
|
+
signal: requestSignal
|
|
4068
4757
|
}),
|
|
4069
4758
|
url
|
|
4070
4759
|
};
|
|
@@ -4131,6 +4820,9 @@ function createServer(options) {
|
|
|
4131
4820
|
middlewares: globalMiddlewares,
|
|
4132
4821
|
injectors: globalInjectors,
|
|
4133
4822
|
helmet: helmetOption,
|
|
4823
|
+
compression: compressionOption,
|
|
4824
|
+
etag: etagOption,
|
|
4825
|
+
registries,
|
|
4134
4826
|
logger: loggerOption,
|
|
4135
4827
|
bodyLimit = DEFAULT_BODY_LIMIT,
|
|
4136
4828
|
http2: http2Option,
|
|
@@ -4138,6 +4830,10 @@ function createServer(options) {
|
|
|
4138
4830
|
} = options;
|
|
4139
4831
|
const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
|
|
4140
4832
|
const configMiddlewares = [];
|
|
4833
|
+
if (compressionOption) {
|
|
4834
|
+
const compOpts = typeof compressionOption === "object" ? compressionOption : {};
|
|
4835
|
+
configMiddlewares.push(compression(compOpts));
|
|
4836
|
+
}
|
|
4141
4837
|
const corsMiddleware = corsOption === false ? null : corsOption === true || corsOption === void 0 ? cors() : cors(corsOption);
|
|
4142
4838
|
if (corsMiddleware) configMiddlewares.push(corsMiddleware);
|
|
4143
4839
|
if (helmetOption) {
|
|
@@ -4146,6 +4842,10 @@ function createServer(options) {
|
|
|
4146
4842
|
}
|
|
4147
4843
|
const loggerMiddlewareInst = loggerOption === false ? null : loggerOption === true || loggerOption === void 0 ? logger() : logger(loggerOption);
|
|
4148
4844
|
if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);
|
|
4845
|
+
if (etagOption) {
|
|
4846
|
+
const etagOpts = typeof etagOption === "object" ? etagOption : {};
|
|
4847
|
+
configMiddlewares.push(etag(etagOpts));
|
|
4848
|
+
}
|
|
4149
4849
|
const outerMiddlewares = [...configMiddlewares];
|
|
4150
4850
|
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
4151
4851
|
outerMiddlewares.push(...globalMiddlewares);
|
|
@@ -4174,22 +4874,36 @@ function createServer(options) {
|
|
|
4174
4874
|
config,
|
|
4175
4875
|
globalInjectors,
|
|
4176
4876
|
bodyLimit,
|
|
4177
|
-
trustedProxy
|
|
4877
|
+
trustedProxy,
|
|
4878
|
+
registries
|
|
4178
4879
|
).catch(() => {
|
|
4179
4880
|
res.statusCode = 500;
|
|
4180
4881
|
res.end();
|
|
4181
4882
|
});
|
|
4182
4883
|
});
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4884
|
+
attachWebSocket({
|
|
4885
|
+
server,
|
|
4886
|
+
routesRef,
|
|
4887
|
+
rootDir,
|
|
4888
|
+
config,
|
|
4889
|
+
globalMiddlewares,
|
|
4890
|
+
trustedProxy,
|
|
4891
|
+
registries
|
|
4892
|
+
});
|
|
4186
4893
|
return { server, routesRef };
|
|
4187
4894
|
}
|
|
4188
|
-
function prepareRequest(req, config, bodyLimit, trustedProxy) {
|
|
4189
|
-
const { request, url } = toWebRequest(req, bodyLimit);
|
|
4895
|
+
function prepareRequest(req, config, bodyLimit, trustedProxy, registries, requestSignal) {
|
|
4896
|
+
const { request, url } = toWebRequest(req, bodyLimit, requestSignal);
|
|
4190
4897
|
const method = request.method.toUpperCase();
|
|
4191
4898
|
const urlPath = url.pathname;
|
|
4192
|
-
const ctx = createContextFromUrl(
|
|
4899
|
+
const ctx = createContextFromUrl(
|
|
4900
|
+
request,
|
|
4901
|
+
url,
|
|
4902
|
+
{},
|
|
4903
|
+
config,
|
|
4904
|
+
getClientIp(req, trustedProxy),
|
|
4905
|
+
registries
|
|
4906
|
+
);
|
|
4193
4907
|
const meta = ctx.meta;
|
|
4194
4908
|
return { request, url, ctx, meta, method, urlPath };
|
|
4195
4909
|
}
|
|
@@ -4202,17 +4916,28 @@ function resolveRouteOrThrow(routes, method, urlPath) {
|
|
|
4202
4916
|
}
|
|
4203
4917
|
throw new RouteNotFoundError(urlPath);
|
|
4204
4918
|
}
|
|
4919
|
+
var routePathCache = /* @__PURE__ */ new WeakMap();
|
|
4920
|
+
function getRoutePaths(route, rootDir, dist) {
|
|
4921
|
+
let cached = routePathCache.get(route);
|
|
4922
|
+
if (!cached) {
|
|
4923
|
+
cached = {
|
|
4924
|
+
absFilePath: path15.resolve(rootDir, route.filePath),
|
|
4925
|
+
schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
|
|
4926
|
+
};
|
|
4927
|
+
routePathCache.set(route, cached);
|
|
4928
|
+
}
|
|
4929
|
+
return cached;
|
|
4930
|
+
}
|
|
4205
4931
|
function createRoutePipeline(opts) {
|
|
4206
4932
|
const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
4207
4933
|
return async () => {
|
|
4208
4934
|
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
4209
4935
|
ctx.params = match.params;
|
|
4210
4936
|
const { route } = match;
|
|
4211
|
-
const
|
|
4212
|
-
const routeModule = await loadRouteModule(
|
|
4937
|
+
const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);
|
|
4938
|
+
const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);
|
|
4213
4939
|
const input = await resolveInputFromUrl(route.method, request, url);
|
|
4214
4940
|
const inputType = getInputTypeForMethod(route.method);
|
|
4215
|
-
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
4216
4941
|
if (isDevOnDemandEnabled()) {
|
|
4217
4942
|
const devDist = getDevDist();
|
|
4218
4943
|
if (devDist) {
|
|
@@ -4223,8 +4948,9 @@ function createRoutePipeline(opts) {
|
|
|
4223
4948
|
if (!result.valid) {
|
|
4224
4949
|
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", result.issues);
|
|
4225
4950
|
}
|
|
4226
|
-
const body = hasBody(route.method) ? result.data : void 0;
|
|
4951
|
+
const body = inputType === "query" && hasBody(route.method) ? await resolveBodyForQueryMethod(request) : hasBody(route.method) ? result.data : void 0;
|
|
4227
4952
|
if (route.middlewares === void 0 && route.injectors === void 0 && route.middlewarePaths) {
|
|
4953
|
+
await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);
|
|
4228
4954
|
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
4229
4955
|
if (bundle) {
|
|
4230
4956
|
route.middlewares = bundle.middlewares;
|
|
@@ -4250,11 +4976,22 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
|
4250
4976
|
}
|
|
4251
4977
|
}
|
|
4252
4978
|
}
|
|
4253
|
-
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy) {
|
|
4979
|
+
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries) {
|
|
4254
4980
|
let meta = { headers: {}, setCookies: [] };
|
|
4255
4981
|
let ctx;
|
|
4982
|
+
const abortController = new AbortController();
|
|
4983
|
+
res.on("close", () => {
|
|
4984
|
+
if (!res.writableEnded) abortController.abort();
|
|
4985
|
+
});
|
|
4256
4986
|
try {
|
|
4257
|
-
const prepared = prepareRequest(
|
|
4987
|
+
const prepared = prepareRequest(
|
|
4988
|
+
req,
|
|
4989
|
+
config,
|
|
4990
|
+
bodyLimit,
|
|
4991
|
+
trustedProxy,
|
|
4992
|
+
registries,
|
|
4993
|
+
abortController.signal
|
|
4994
|
+
);
|
|
4258
4995
|
ctx = prepared.ctx;
|
|
4259
4996
|
meta = prepared.meta;
|
|
4260
4997
|
const { request, url, method, urlPath } = prepared;
|
|
@@ -4272,6 +5009,7 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
|
|
|
4272
5009
|
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
4273
5010
|
await sendSuccessResponse(response, res);
|
|
4274
5011
|
} catch (err) {
|
|
5012
|
+
if (res.destroyed || res.writableEnded) return;
|
|
4275
5013
|
await sendErrorResponse(err, meta, res, onError, ctx);
|
|
4276
5014
|
}
|
|
4277
5015
|
}
|
|
@@ -4305,8 +5043,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
4305
5043
|
}
|
|
4306
5044
|
|
|
4307
5045
|
// src/cli/generateRoutes.ts
|
|
4308
|
-
import
|
|
4309
|
-
import
|
|
5046
|
+
import fs14 from "fs";
|
|
5047
|
+
import path16 from "path";
|
|
4310
5048
|
async function hydrateRoutes(manifest) {
|
|
4311
5049
|
const hydrateRoute = (serialized) => ({
|
|
4312
5050
|
method: serialized.method,
|
|
@@ -4331,11 +5069,10 @@ async function hydrateRoutes(manifest) {
|
|
|
4331
5069
|
}
|
|
4332
5070
|
|
|
4333
5071
|
// src/cli/generateAgentArtifacts.ts
|
|
4334
|
-
import
|
|
4335
|
-
import fs14 from "fs/promises";
|
|
5072
|
+
import path17 from "path";
|
|
4336
5073
|
|
|
4337
5074
|
// src/ast/extractAgentMetadata.ts
|
|
4338
|
-
import
|
|
5075
|
+
import ts9 from "typescript";
|
|
4339
5076
|
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
4340
5077
|
const sourceFile = program.getSourceFile(filePath);
|
|
4341
5078
|
if (!sourceFile) return null;
|
|
@@ -4351,9 +5088,9 @@ function extractAgentMetadata(program, filePath, pathMeta) {
|
|
|
4351
5088
|
jsDocOwner = runNode;
|
|
4352
5089
|
}
|
|
4353
5090
|
}
|
|
4354
|
-
const jsDoc = jsDocOwner ?
|
|
4355
|
-
const description =
|
|
4356
|
-
const agentNameOverride =
|
|
5091
|
+
const jsDoc = jsDocOwner ? getJSDocFromNode(jsDocOwner) : void 0;
|
|
5092
|
+
const description = extractDescription(jsDoc);
|
|
5093
|
+
const agentNameOverride = extractJSDocTagValue(jsDoc, "agent");
|
|
4357
5094
|
let systemPrompt;
|
|
4358
5095
|
let tools;
|
|
4359
5096
|
let agents;
|
|
@@ -4381,22 +5118,22 @@ function extractAgentMetadata(program, filePath, pathMeta) {
|
|
|
4381
5118
|
}
|
|
4382
5119
|
function findConfigExport(sourceFile) {
|
|
4383
5120
|
let result = null;
|
|
4384
|
-
|
|
5121
|
+
ts9.forEachChild(sourceFile, (node) => {
|
|
4385
5122
|
if (result) return;
|
|
4386
|
-
if (
|
|
5123
|
+
if (ts9.isVariableStatement(node) && hasExportModifier(node)) {
|
|
4387
5124
|
for (const decl of node.declarationList.declarations) {
|
|
4388
5125
|
if (result) break;
|
|
4389
|
-
const nameText =
|
|
5126
|
+
const nameText = ts9.isIdentifier(decl.name) ? decl.name.text : "";
|
|
4390
5127
|
if (nameText !== "config" || !decl.initializer) continue;
|
|
4391
|
-
if (
|
|
5128
|
+
if (ts9.isObjectLiteralExpression(decl.initializer)) {
|
|
4392
5129
|
result = { jsDocOwner: node, objectLiteral: decl.initializer };
|
|
4393
|
-
} else if (
|
|
5130
|
+
} else if (ts9.isArrowFunction(decl.initializer)) {
|
|
4394
5131
|
const returnObj = getReturnObjectLiteral(decl.initializer);
|
|
4395
5132
|
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
4396
5133
|
}
|
|
4397
5134
|
}
|
|
4398
5135
|
}
|
|
4399
|
-
if (
|
|
5136
|
+
if (ts9.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
|
|
4400
5137
|
const returnObj = getReturnObjectLiteral(node);
|
|
4401
5138
|
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
4402
5139
|
}
|
|
@@ -4405,18 +5142,18 @@ function findConfigExport(sourceFile) {
|
|
|
4405
5142
|
}
|
|
4406
5143
|
function findRunExport(sourceFile) {
|
|
4407
5144
|
let result = null;
|
|
4408
|
-
|
|
5145
|
+
ts9.forEachChild(sourceFile, (node) => {
|
|
4409
5146
|
if (result) return;
|
|
4410
|
-
if (
|
|
5147
|
+
if (ts9.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "run") {
|
|
4411
5148
|
result = node;
|
|
4412
5149
|
return;
|
|
4413
5150
|
}
|
|
4414
|
-
if (
|
|
5151
|
+
if (ts9.isVariableStatement(node) && hasExportModifier(node)) {
|
|
4415
5152
|
for (const decl of node.declarationList.declarations) {
|
|
4416
5153
|
if (result) break;
|
|
4417
|
-
const nameText =
|
|
5154
|
+
const nameText = ts9.isIdentifier(decl.name) ? decl.name.text : "";
|
|
4418
5155
|
if (nameText !== "run" || !decl.initializer) continue;
|
|
4419
|
-
if (
|
|
5156
|
+
if (ts9.isArrowFunction(decl.initializer) || ts9.isFunctionExpression(decl.initializer)) {
|
|
4420
5157
|
result = node;
|
|
4421
5158
|
}
|
|
4422
5159
|
}
|
|
@@ -4427,52 +5164,22 @@ function findRunExport(sourceFile) {
|
|
|
4427
5164
|
function getReturnObjectLiteral(fn) {
|
|
4428
5165
|
const body = fn.body;
|
|
4429
5166
|
if (!body) return null;
|
|
4430
|
-
if (
|
|
5167
|
+
if (ts9.isObjectLiteralExpression(body)) {
|
|
4431
5168
|
return body;
|
|
4432
5169
|
}
|
|
4433
|
-
if (
|
|
5170
|
+
if (ts9.isBlock(body)) {
|
|
4434
5171
|
for (const stmt of body.statements) {
|
|
4435
|
-
if (
|
|
5172
|
+
if (ts9.isReturnStatement(stmt) && stmt.expression && ts9.isObjectLiteralExpression(stmt.expression)) {
|
|
4436
5173
|
return stmt.expression;
|
|
4437
5174
|
}
|
|
4438
5175
|
}
|
|
4439
5176
|
}
|
|
4440
5177
|
return null;
|
|
4441
5178
|
}
|
|
4442
|
-
function hasExportModifier2(node) {
|
|
4443
|
-
if (!ts8.canHaveModifiers(node)) return false;
|
|
4444
|
-
const modifiers = ts8.getModifiers(node);
|
|
4445
|
-
return !!modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword);
|
|
4446
|
-
}
|
|
4447
|
-
function getJSDocFromNode2(node) {
|
|
4448
|
-
const apiDocs = ts8.getJSDocCommentsAndTags(node).filter((entry) => ts8.isJSDoc(entry));
|
|
4449
|
-
if (apiDocs.length > 0) return apiDocs[0];
|
|
4450
|
-
const directDocs = node.jsDoc;
|
|
4451
|
-
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
4452
|
-
return void 0;
|
|
4453
|
-
}
|
|
4454
|
-
function extractDescription2(jsDoc) {
|
|
4455
|
-
if (!jsDoc) return void 0;
|
|
4456
|
-
if (typeof jsDoc.comment !== "string") return void 0;
|
|
4457
|
-
const trimmed = jsDoc.comment.trim();
|
|
4458
|
-
return trimmed || void 0;
|
|
4459
|
-
}
|
|
4460
|
-
function extractAgentTagValue(jsDoc) {
|
|
4461
|
-
if (!jsDoc || !jsDoc.tags) return void 0;
|
|
4462
|
-
for (const tag of jsDoc.tags) {
|
|
4463
|
-
if (tag.tagName.text !== "agent") continue;
|
|
4464
|
-
if (typeof tag.comment !== "string") return void 0;
|
|
4465
|
-
const text = tag.comment.trim();
|
|
4466
|
-
if (!text) return void 0;
|
|
4467
|
-
const cleaned = text.replace(/^\{|\}$/g, "").trim();
|
|
4468
|
-
return cleaned || void 0;
|
|
4469
|
-
}
|
|
4470
|
-
return void 0;
|
|
4471
|
-
}
|
|
4472
5179
|
function extractConfigFields(objLit) {
|
|
4473
5180
|
const result = {};
|
|
4474
5181
|
for (const prop of objLit.properties) {
|
|
4475
|
-
if (!
|
|
5182
|
+
if (!ts9.isPropertyAssignment(prop)) continue;
|
|
4476
5183
|
const propName = getPropertyName(prop.name);
|
|
4477
5184
|
if (!propName) continue;
|
|
4478
5185
|
switch (propName) {
|
|
@@ -4496,26 +5203,26 @@ function extractConfigFields(objLit) {
|
|
|
4496
5203
|
return result;
|
|
4497
5204
|
}
|
|
4498
5205
|
function getPropertyName(name) {
|
|
4499
|
-
if (
|
|
4500
|
-
if (
|
|
5206
|
+
if (ts9.isIdentifier(name)) return name.text;
|
|
5207
|
+
if (ts9.isStringLiteral(name)) return name.text;
|
|
4501
5208
|
return null;
|
|
4502
5209
|
}
|
|
4503
5210
|
function extractStringValue(expr) {
|
|
4504
|
-
if (
|
|
5211
|
+
if (ts9.isStringLiteral(expr)) return expr.text;
|
|
4505
5212
|
return void 0;
|
|
4506
5213
|
}
|
|
4507
5214
|
function extractNumberValue(expr) {
|
|
4508
|
-
if (
|
|
5215
|
+
if (ts9.isNumericLiteral(expr)) {
|
|
4509
5216
|
const num = Number(expr.text);
|
|
4510
5217
|
return Number.isNaN(num) ? void 0 : num;
|
|
4511
5218
|
}
|
|
4512
5219
|
return void 0;
|
|
4513
5220
|
}
|
|
4514
5221
|
function extractStringArrayValue(expr) {
|
|
4515
|
-
if (!
|
|
5222
|
+
if (!ts9.isArrayLiteralExpression(expr)) return void 0;
|
|
4516
5223
|
const values = [];
|
|
4517
5224
|
for (const element of expr.elements) {
|
|
4518
|
-
if (!
|
|
5225
|
+
if (!ts9.isStringLiteral(element)) return void 0;
|
|
4519
5226
|
values.push(element.text);
|
|
4520
5227
|
}
|
|
4521
5228
|
return values;
|
|
@@ -4523,15 +5230,8 @@ function extractStringArrayValue(expr) {
|
|
|
4523
5230
|
|
|
4524
5231
|
// src/cli/generateAgentArtifacts.ts
|
|
4525
5232
|
init_createProgram();
|
|
5233
|
+
init_atomicWrite();
|
|
4526
5234
|
var AGENTS_FILE = "faapi-agents.js";
|
|
4527
|
-
function toProdFilePath2(filePath, dist) {
|
|
4528
|
-
let rel = filePath.replace(/\\/g, "/");
|
|
4529
|
-
if (rel.startsWith("src/")) {
|
|
4530
|
-
rel = rel.slice(4);
|
|
4531
|
-
}
|
|
4532
|
-
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
4533
|
-
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
4534
|
-
}
|
|
4535
5235
|
function serializeAgents(agents, dist = "dist") {
|
|
4536
5236
|
return agents.map((a) => ({
|
|
4537
5237
|
name: a.name,
|
|
@@ -4542,16 +5242,14 @@ function serializeAgents(agents, dist = "dist") {
|
|
|
4542
5242
|
agents: a.agents,
|
|
4543
5243
|
model: a.model,
|
|
4544
5244
|
maxTurns: a.maxTurns,
|
|
4545
|
-
filePath:
|
|
5245
|
+
filePath: toProdFilePath(a.filePath, dist)
|
|
4546
5246
|
}));
|
|
4547
5247
|
}
|
|
4548
5248
|
async function writeAgentsModule(manifest, outputPath) {
|
|
4549
|
-
const dir = path14.dirname(outputPath);
|
|
4550
|
-
await fs14.mkdir(dir, { recursive: true });
|
|
4551
5249
|
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
4552
5250
|
export const agents = ${JSON.stringify(manifest, null, 2)};
|
|
4553
5251
|
`;
|
|
4554
|
-
await
|
|
5252
|
+
await atomicWriteFile(outputPath, content);
|
|
4555
5253
|
}
|
|
4556
5254
|
function hydrateAgents(manifest) {
|
|
4557
5255
|
return manifest.map((a) => ({
|
|
@@ -4568,9 +5266,9 @@ function hydrateAgents(manifest) {
|
|
|
4568
5266
|
}
|
|
4569
5267
|
async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
4570
5268
|
const metadata = [];
|
|
4571
|
-
const programByFile = createPrograms(agents.map((m) =>
|
|
5269
|
+
const programByFile = createPrograms(agents.map((m) => path17.resolve(rootDir, m.filePath)));
|
|
4572
5270
|
for (const manifest of agents) {
|
|
4573
|
-
const absPath =
|
|
5271
|
+
const absPath = path17.resolve(rootDir, manifest.filePath);
|
|
4574
5272
|
const program = programByFile.get(absPath);
|
|
4575
5273
|
const result = extractAgentMetadata(program, absPath, {
|
|
4576
5274
|
name: manifest.name,
|
|
@@ -4582,17 +5280,20 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
|
4582
5280
|
}
|
|
4583
5281
|
}
|
|
4584
5282
|
const serialized = serializeAgents(metadata, dist);
|
|
4585
|
-
const agentsPath =
|
|
5283
|
+
const agentsPath = path17.resolve(rootDir, dist, AGENTS_FILE);
|
|
4586
5284
|
await writeAgentsModule(serialized, agentsPath);
|
|
4587
5285
|
return metadata;
|
|
4588
5286
|
}
|
|
4589
5287
|
|
|
4590
5288
|
// src/cli/loadPlugins.ts
|
|
4591
|
-
|
|
5289
|
+
import path18 from "path";
|
|
5290
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5291
|
+
async function loadPlugins(declarations, ctx, rootDir) {
|
|
4592
5292
|
const handlerWrappers = [];
|
|
4593
5293
|
const upgradeWrappers = [];
|
|
5294
|
+
const failures = [];
|
|
4594
5295
|
if (!declarations || declarations.length === 0) {
|
|
4595
|
-
return { handlerWrappers, upgradeWrappers };
|
|
5296
|
+
return { handlerWrappers, upgradeWrappers, failures };
|
|
4596
5297
|
}
|
|
4597
5298
|
const fullCtx = {
|
|
4598
5299
|
...ctx,
|
|
@@ -4605,7 +5306,18 @@ async function loadPlugins(declarations, ctx) {
|
|
|
4605
5306
|
};
|
|
4606
5307
|
const loaded = /* @__PURE__ */ new Set();
|
|
4607
5308
|
for (const decl of declarations) {
|
|
4608
|
-
|
|
5309
|
+
let specifier;
|
|
5310
|
+
let options;
|
|
5311
|
+
let enable;
|
|
5312
|
+
try {
|
|
5313
|
+
({ specifier, options, enable } = resolveDeclaration(decl));
|
|
5314
|
+
} catch (err) {
|
|
5315
|
+
failures.push({
|
|
5316
|
+
specifier: JSON.stringify(decl),
|
|
5317
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
5318
|
+
});
|
|
5319
|
+
continue;
|
|
5320
|
+
}
|
|
4609
5321
|
if (enable === false) continue;
|
|
4610
5322
|
if (loaded.has(specifier)) {
|
|
4611
5323
|
console.warn(`! Plugin already loaded: ${specifier}, skipping`);
|
|
@@ -4613,21 +5325,38 @@ async function loadPlugins(declarations, ctx) {
|
|
|
4613
5325
|
}
|
|
4614
5326
|
loaded.add(specifier);
|
|
4615
5327
|
try {
|
|
4616
|
-
const mod = await import(specifier);
|
|
5328
|
+
const mod = await import(resolveSpecifier(specifier, rootDir));
|
|
4617
5329
|
const plugin = mod.default ?? mod;
|
|
4618
5330
|
if (typeof plugin.setup !== "function") {
|
|
4619
|
-
|
|
5331
|
+
failures.push({ specifier, reason: "plugin has no setup function" });
|
|
4620
5332
|
continue;
|
|
4621
5333
|
}
|
|
4622
5334
|
await plugin.setup({ ...fullCtx, options });
|
|
4623
5335
|
console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);
|
|
4624
5336
|
} catch (err) {
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
5337
|
+
failures.push({
|
|
5338
|
+
specifier,
|
|
5339
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
5340
|
+
});
|
|
4628
5341
|
}
|
|
4629
5342
|
}
|
|
4630
|
-
|
|
5343
|
+
if (failures.length > 0) {
|
|
5344
|
+
console.error(
|
|
5345
|
+
`[faapi] ${failures.length} plugin(s) failed to load:
|
|
5346
|
+
` + failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join("\n")
|
|
5347
|
+
);
|
|
5348
|
+
}
|
|
5349
|
+
return { handlerWrappers, upgradeWrappers, failures };
|
|
5350
|
+
}
|
|
5351
|
+
function resolveSpecifier(specifier, rootDir) {
|
|
5352
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
5353
|
+
const base = rootDir ?? process.cwd();
|
|
5354
|
+
return pathToFileURL2(path18.resolve(base, specifier)).href;
|
|
5355
|
+
}
|
|
5356
|
+
if (path18.isAbsolute(specifier)) {
|
|
5357
|
+
return pathToFileURL2(specifier).href;
|
|
5358
|
+
}
|
|
5359
|
+
return specifier;
|
|
4631
5360
|
}
|
|
4632
5361
|
function resolveDeclaration(decl) {
|
|
4633
5362
|
if (typeof decl === "string") {
|
|
@@ -4652,25 +5381,24 @@ var DEFAULT_PORT = 3e3;
|
|
|
4652
5381
|
var ROUTES_FILE = "faapi-routes.js";
|
|
4653
5382
|
var TOOLS_FILE2 = "faapi-tools.js";
|
|
4654
5383
|
var AGENTS_FILE2 = "faapi-agents.js";
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
const toolsPath = path15.resolve(rootDir, dist, TOOLS_FILE2);
|
|
5384
|
+
async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
|
|
5385
|
+
const toolsPath = path19.resolve(rootDir, dist, TOOLS_FILE2);
|
|
4658
5386
|
if (!fs15.existsSync(toolsPath)) {
|
|
4659
5387
|
return [];
|
|
4660
5388
|
}
|
|
4661
5389
|
const serialized = await importWithCacheBust(toolsPath);
|
|
4662
5390
|
const hydrated = hydrateTools(serialized.tools ?? []);
|
|
4663
|
-
|
|
5391
|
+
registries.tool.hydrate(hydrated);
|
|
4664
5392
|
return hydrated;
|
|
4665
5393
|
}
|
|
4666
|
-
async function loadAndHydrateAgents(rootDir, dist) {
|
|
4667
|
-
const agentsPath =
|
|
5394
|
+
async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
|
|
5395
|
+
const agentsPath = path19.resolve(rootDir, dist, AGENTS_FILE2);
|
|
4668
5396
|
if (!fs15.existsSync(agentsPath)) {
|
|
4669
5397
|
return [];
|
|
4670
5398
|
}
|
|
4671
5399
|
const serialized = await importWithCacheBust(agentsPath);
|
|
4672
5400
|
const hydrated = hydrateAgents(serialized.agents ?? []);
|
|
4673
|
-
|
|
5401
|
+
registries.agent.hydrate(hydrated);
|
|
4674
5402
|
return hydrated;
|
|
4675
5403
|
}
|
|
4676
5404
|
var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
|
|
@@ -4684,6 +5412,23 @@ function setCurrentApp(app) {
|
|
|
4684
5412
|
globalThis[APP_INSTANCE_KEY] = app;
|
|
4685
5413
|
}
|
|
4686
5414
|
}
|
|
5415
|
+
var SHUTDOWN_INSTALLED_KEY = /* @__PURE__ */ Symbol.for("faapi.defaultShutdownInstalled");
|
|
5416
|
+
function registerDefaultShutdownHandlers() {
|
|
5417
|
+
const g = globalThis;
|
|
5418
|
+
if (g[SHUTDOWN_INSTALLED_KEY]) return;
|
|
5419
|
+
g[SHUTDOWN_INSTALLED_KEY] = true;
|
|
5420
|
+
const shutdown = (signal) => {
|
|
5421
|
+
console.log(`
|
|
5422
|
+
- Received ${signal}, shutting down...`);
|
|
5423
|
+
void (async () => {
|
|
5424
|
+
const app = getCurrentApp();
|
|
5425
|
+
if (app) await app.close();
|
|
5426
|
+
process.exit(0);
|
|
5427
|
+
})();
|
|
5428
|
+
};
|
|
5429
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
5430
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
5431
|
+
}
|
|
4687
5432
|
function getApp() {
|
|
4688
5433
|
const app = getCurrentApp();
|
|
4689
5434
|
if (!app) {
|
|
@@ -4701,6 +5446,8 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
4701
5446
|
"extendContext",
|
|
4702
5447
|
"plugins",
|
|
4703
5448
|
"helmet",
|
|
5449
|
+
"compression",
|
|
5450
|
+
"etag",
|
|
4704
5451
|
"bodyLimit",
|
|
4705
5452
|
"logger",
|
|
4706
5453
|
"http2",
|
|
@@ -4713,7 +5460,7 @@ function isFaapiConfigKey(key) {
|
|
|
4713
5460
|
async function createAppBase(options) {
|
|
4714
5461
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
4715
5462
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
4716
|
-
const routesPath =
|
|
5463
|
+
const routesPath = path19.resolve(rootDir, dist, ROUTES_FILE);
|
|
4717
5464
|
if (!fs15.existsSync(routesPath)) {
|
|
4718
5465
|
throw new Error(
|
|
4719
5466
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
@@ -4733,8 +5480,9 @@ async function createAppBase(options) {
|
|
|
4733
5480
|
}
|
|
4734
5481
|
}
|
|
4735
5482
|
}
|
|
4736
|
-
const
|
|
4737
|
-
const
|
|
5483
|
+
const registries = createAppRegistries();
|
|
5484
|
+
const tools = await loadAndHydrateTools(rootDir, dist, registries);
|
|
5485
|
+
const agents = await loadAndHydrateAgents(rootDir, dist, registries);
|
|
4738
5486
|
const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
|
|
4739
5487
|
const { server, routesRef } = createServer({
|
|
4740
5488
|
routes: sorted,
|
|
@@ -4747,30 +5495,52 @@ async function createAppBase(options) {
|
|
|
4747
5495
|
middlewares: config?.middlewares,
|
|
4748
5496
|
injectors: config?.injectors,
|
|
4749
5497
|
helmet: config?.helmet,
|
|
5498
|
+
compression: config?.compression,
|
|
5499
|
+
etag: config?.etag,
|
|
4750
5500
|
logger: config?.logger,
|
|
4751
5501
|
bodyLimit: config?.bodyLimit,
|
|
4752
5502
|
http2: config?.http2,
|
|
4753
|
-
trustedProxy: config?.trustedProxy
|
|
4754
|
-
|
|
4755
|
-
const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {
|
|
4756
|
-
rootDir,
|
|
4757
|
-
routes: sorted,
|
|
4758
|
-
getRoutes: () => sorted,
|
|
4759
|
-
server,
|
|
4760
|
-
config: pluginConfig
|
|
5503
|
+
trustedProxy: config?.trustedProxy,
|
|
5504
|
+
registries
|
|
4761
5505
|
});
|
|
5506
|
+
const { handlerWrappers, upgradeWrappers } = await loadPlugins(
|
|
5507
|
+
config?.plugins,
|
|
5508
|
+
{
|
|
5509
|
+
rootDir,
|
|
5510
|
+
registries,
|
|
5511
|
+
routes: sorted,
|
|
5512
|
+
getRoutes: () => sorted,
|
|
5513
|
+
server,
|
|
5514
|
+
config: pluginConfig
|
|
5515
|
+
},
|
|
5516
|
+
rootDir
|
|
5517
|
+
);
|
|
4762
5518
|
applyPluginWrappers(server, handlerWrappers, upgradeWrappers);
|
|
4763
5519
|
let closed = false;
|
|
4764
5520
|
const app = {
|
|
4765
5521
|
server: null,
|
|
5522
|
+
registries,
|
|
4766
5523
|
routes: sorted,
|
|
4767
5524
|
wsRoutes,
|
|
4768
5525
|
rootDir,
|
|
4769
5526
|
async listen(listenPort) {
|
|
4770
5527
|
const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
|
|
4771
5528
|
const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
|
|
4772
|
-
return new Promise((resolve) => {
|
|
5529
|
+
return new Promise((resolve, reject) => {
|
|
5530
|
+
const onListenError = (err) => {
|
|
5531
|
+
if (err.code === "EADDRINUSE") {
|
|
5532
|
+
reject(
|
|
5533
|
+
new Error(
|
|
5534
|
+
`Port ${actualPort} is already in use. Is another faapi instance running? Change the port via the PORT env var.`
|
|
5535
|
+
)
|
|
5536
|
+
);
|
|
5537
|
+
return;
|
|
5538
|
+
}
|
|
5539
|
+
reject(err);
|
|
5540
|
+
};
|
|
5541
|
+
server.once("error", onListenError);
|
|
4773
5542
|
server.listen(actualPort, async () => {
|
|
5543
|
+
server.off("error", onListenError);
|
|
4774
5544
|
const address = server.address();
|
|
4775
5545
|
const p = typeof address === "object" && address !== null ? address.port : actualPort;
|
|
4776
5546
|
console.log("faapi server started");
|
|
@@ -4798,18 +5568,9 @@ async function createAppBase(options) {
|
|
|
4798
5568
|
console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
|
|
4799
5569
|
}
|
|
4800
5570
|
}
|
|
4801
|
-
|
|
4802
|
-
const graceful = async (signal) => {
|
|
4803
|
-
console.log(`
|
|
4804
|
-
- Received ${signal}, shutting down...`);
|
|
4805
|
-
await app.close();
|
|
4806
|
-
process.exit(0);
|
|
4807
|
-
};
|
|
4808
|
-
process.on("SIGTERM", () => void graceful("SIGTERM"));
|
|
4809
|
-
process.on("SIGINT", () => void graceful("SIGINT"));
|
|
4810
|
-
}
|
|
5571
|
+
registerDefaultShutdownHandlers();
|
|
4811
5572
|
if (config?.lifecycle?.onReady) {
|
|
4812
|
-
await config.lifecycle.onReady({ rootDir, routes: sorted, server });
|
|
5573
|
+
await config.lifecycle.onReady({ rootDir, routes: sorted, server, registries });
|
|
4813
5574
|
console.log("- onReady hook executed");
|
|
4814
5575
|
}
|
|
4815
5576
|
app.server = server;
|
|
@@ -4886,39 +5647,50 @@ async function createAppBase(options) {
|
|
|
4886
5647
|
if (closed) return;
|
|
4887
5648
|
closed = true;
|
|
4888
5649
|
const s = server;
|
|
4889
|
-
|
|
4890
|
-
s.closeIdleConnections();
|
|
4891
|
-
}
|
|
4892
|
-
if (typeof s.closeAllConnections === "function") {
|
|
4893
|
-
s.closeAllConnections();
|
|
4894
|
-
}
|
|
5650
|
+
s.closeIdleConnections?.();
|
|
4895
5651
|
if (config?.lifecycle?.onClose) {
|
|
4896
|
-
await config.lifecycle.onClose({ rootDir, routes: sorted, server });
|
|
5652
|
+
await config.lifecycle.onClose({ rootDir, routes: sorted, server, registries });
|
|
4897
5653
|
}
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
5654
|
+
registries.tool.clear();
|
|
5655
|
+
registries.agent.clear();
|
|
5656
|
+
registries.skill.clear();
|
|
5657
|
+
registries.agentHandle.clear();
|
|
4902
5658
|
if (!server.listening) {
|
|
4903
5659
|
app.server = null;
|
|
4904
5660
|
if (getCurrentApp() === app) setCurrentApp(null);
|
|
4905
5661
|
return;
|
|
4906
5662
|
}
|
|
4907
|
-
|
|
5663
|
+
const drained = new Promise((resolve) => {
|
|
4908
5664
|
server.close((err) => {
|
|
4909
5665
|
if (err) console.error("Error closing server:", err);
|
|
4910
|
-
app.server = null;
|
|
4911
|
-
if (getCurrentApp() === app) setCurrentApp(null);
|
|
4912
5666
|
resolve();
|
|
4913
5667
|
});
|
|
4914
5668
|
});
|
|
5669
|
+
const drainTimeoutMs = Number(process.env.FAAPI_SHUTDOWN_TIMEOUT_MS ?? 1e4);
|
|
5670
|
+
if (typeof s.closeAllConnections === "function" && Number.isFinite(drainTimeoutMs) && drainTimeoutMs >= 0) {
|
|
5671
|
+
const forceClose = new Promise((resolve) => {
|
|
5672
|
+
const timer = setTimeout(() => {
|
|
5673
|
+
s.closeAllConnections?.();
|
|
5674
|
+
resolve();
|
|
5675
|
+
}, drainTimeoutMs);
|
|
5676
|
+
timer.unref?.();
|
|
5677
|
+
});
|
|
5678
|
+
await Promise.race([drained, forceClose]);
|
|
5679
|
+
const tail = new Promise((resolve) => setTimeout(resolve, 250).unref?.());
|
|
5680
|
+
await Promise.race([drained, tail]);
|
|
5681
|
+
} else {
|
|
5682
|
+
await drained;
|
|
5683
|
+
}
|
|
5684
|
+
app.server = null;
|
|
5685
|
+
if (getCurrentApp() === app) setCurrentApp(null);
|
|
4915
5686
|
}
|
|
4916
5687
|
};
|
|
4917
5688
|
setCurrentApp(app);
|
|
4918
5689
|
const ctx = {
|
|
4919
5690
|
rootDir,
|
|
5691
|
+
registries,
|
|
4920
5692
|
dist,
|
|
4921
|
-
patterns:
|
|
5693
|
+
patterns: ROUTE_PATTERNS,
|
|
4922
5694
|
server,
|
|
4923
5695
|
routesRef,
|
|
4924
5696
|
config,
|
|
@@ -4936,7 +5708,7 @@ async function createAppBase(options) {
|
|
|
4936
5708
|
|
|
4937
5709
|
// src/router/scanRoutes.ts
|
|
4938
5710
|
import fg2 from "fast-glob";
|
|
4939
|
-
import
|
|
5711
|
+
import path20 from "path";
|
|
4940
5712
|
import fs16 from "fs";
|
|
4941
5713
|
|
|
4942
5714
|
// src/router/constants.ts
|
|
@@ -4944,9 +5716,9 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
|
4944
5716
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
4945
5717
|
|
|
4946
5718
|
// src/utils/normalizePath.ts
|
|
4947
|
-
function normalizePath(
|
|
4948
|
-
if (!
|
|
4949
|
-
let result =
|
|
5719
|
+
function normalizePath(path23) {
|
|
5720
|
+
if (!path23) return "";
|
|
5721
|
+
let result = path23.replace(/\\/g, "/");
|
|
4950
5722
|
result = result.replace(/\/+/g, "/");
|
|
4951
5723
|
result = result.replace(/\/+$/, "");
|
|
4952
5724
|
if (result && !result.startsWith("/")) {
|
|
@@ -5007,26 +5779,26 @@ function extractExportsFromSource(source) {
|
|
|
5007
5779
|
return names;
|
|
5008
5780
|
}
|
|
5009
5781
|
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
5010
|
-
const routeDir =
|
|
5011
|
-
const resolvedRoot =
|
|
5782
|
+
const routeDir = path20.dirname(routeFilePath);
|
|
5783
|
+
const resolvedRoot = path20.resolve(rootDir);
|
|
5012
5784
|
const paths = [];
|
|
5013
|
-
let currentDir =
|
|
5785
|
+
let currentDir = path20.resolve(rootDir, routeDir);
|
|
5014
5786
|
while (true) {
|
|
5015
5787
|
if (dist) {
|
|
5016
|
-
const mwTsPath =
|
|
5017
|
-
const mwJsPath =
|
|
5018
|
-
const absTsPath =
|
|
5019
|
-
const absJsPath =
|
|
5788
|
+
const mwTsPath = path20.join(currentDir, "middlewares.ts");
|
|
5789
|
+
const mwJsPath = path20.join(currentDir, "middlewares.js");
|
|
5790
|
+
const absTsPath = path20.resolve(rootDir, mwTsPath);
|
|
5791
|
+
const absJsPath = path20.resolve(rootDir, mwJsPath);
|
|
5020
5792
|
const absMwPath = fs16.existsSync(absTsPath) ? absTsPath : fs16.existsSync(absJsPath) ? absJsPath : null;
|
|
5021
5793
|
if (absMwPath) {
|
|
5022
|
-
const relMwPath =
|
|
5023
|
-
const prodAbsPath =
|
|
5794
|
+
const relMwPath = path20.relative(rootDir, absMwPath);
|
|
5795
|
+
const prodAbsPath = path20.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
5024
5796
|
paths.push(prodAbsPath);
|
|
5025
5797
|
}
|
|
5026
5798
|
} else {
|
|
5027
5799
|
for (const ext of [".ts", ".js"]) {
|
|
5028
|
-
const mwPath =
|
|
5029
|
-
const absMwPath =
|
|
5800
|
+
const mwPath = path20.join(currentDir, `middlewares${ext}`);
|
|
5801
|
+
const absMwPath = path20.resolve(rootDir, mwPath);
|
|
5030
5802
|
if (fs16.existsSync(absMwPath)) {
|
|
5031
5803
|
paths.push(absMwPath);
|
|
5032
5804
|
break;
|
|
@@ -5034,21 +5806,13 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
|
5034
5806
|
}
|
|
5035
5807
|
}
|
|
5036
5808
|
if (currentDir === resolvedRoot) break;
|
|
5037
|
-
const parentDir =
|
|
5809
|
+
const parentDir = path20.dirname(currentDir);
|
|
5038
5810
|
if (parentDir === currentDir) break;
|
|
5039
5811
|
currentDir = parentDir;
|
|
5040
5812
|
}
|
|
5041
5813
|
paths.reverse();
|
|
5042
5814
|
return paths;
|
|
5043
5815
|
}
|
|
5044
|
-
function toProdFilePath3(filePath, dist) {
|
|
5045
|
-
let rel = filePath.replace(/\\/g, "/");
|
|
5046
|
-
if (rel.startsWith("src/")) {
|
|
5047
|
-
rel = rel.slice(4);
|
|
5048
|
-
}
|
|
5049
|
-
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
5050
|
-
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
5051
|
-
}
|
|
5052
5816
|
async function scanRoutes(rootDir, patterns, dist) {
|
|
5053
5817
|
const files = await fg2(patterns, {
|
|
5054
5818
|
cwd: rootDir,
|
|
@@ -5061,7 +5825,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
5061
5825
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
5062
5826
|
const fileName = normalizedFile.split("/").pop();
|
|
5063
5827
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
5064
|
-
const absPath =
|
|
5828
|
+
const absPath = path20.resolve(rootDir, normalizedFile);
|
|
5065
5829
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
5066
5830
|
const paramNames = extractParamNames(urlPath);
|
|
5067
5831
|
const isDynamic = paramNames.length > 0;
|
|
@@ -5110,7 +5874,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
5110
5874
|
|
|
5111
5875
|
// src/tools/scanTools.ts
|
|
5112
5876
|
import fg3 from "fast-glob";
|
|
5113
|
-
import
|
|
5877
|
+
import path21 from "path";
|
|
5114
5878
|
import fs17 from "fs";
|
|
5115
5879
|
var TOOL_PATTERNS = ["src/tools/**/*.ts"];
|
|
5116
5880
|
var TOOL_EXPORT_RE = new RegExp(
|
|
@@ -5161,7 +5925,7 @@ async function scanTools(rootDir, patterns) {
|
|
|
5161
5925
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
5162
5926
|
continue;
|
|
5163
5927
|
}
|
|
5164
|
-
const absPath =
|
|
5928
|
+
const absPath = path21.resolve(rootDir, normalizedFile);
|
|
5165
5929
|
const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
|
|
5166
5930
|
const exportNames = extractToolExportsFromSource(source);
|
|
5167
5931
|
const namespace = filePathToToolNamespace(normalizedFile);
|
|
@@ -5186,7 +5950,7 @@ async function scanTools(rootDir, patterns) {
|
|
|
5186
5950
|
|
|
5187
5951
|
// src/agents/scanAgents.ts
|
|
5188
5952
|
import fg4 from "fast-glob";
|
|
5189
|
-
import
|
|
5953
|
+
import path22 from "path";
|
|
5190
5954
|
import fs18 from "fs";
|
|
5191
5955
|
var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
|
|
5192
5956
|
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
@@ -5219,7 +5983,7 @@ async function scanAgents(rootDir, patterns) {
|
|
|
5219
5983
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
5220
5984
|
continue;
|
|
5221
5985
|
}
|
|
5222
|
-
const absPath =
|
|
5986
|
+
const absPath = path22.resolve(rootDir, normalizedFile);
|
|
5223
5987
|
const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
|
|
5224
5988
|
const { hasRun } = detectAgentExports(source);
|
|
5225
5989
|
const name = extractAgentNameFromPath(normalizedFile);
|
|
@@ -5255,6 +6019,14 @@ async function createDevApp(options) {
|
|
|
5255
6019
|
if (isDevOnDemandEnabled()) {
|
|
5256
6020
|
await deleteSchemaFiles(sorted, ctx.rootDir, ctx.dist);
|
|
5257
6021
|
clearGeneratedSchemas();
|
|
6022
|
+
void (async () => {
|
|
6023
|
+
try {
|
|
6024
|
+
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
6025
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
6026
|
+
} catch (err) {
|
|
6027
|
+
console.error("[faapi] Background schema regeneration failed:", err);
|
|
6028
|
+
}
|
|
6029
|
+
})();
|
|
5258
6030
|
} else {
|
|
5259
6031
|
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
5260
6032
|
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
@@ -5268,14 +6040,14 @@ async function createDevApp(options) {
|
|
|
5268
6040
|
await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {
|
|
5269
6041
|
skipSchema: isDevOnDemandEnabled()
|
|
5270
6042
|
});
|
|
5271
|
-
await loadAndHydrateTools(ctx.rootDir, ctx.dist);
|
|
6043
|
+
await loadAndHydrateTools(ctx.rootDir, ctx.dist, ctx.registries);
|
|
5272
6044
|
};
|
|
5273
6045
|
devApp.reloadAgents = async () => {
|
|
5274
6046
|
setLoadTimestamp(Date.now());
|
|
5275
6047
|
invalidateProgramCache();
|
|
5276
6048
|
const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);
|
|
5277
6049
|
await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
|
|
5278
|
-
await loadAndHydrateAgents(ctx.rootDir, ctx.dist);
|
|
6050
|
+
await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
|
|
5279
6051
|
};
|
|
5280
6052
|
return devApp;
|
|
5281
6053
|
}
|
|
@@ -5297,6 +6069,7 @@ export {
|
|
|
5297
6069
|
collectRouteSchemaSources,
|
|
5298
6070
|
cors,
|
|
5299
6071
|
createProdApp as createApp,
|
|
6072
|
+
createAppRegistries,
|
|
5300
6073
|
createDevApp,
|
|
5301
6074
|
createProdApp,
|
|
5302
6075
|
createProgram,
|