@faapi/faapi 6.2.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1397 -499
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +156 -3
- package/dist/index.js +1090 -259
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-Bm--uTbm.d.ts → routeTypes-_17rXzal.d.ts} +355 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +48 -2
- package/dist/testing.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1120,8 +1120,10 @@ var init_resolveInjection = __esm({
|
|
|
1120
1120
|
fields: "fields",
|
|
1121
1121
|
agent: "agent",
|
|
1122
1122
|
// Phase 2.3
|
|
1123
|
-
agents: "agents"
|
|
1123
|
+
agents: "agents",
|
|
1124
1124
|
// Phase 2.3
|
|
1125
|
+
tasks: "tasks"
|
|
1126
|
+
// 任务子系统:TaskClient(入队/查询)
|
|
1125
1127
|
};
|
|
1126
1128
|
injectionCache = /* @__PURE__ */ new WeakMap();
|
|
1127
1129
|
}
|
|
@@ -1231,13 +1233,13 @@ var init_collectRouteSchemaSources = __esm({
|
|
|
1231
1233
|
});
|
|
1232
1234
|
|
|
1233
1235
|
// src/utils/atomicWrite.ts
|
|
1234
|
-
import
|
|
1235
|
-
import
|
|
1236
|
+
import path9 from "path";
|
|
1237
|
+
import fs7 from "fs";
|
|
1236
1238
|
async function atomicWriteFile(outputPath, content) {
|
|
1237
|
-
await
|
|
1239
|
+
await fs7.promises.mkdir(path9.dirname(outputPath), { recursive: true });
|
|
1238
1240
|
const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1239
|
-
await
|
|
1240
|
-
await
|
|
1241
|
+
await fs7.promises.writeFile(tmp, content, "utf-8");
|
|
1242
|
+
await fs7.promises.rename(tmp, outputPath);
|
|
1241
1243
|
}
|
|
1242
1244
|
var init_atomicWrite = __esm({
|
|
1243
1245
|
"src/utils/atomicWrite.ts"() {
|
|
@@ -1584,7 +1586,7 @@ __export(generateSchemaFiles_exports, {
|
|
|
1584
1586
|
getRuntimeSchemaPath: () => getRuntimeSchemaPath,
|
|
1585
1587
|
getSchemaOutputPath: () => getSchemaOutputPath
|
|
1586
1588
|
});
|
|
1587
|
-
import
|
|
1589
|
+
import path10 from "path";
|
|
1588
1590
|
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1589
1591
|
let rel = sourceFile.replace(/\\/g, "/");
|
|
1590
1592
|
if (rel.startsWith("src/")) {
|
|
@@ -1592,7 +1594,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
1592
1594
|
}
|
|
1593
1595
|
const idx = rel.lastIndexOf("/");
|
|
1594
1596
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1595
|
-
return
|
|
1597
|
+
return path10.resolve(rootDir, dist, relDir, "zod.js");
|
|
1596
1598
|
}
|
|
1597
1599
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
1598
1600
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -1603,7 +1605,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
1603
1605
|
}
|
|
1604
1606
|
const idx = rel.lastIndexOf("/");
|
|
1605
1607
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1606
|
-
return
|
|
1608
|
+
return path10.resolve(rootDir, dist, relDir, "zod.js");
|
|
1607
1609
|
}
|
|
1608
1610
|
function getHelpersImportPath(relDir) {
|
|
1609
1611
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -1662,7 +1664,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1662
1664
|
}
|
|
1663
1665
|
const fileEntries = [];
|
|
1664
1666
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1665
|
-
const relFile =
|
|
1667
|
+
const relFile = path10.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1666
1668
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1667
1669
|
let relForDir = relFile;
|
|
1668
1670
|
if (relForDir.startsWith("src/")) {
|
|
@@ -1681,7 +1683,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1681
1683
|
}
|
|
1682
1684
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1683
1685
|
if (usesCoerceHelpers(allSourceCode)) {
|
|
1684
|
-
const helpersPath =
|
|
1686
|
+
const helpersPath = path10.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1685
1687
|
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
1686
1688
|
}
|
|
1687
1689
|
await Promise.all(
|
|
@@ -1707,6 +1709,29 @@ init_resolveTypeNode();
|
|
|
1707
1709
|
init_inputType();
|
|
1708
1710
|
init_collectRouteSchemaSources();
|
|
1709
1711
|
|
|
1712
|
+
// src/task/taskRegistry.ts
|
|
1713
|
+
function createTaskRegistry() {
|
|
1714
|
+
let registry = /* @__PURE__ */ new Map();
|
|
1715
|
+
return {
|
|
1716
|
+
hydrate(tasks) {
|
|
1717
|
+
const next = /* @__PURE__ */ new Map();
|
|
1718
|
+
for (const task of tasks) {
|
|
1719
|
+
next.set(task.name, task);
|
|
1720
|
+
}
|
|
1721
|
+
registry = next;
|
|
1722
|
+
},
|
|
1723
|
+
get(name) {
|
|
1724
|
+
return registry.get(name);
|
|
1725
|
+
},
|
|
1726
|
+
list() {
|
|
1727
|
+
return Array.from(registry.values());
|
|
1728
|
+
},
|
|
1729
|
+
clear() {
|
|
1730
|
+
registry = /* @__PURE__ */ new Map();
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1710
1735
|
// src/injection/registries.ts
|
|
1711
1736
|
function createToolRegistry() {
|
|
1712
1737
|
let registry = /* @__PURE__ */ new Map();
|
|
@@ -1829,15 +1854,604 @@ function createAgentHandleStore() {
|
|
|
1829
1854
|
}
|
|
1830
1855
|
};
|
|
1831
1856
|
}
|
|
1857
|
+
function createTaskHandleStore() {
|
|
1858
|
+
let currentFactory = null;
|
|
1859
|
+
return {
|
|
1860
|
+
register(factory) {
|
|
1861
|
+
currentFactory = factory;
|
|
1862
|
+
},
|
|
1863
|
+
get(ctx) {
|
|
1864
|
+
if (currentFactory === null) return void 0;
|
|
1865
|
+
return currentFactory(ctx);
|
|
1866
|
+
},
|
|
1867
|
+
clear() {
|
|
1868
|
+
currentFactory = null;
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1832
1872
|
function createAppRegistries() {
|
|
1833
1873
|
const tool = createToolRegistry();
|
|
1834
1874
|
const agent = createAgentRegistry(tool);
|
|
1835
1875
|
const skill = createSkillRegistry();
|
|
1876
|
+
const task = createTaskRegistry();
|
|
1836
1877
|
const agentHandle = createAgentHandleStore();
|
|
1837
|
-
|
|
1878
|
+
const taskHandle = createTaskHandleStore();
|
|
1879
|
+
return { tool, agent, skill, task, agentHandle, taskHandle };
|
|
1838
1880
|
}
|
|
1839
1881
|
var defaultRegistries = createAppRegistries();
|
|
1840
1882
|
|
|
1883
|
+
// src/task/taskQueue.ts
|
|
1884
|
+
import path3 from "path";
|
|
1885
|
+
|
|
1886
|
+
// src/errors/FaapiError.ts
|
|
1887
|
+
var FaapiError = class extends Error {
|
|
1888
|
+
constructor(code, message, statusCode) {
|
|
1889
|
+
super(message);
|
|
1890
|
+
this.code = code;
|
|
1891
|
+
this.statusCode = statusCode;
|
|
1892
|
+
this.name = "FaapiError";
|
|
1893
|
+
}
|
|
1894
|
+
code;
|
|
1895
|
+
statusCode;
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1898
|
+
// src/errors/httpErrors.ts
|
|
1899
|
+
function deriveStatusCode(issues) {
|
|
1900
|
+
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
1901
|
+
return has400 ? 400 : 422;
|
|
1902
|
+
}
|
|
1903
|
+
var ValidationError = class extends FaapiError {
|
|
1904
|
+
constructor(message, issues) {
|
|
1905
|
+
super("VALIDATION_ERROR", message, deriveStatusCode(issues));
|
|
1906
|
+
this.issues = issues;
|
|
1907
|
+
this.name = "ValidationError";
|
|
1908
|
+
}
|
|
1909
|
+
issues;
|
|
1910
|
+
};
|
|
1911
|
+
var RouteNotFoundError = class extends FaapiError {
|
|
1912
|
+
constructor(path28) {
|
|
1913
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path28}`, 404);
|
|
1914
|
+
this.name = "RouteNotFoundError";
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
var MethodNotAllowedError = class extends FaapiError {
|
|
1918
|
+
constructor(method, path28, allowedMethods) {
|
|
1919
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path28}`, 405);
|
|
1920
|
+
this.allowedMethods = allowedMethods;
|
|
1921
|
+
this.name = "MethodNotAllowedError";
|
|
1922
|
+
}
|
|
1923
|
+
allowedMethods;
|
|
1924
|
+
};
|
|
1925
|
+
var InternalError = class extends FaapiError {
|
|
1926
|
+
constructor(message) {
|
|
1927
|
+
super("INTERNAL_ERROR", message, 500);
|
|
1928
|
+
this.name = "InternalError";
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
var ModuleLoadError = class extends FaapiError {
|
|
1932
|
+
constructor(filePath, reason) {
|
|
1933
|
+
super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
|
|
1934
|
+
this.name = "ModuleLoadError";
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
var PayloadTooLargeError = class extends FaapiError {
|
|
1938
|
+
constructor(maxSize) {
|
|
1939
|
+
super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
|
|
1940
|
+
this.name = "PayloadTooLargeError";
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
|
|
1944
|
+
// src/task/taskWorker.ts
|
|
1945
|
+
import { Worker } from "worker_threads";
|
|
1946
|
+
import { pathToFileURL } from "url";
|
|
1947
|
+
var KILL_GRACE_MS = 5e3;
|
|
1948
|
+
var TaskCancelledError = class extends Error {
|
|
1949
|
+
constructor(message) {
|
|
1950
|
+
super(message);
|
|
1951
|
+
this.name = "TaskCancelledError";
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
function safeConfig(config) {
|
|
1955
|
+
if (config === void 0 || config === null) return config;
|
|
1956
|
+
try {
|
|
1957
|
+
structuredClone(config);
|
|
1958
|
+
return config;
|
|
1959
|
+
} catch {
|
|
1960
|
+
}
|
|
1961
|
+
try {
|
|
1962
|
+
return JSON.parse(JSON.stringify(config));
|
|
1963
|
+
} catch {
|
|
1964
|
+
return void 0;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
function buildWrapperSource(moduleUrl) {
|
|
1968
|
+
return `
|
|
1969
|
+
import { parentPort } from 'node:worker_threads';
|
|
1970
|
+
import * as mod from '${moduleUrl}';
|
|
1971
|
+
|
|
1972
|
+
const run = mod.run;
|
|
1973
|
+
if (typeof run !== 'function') {
|
|
1974
|
+
parentPort.postMessage({ type: 'error', message: 'Task module has no run export' });
|
|
1975
|
+
} else {
|
|
1976
|
+
let controller = null;
|
|
1977
|
+
parentPort.on('message', async (msg) => {
|
|
1978
|
+
if (msg?.type === 'abort') {
|
|
1979
|
+
controller?.abort(new Error(msg.reason));
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
if (msg?.type !== 'run') return;
|
|
1983
|
+
controller = new AbortController();
|
|
1984
|
+
const { payload, taskCtx } = msg;
|
|
1985
|
+
try {
|
|
1986
|
+
const result = await run(payload, { ...taskCtx, signal: controller.signal });
|
|
1987
|
+
parentPort.postMessage({ type: 'done', result });
|
|
1988
|
+
} catch (err) {
|
|
1989
|
+
parentPort.postMessage({
|
|
1990
|
+
type: 'error',
|
|
1991
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
`;
|
|
1997
|
+
}
|
|
1998
|
+
async function runTaskInWorker(options) {
|
|
1999
|
+
const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
|
|
2000
|
+
const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
|
|
2001
|
+
const moduleUrl = pathToFileURL(taskModulePath).href;
|
|
2002
|
+
const wrapperUrl = new URL(
|
|
2003
|
+
`data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
|
|
2004
|
+
);
|
|
2005
|
+
return new Promise((resolve, reject) => {
|
|
2006
|
+
let worker;
|
|
2007
|
+
try {
|
|
2008
|
+
worker = new Worker(wrapperUrl);
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
reject(err);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
let phase = "running";
|
|
2014
|
+
let cancelReason = "";
|
|
2015
|
+
let graceTimer;
|
|
2016
|
+
const finish = (settle) => {
|
|
2017
|
+
if (phase === "settled") return;
|
|
2018
|
+
phase = "settled";
|
|
2019
|
+
clearTimeout(timeoutTimer);
|
|
2020
|
+
if (graceTimer) clearTimeout(graceTimer);
|
|
2021
|
+
externalSignal?.removeEventListener("abort", onExternalAbort);
|
|
2022
|
+
void worker.terminate();
|
|
2023
|
+
settle();
|
|
2024
|
+
};
|
|
2025
|
+
const startCancel = (reason) => {
|
|
2026
|
+
if (phase !== "running") return;
|
|
2027
|
+
phase = "grace";
|
|
2028
|
+
cancelReason = reason;
|
|
2029
|
+
worker.postMessage({ type: "abort", reason });
|
|
2030
|
+
graceTimer = setTimeout(() => {
|
|
2031
|
+
finish(
|
|
2032
|
+
() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${reason} and was terminated`))
|
|
2033
|
+
);
|
|
2034
|
+
}, killGraceMs);
|
|
2035
|
+
};
|
|
2036
|
+
const onExternalAbort = () => startCancel("cancelled (driver stop timeout)");
|
|
2037
|
+
if (externalSignal) {
|
|
2038
|
+
if (externalSignal.aborted) {
|
|
2039
|
+
onExternalAbort();
|
|
2040
|
+
} else {
|
|
2041
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
const timeoutTimer = setTimeout(
|
|
2045
|
+
() => startCancel(`timed out after ${timeoutMs}ms`),
|
|
2046
|
+
timeoutMs
|
|
2047
|
+
);
|
|
2048
|
+
worker.on("message", (msg) => {
|
|
2049
|
+
if (phase === "grace") {
|
|
2050
|
+
if (msg?.type === "done") {
|
|
2051
|
+
finish(
|
|
2052
|
+
() => reject(
|
|
2053
|
+
new TaskCancelledError(
|
|
2054
|
+
`Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
|
|
2055
|
+
)
|
|
2056
|
+
)
|
|
2057
|
+
);
|
|
2058
|
+
} else if (msg?.type === "error") {
|
|
2059
|
+
finish(() => reject(new TaskCancelledError(msg.message ?? cancelReason)));
|
|
2060
|
+
}
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
if (phase !== "running") return;
|
|
2064
|
+
if (msg?.type === "done") {
|
|
2065
|
+
finish(() => resolve(msg.result));
|
|
2066
|
+
} else if (msg?.type === "error") {
|
|
2067
|
+
finish(() => reject(new Error(msg.message ?? "task worker error")));
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
worker.on("error", (err) => {
|
|
2071
|
+
finish(() => reject(err));
|
|
2072
|
+
});
|
|
2073
|
+
worker.on("exit", (code) => {
|
|
2074
|
+
if (phase === "grace") {
|
|
2075
|
+
finish(() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${cancelReason}`)));
|
|
2076
|
+
} else if (phase === "running") {
|
|
2077
|
+
finish(() => reject(new Error(`Task worker exited unexpectedly (code ${code})`)));
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
try {
|
|
2081
|
+
worker.postMessage({
|
|
2082
|
+
type: "run",
|
|
2083
|
+
payload,
|
|
2084
|
+
taskCtx: { config: safeConfig(taskCtx.config), job: taskCtx.job }
|
|
2085
|
+
});
|
|
2086
|
+
} catch (err) {
|
|
2087
|
+
finish(
|
|
2088
|
+
() => reject(
|
|
2089
|
+
new Error(`Task "${taskCtx.job.name}" payload/config is not cloneable: ${String(err)}`)
|
|
2090
|
+
)
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// src/task/taskQueue.ts
|
|
2097
|
+
function createTaskQueue(deps) {
|
|
2098
|
+
const { registry, rootDir, driver } = deps;
|
|
2099
|
+
const records = /* @__PURE__ */ new Map();
|
|
2100
|
+
const moduleCache2 = /* @__PURE__ */ new Map();
|
|
2101
|
+
const schemaCache = /* @__PURE__ */ new Map();
|
|
2102
|
+
let started = false;
|
|
2103
|
+
let stopped = false;
|
|
2104
|
+
const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
|
|
2105
|
+
const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
|
|
2106
|
+
const zodPath = path3.join(path3.dirname(filePath), "zod.js");
|
|
2107
|
+
try {
|
|
2108
|
+
const mod = await import(zodPath);
|
|
2109
|
+
const schemaKey = Object.keys(mod).find((k) => k.endsWith("Schema"));
|
|
2110
|
+
return schemaKey ? mod[schemaKey] : void 0;
|
|
2111
|
+
} catch {
|
|
2112
|
+
return void 0;
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
function assertKnownTask(name) {
|
|
2116
|
+
const meta = registry.get(name);
|
|
2117
|
+
if (!meta) {
|
|
2118
|
+
const known = registry.list().map((t) => t.name).join(", ") || "(none)";
|
|
2119
|
+
throw new Error(`[faapi] Unknown task "${name}". Registered tasks: ${known}`);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
async function validatePayload(name, payload) {
|
|
2123
|
+
if (!schemaCache.has(name)) {
|
|
2124
|
+
const meta = registry.get(name);
|
|
2125
|
+
schemaCache.set(
|
|
2126
|
+
name,
|
|
2127
|
+
await loadPayloadSchema(path3.resolve(rootDir, meta.filePath)).catch(() => void 0)
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
const schema = schemaCache.get(name);
|
|
2131
|
+
if (!schema || typeof schema.safeParse !== "function") {
|
|
2132
|
+
return payload;
|
|
2133
|
+
}
|
|
2134
|
+
const result = schema.safeParse(payload);
|
|
2135
|
+
if (!result.success) {
|
|
2136
|
+
throw new ValidationError(
|
|
2137
|
+
`Task payload validation failed for "${name}": ${JSON.stringify(result.error)}`,
|
|
2138
|
+
[]
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
return result.data;
|
|
2142
|
+
}
|
|
2143
|
+
async function runJob(job) {
|
|
2144
|
+
const meta = registry.get(job.name);
|
|
2145
|
+
if (!meta) throw new Error(`[faapi] Unknown task "${job.name}" at worker dispatch`);
|
|
2146
|
+
const record = records.get(job.id) ?? {
|
|
2147
|
+
id: job.id,
|
|
2148
|
+
name: job.name,
|
|
2149
|
+
payload: job.payload,
|
|
2150
|
+
status: "pending",
|
|
2151
|
+
attempts: 0,
|
|
2152
|
+
createdAt: Date.now()
|
|
2153
|
+
};
|
|
2154
|
+
record.attempts = job.attempt;
|
|
2155
|
+
record.status = "running";
|
|
2156
|
+
record.error = void 0;
|
|
2157
|
+
records.set(job.id, record);
|
|
2158
|
+
try {
|
|
2159
|
+
let result;
|
|
2160
|
+
if (meta.timeoutMs !== void 0 && meta.timeoutMs > 0) {
|
|
2161
|
+
result = await (deps.runIsolated ?? runTaskInWorker)({
|
|
2162
|
+
taskModulePath: path3.resolve(rootDir, meta.filePath),
|
|
2163
|
+
payload: job.payload,
|
|
2164
|
+
taskCtx: {
|
|
2165
|
+
config: deps.config,
|
|
2166
|
+
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
2167
|
+
},
|
|
2168
|
+
timeoutMs: meta.timeoutMs,
|
|
2169
|
+
externalSignal: job.signal
|
|
2170
|
+
});
|
|
2171
|
+
} else {
|
|
2172
|
+
let mod = moduleCache2.get(job.name);
|
|
2173
|
+
if (!mod) {
|
|
2174
|
+
mod = await loadTaskModule(path3.resolve(rootDir, meta.filePath));
|
|
2175
|
+
moduleCache2.set(job.name, mod);
|
|
2176
|
+
}
|
|
2177
|
+
if (typeof mod.run !== "function") {
|
|
2178
|
+
throw new Error(`Task "${job.name}" module has no run export`);
|
|
2179
|
+
}
|
|
2180
|
+
const taskCtx = {
|
|
2181
|
+
signal: job.signal,
|
|
2182
|
+
config: deps.config,
|
|
2183
|
+
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
2184
|
+
};
|
|
2185
|
+
result = await mod.run(job.payload, taskCtx);
|
|
2186
|
+
}
|
|
2187
|
+
record.status = "done";
|
|
2188
|
+
record.result = result;
|
|
2189
|
+
return result;
|
|
2190
|
+
} catch (err) {
|
|
2191
|
+
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
2192
|
+
record.status = cancelled ? "cancelled" : "failed";
|
|
2193
|
+
record.error = err instanceof Error ? err.message : String(err);
|
|
2194
|
+
if (deps.onFailed) {
|
|
2195
|
+
void Promise.resolve().then(
|
|
2196
|
+
() => deps.onFailed({
|
|
2197
|
+
task: job.name,
|
|
2198
|
+
jobId: job.id,
|
|
2199
|
+
attempt: job.attempt,
|
|
2200
|
+
willRetry: job.attempt <= (meta.retries ?? 0),
|
|
2201
|
+
cancelled,
|
|
2202
|
+
error: record.error
|
|
2203
|
+
})
|
|
2204
|
+
).catch(() => {
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
throw err;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
async function startWorkers() {
|
|
2211
|
+
for (const meta of registry.list()) {
|
|
2212
|
+
await driver.startWorker(meta.name, {
|
|
2213
|
+
concurrency: meta.concurrency ?? 1,
|
|
2214
|
+
process: runJob
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
const queue = {
|
|
2219
|
+
async enqueue(name, payload = {}, opts) {
|
|
2220
|
+
if (stopped) {
|
|
2221
|
+
throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
|
|
2222
|
+
}
|
|
2223
|
+
assertKnownTask(name);
|
|
2224
|
+
const data = await validatePayload(name, payload);
|
|
2225
|
+
const meta = registry.get(name);
|
|
2226
|
+
const id = await driver.enqueue(name, data, {
|
|
2227
|
+
delayMs: opts?.delayMs,
|
|
2228
|
+
retries: meta.retries ?? 0,
|
|
2229
|
+
dedupId: opts?.dedupId
|
|
2230
|
+
});
|
|
2231
|
+
if (!records.has(id)) {
|
|
2232
|
+
records.set(id, {
|
|
2233
|
+
id,
|
|
2234
|
+
name,
|
|
2235
|
+
payload: data,
|
|
2236
|
+
status: "pending",
|
|
2237
|
+
attempts: 0,
|
|
2238
|
+
createdAt: Date.now(),
|
|
2239
|
+
...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
return { id };
|
|
2243
|
+
},
|
|
2244
|
+
list(name) {
|
|
2245
|
+
const snapshot = [];
|
|
2246
|
+
for (const job of records.values()) {
|
|
2247
|
+
if (name !== void 0 && job.name !== name) continue;
|
|
2248
|
+
snapshot.push({ ...job });
|
|
2249
|
+
}
|
|
2250
|
+
return snapshot;
|
|
2251
|
+
},
|
|
2252
|
+
async listQueued(name) {
|
|
2253
|
+
if (!driver.list) {
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
"[faapi] Task driver does not support listing queued tasks (TaskDriver.list is not implemented). Use the queue system's own management tools, or see driverTypes.md for per-driver capability."
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
const queued = await driver.list({ name, limit: 50 });
|
|
2259
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2260
|
+
for (const record of queued) {
|
|
2261
|
+
if (name !== void 0 && record.name !== name) continue;
|
|
2262
|
+
merged.set(record.id, {
|
|
2263
|
+
id: record.id,
|
|
2264
|
+
name: record.name,
|
|
2265
|
+
payload: record.payload,
|
|
2266
|
+
status: record.status,
|
|
2267
|
+
attempts: record.attempts,
|
|
2268
|
+
createdAt: record.createdAt,
|
|
2269
|
+
...record.result !== void 0 ? { result: record.result } : {},
|
|
2270
|
+
...record.error !== void 0 ? { error: record.error } : {},
|
|
2271
|
+
...record.runAt !== void 0 ? { runAt: record.runAt } : {}
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
for (const local of records.values()) {
|
|
2275
|
+
if (name !== void 0 && local.name !== name) continue;
|
|
2276
|
+
merged.set(local.id, { ...local });
|
|
2277
|
+
}
|
|
2278
|
+
return [...merged.values()];
|
|
2279
|
+
},
|
|
2280
|
+
async cancel(name, id) {
|
|
2281
|
+
if (!driver.cancel) {
|
|
2282
|
+
throw new Error(
|
|
2283
|
+
"[faapi] Task driver does not support cancelling tasks (TaskDriver.cancel is not implemented)."
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
await driver.cancel(name, id);
|
|
2287
|
+
const record = records.get(id);
|
|
2288
|
+
if (record) record.status = "cancelled";
|
|
2289
|
+
},
|
|
2290
|
+
async retry(name, id) {
|
|
2291
|
+
if (!driver.retry) {
|
|
2292
|
+
throw new Error(
|
|
2293
|
+
"[faapi] Task driver does not support retrying tasks (TaskDriver.retry is not implemented)."
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
await driver.retry(name, id);
|
|
2297
|
+
const record = records.get(id);
|
|
2298
|
+
if (record) record.status = "pending";
|
|
2299
|
+
},
|
|
2300
|
+
async start() {
|
|
2301
|
+
if (stopped) {
|
|
2302
|
+
throw new Error("[faapi] Task queue is stopped and cannot be restarted");
|
|
2303
|
+
}
|
|
2304
|
+
if (started) return;
|
|
2305
|
+
started = true;
|
|
2306
|
+
await startWorkers();
|
|
2307
|
+
},
|
|
2308
|
+
async stop(timeoutMs = 1e4) {
|
|
2309
|
+
stopped = true;
|
|
2310
|
+
await driver.stop(timeoutMs);
|
|
2311
|
+
},
|
|
2312
|
+
async reload() {
|
|
2313
|
+
await driver.stopWorkers?.();
|
|
2314
|
+
await startWorkers();
|
|
2315
|
+
},
|
|
2316
|
+
invalidateModules() {
|
|
2317
|
+
moduleCache2.clear();
|
|
2318
|
+
},
|
|
2319
|
+
invalidateSchemas() {
|
|
2320
|
+
schemaCache.clear();
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
return queue;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// src/task/loadTaskDriver.ts
|
|
2327
|
+
async function loadTaskDriver(driver, driverOptions) {
|
|
2328
|
+
if (driver === void 0) {
|
|
2329
|
+
throw new Error(
|
|
2330
|
+
"[faapi] config.task.driver is required when tasks are defined. Install @faapi/task-pgboss or @faapi/task-bullmq and set `task.driver` to 'pgboss' or 'bullmq' (or pass a TaskDriver instance)."
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
if (driver === "memory") {
|
|
2334
|
+
throw new Error(
|
|
2335
|
+
"[faapi] The built-in memory task driver has been removed. Migrate to a persistent driver: install @faapi/task-pgboss or @faapi/task-bullmq and set `task.driver` to 'pgboss' or 'bullmq'."
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
if (typeof driver === "object") {
|
|
2339
|
+
return driver;
|
|
2340
|
+
}
|
|
2341
|
+
if (driver !== "pgboss" && driver !== "bullmq") {
|
|
2342
|
+
throw new Error(
|
|
2343
|
+
`[faapi] Unknown task driver "${driver}". Supported: 'pgboss', 'bullmq', or a TaskDriver instance.`
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
const specifier = `@faapi/task-${driver}`;
|
|
2347
|
+
let mod;
|
|
2348
|
+
try {
|
|
2349
|
+
mod = await import(
|
|
2350
|
+
/* @vite-ignore */
|
|
2351
|
+
specifier
|
|
2352
|
+
);
|
|
2353
|
+
} catch {
|
|
2354
|
+
throw new Error(
|
|
2355
|
+
`[faapi] Task driver "${driver}" requires package "${specifier}" to be installed in your project.`
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
const factoryName = driver === "pgboss" ? "createPgBossDriver" : "createBullMQDriver";
|
|
2359
|
+
const factory = mod[factoryName];
|
|
2360
|
+
if (typeof factory !== "function") {
|
|
2361
|
+
throw new Error(
|
|
2362
|
+
`[faapi] Package "${specifier}" does not export ${factoryName}() \u2014 check the package version.`
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
return factory(driverOptions);
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// src/task/cronScheduler.ts
|
|
2369
|
+
import { Cron } from "croner";
|
|
2370
|
+
function createCronScheduler(registry, enqueue) {
|
|
2371
|
+
let schedules = [];
|
|
2372
|
+
return {
|
|
2373
|
+
start() {
|
|
2374
|
+
this.stop();
|
|
2375
|
+
for (const task of registry.list()) {
|
|
2376
|
+
if (!task.cron) continue;
|
|
2377
|
+
const schedule = new Cron(task.cron, () => {
|
|
2378
|
+
const runAt = schedule.currentRun();
|
|
2379
|
+
void Promise.resolve().then(
|
|
2380
|
+
() => enqueue(task.name, {
|
|
2381
|
+
dedupId: runAt ? `cron:${task.name}:${runAt.toISOString()}` : void 0
|
|
2382
|
+
})
|
|
2383
|
+
).catch((err) => {
|
|
2384
|
+
console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
|
|
2385
|
+
});
|
|
2386
|
+
});
|
|
2387
|
+
schedules.push(schedule);
|
|
2388
|
+
}
|
|
2389
|
+
},
|
|
2390
|
+
stop() {
|
|
2391
|
+
for (const schedule of schedules) {
|
|
2392
|
+
schedule.stop();
|
|
2393
|
+
}
|
|
2394
|
+
schedules = [];
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
// src/task/scanTasks.ts
|
|
2400
|
+
import fg from "fast-glob";
|
|
2401
|
+
import path4 from "path";
|
|
2402
|
+
import fs2 from "fs";
|
|
2403
|
+
var TASK_PATTERNS = ["src/tasks/**/task.ts"];
|
|
2404
|
+
var TASK_FILENAME = "task.ts";
|
|
2405
|
+
var CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
|
|
2406
|
+
var CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
|
|
2407
|
+
var RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
|
|
2408
|
+
var TIMEOUT_MS_RE = /(?:^|\n)\s*timeoutMs:\s*(\d+)/;
|
|
2409
|
+
function filePathToTaskName(filePath) {
|
|
2410
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
2411
|
+
const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
|
|
2412
|
+
if (!match) {
|
|
2413
|
+
throw new Error(`[faapi] Invalid task file path: ${filePath}`);
|
|
2414
|
+
}
|
|
2415
|
+
return match[1].split("/").join(".");
|
|
2416
|
+
}
|
|
2417
|
+
async function scanTasks(rootDir, patterns) {
|
|
2418
|
+
const files = await fg(patterns, {
|
|
2419
|
+
cwd: rootDir,
|
|
2420
|
+
onlyFiles: true,
|
|
2421
|
+
absolute: false
|
|
2422
|
+
});
|
|
2423
|
+
const tasks = [];
|
|
2424
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2425
|
+
for (const file of files) {
|
|
2426
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
2427
|
+
const fileName = path4.posix.basename(normalizedFile);
|
|
2428
|
+
if (fileName !== TASK_FILENAME) {
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
const absPath = path4.resolve(rootDir, normalizedFile);
|
|
2432
|
+
const source = await fs2.promises.readFile(absPath, "utf8").catch(() => "");
|
|
2433
|
+
const name = filePathToTaskName(normalizedFile);
|
|
2434
|
+
const prevFile = seen.get(name);
|
|
2435
|
+
if (prevFile) {
|
|
2436
|
+
throw new Error(
|
|
2437
|
+
`Task conflict: "${name}" declared in both ${prevFile} and ${normalizedFile}`
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
seen.set(name, normalizedFile);
|
|
2441
|
+
const manifest = { name, filePath: normalizedFile };
|
|
2442
|
+
const cron = CRON_RE.exec(source)?.[1];
|
|
2443
|
+
const concurrency = CONCURRENCY_RE.exec(source)?.[1];
|
|
2444
|
+
const retries = RETRIES_RE.exec(source)?.[1];
|
|
2445
|
+
const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
|
|
2446
|
+
if (cron !== void 0) manifest.cron = cron;
|
|
2447
|
+
if (concurrency !== void 0) manifest.concurrency = Number(concurrency);
|
|
2448
|
+
if (retries !== void 0) manifest.retries = Number(retries);
|
|
2449
|
+
if (timeoutMs !== void 0) manifest.timeoutMs = Number(timeoutMs);
|
|
2450
|
+
tasks.push(manifest);
|
|
2451
|
+
}
|
|
2452
|
+
return tasks;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
1841
2455
|
// src/injection/agentRegistry.ts
|
|
1842
2456
|
function getAgent(name) {
|
|
1843
2457
|
return defaultRegistries.agent.getAgent(name);
|
|
@@ -1878,7 +2492,7 @@ function listSkills() {
|
|
|
1878
2492
|
}
|
|
1879
2493
|
|
|
1880
2494
|
// src/loader/loadAgentModule.ts
|
|
1881
|
-
import
|
|
2495
|
+
import fs10 from "fs";
|
|
1882
2496
|
|
|
1883
2497
|
// src/loader/resolveExports.ts
|
|
1884
2498
|
function resolveExport(module, exportName) {
|
|
@@ -1896,7 +2510,7 @@ function resolveExport(module, exportName) {
|
|
|
1896
2510
|
}
|
|
1897
2511
|
|
|
1898
2512
|
// src/utils/importWithCacheBust.ts
|
|
1899
|
-
import { pathToFileURL } from "url";
|
|
2513
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1900
2514
|
var loadTs;
|
|
1901
2515
|
function setLoadTimestamp(ts11) {
|
|
1902
2516
|
loadTs = ts11;
|
|
@@ -1910,13 +2524,13 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
|
1910
2524
|
const importActual = getVitestImportActual();
|
|
1911
2525
|
if (importActual) {
|
|
1912
2526
|
if (bustViteCache) {
|
|
1913
|
-
let url2 =
|
|
2527
|
+
let url2 = pathToFileURL2(filePath).href;
|
|
1914
2528
|
url2 += `?t=${Date.now()}`;
|
|
1915
2529
|
return await import(url2);
|
|
1916
2530
|
}
|
|
1917
2531
|
return await importActual(filePath);
|
|
1918
2532
|
}
|
|
1919
|
-
let url =
|
|
2533
|
+
let url = pathToFileURL2(filePath).href;
|
|
1920
2534
|
if (loadTs !== void 0) {
|
|
1921
2535
|
url += `?t=${loadTs}`;
|
|
1922
2536
|
}
|
|
@@ -1924,18 +2538,18 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
|
1924
2538
|
}
|
|
1925
2539
|
|
|
1926
2540
|
// src/cli/compileOnDemand.ts
|
|
1927
|
-
import
|
|
1928
|
-
import
|
|
2541
|
+
import path12 from "path";
|
|
2542
|
+
import fs9 from "fs";
|
|
1929
2543
|
|
|
1930
2544
|
// src/cli/compileSourceFiles.ts
|
|
1931
|
-
import
|
|
1932
|
-
import
|
|
1933
|
-
import
|
|
2545
|
+
import path8 from "path";
|
|
2546
|
+
import fs6 from "fs";
|
|
2547
|
+
import fg2 from "fast-glob";
|
|
1934
2548
|
|
|
1935
2549
|
// src/cli/aliasPlugin.ts
|
|
1936
2550
|
import ts7 from "typescript";
|
|
1937
|
-
import
|
|
1938
|
-
import
|
|
2551
|
+
import path7 from "path";
|
|
2552
|
+
import fs5 from "fs";
|
|
1939
2553
|
|
|
1940
2554
|
// src/utils/resolveAlias.ts
|
|
1941
2555
|
function resolveAlias(specifier, config) {
|
|
@@ -1961,8 +2575,8 @@ function resolveAlias(specifier, config) {
|
|
|
1961
2575
|
}
|
|
1962
2576
|
|
|
1963
2577
|
// src/utils/prodPaths.ts
|
|
1964
|
-
import
|
|
1965
|
-
import
|
|
2578
|
+
import path5 from "path";
|
|
2579
|
+
import fs3 from "fs";
|
|
1966
2580
|
var APP_DIR = "src";
|
|
1967
2581
|
var ROUTE_PATTERNS = ["src/api/**/*.ts"];
|
|
1968
2582
|
function toProdFilePath(filePath, dist) {
|
|
@@ -1981,27 +2595,27 @@ function toProdExtension(filePath) {
|
|
|
1981
2595
|
}
|
|
1982
2596
|
function toRealPath(p) {
|
|
1983
2597
|
try {
|
|
1984
|
-
return
|
|
2598
|
+
return fs3.realpathSync(p);
|
|
1985
2599
|
} catch {
|
|
1986
2600
|
return p;
|
|
1987
2601
|
}
|
|
1988
2602
|
}
|
|
1989
2603
|
function isInsideDir(filePath, dir) {
|
|
1990
|
-
const rel =
|
|
1991
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
2604
|
+
const rel = path5.relative(dir, filePath);
|
|
2605
|
+
return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
1992
2606
|
}
|
|
1993
2607
|
|
|
1994
2608
|
// src/utils/readTsconfig.ts
|
|
1995
2609
|
import ts6 from "typescript";
|
|
1996
|
-
import
|
|
1997
|
-
import
|
|
2610
|
+
import path6 from "path";
|
|
2611
|
+
import fs4 from "fs";
|
|
1998
2612
|
var tsconfigCache = /* @__PURE__ */ new Map();
|
|
1999
2613
|
function readTsconfig(rootDir) {
|
|
2000
|
-
const tsconfigPath =
|
|
2001
|
-
if (!
|
|
2614
|
+
const tsconfigPath = path6.resolve(rootDir, "tsconfig.json");
|
|
2615
|
+
if (!fs4.existsSync(tsconfigPath)) return null;
|
|
2002
2616
|
let mtimeMs;
|
|
2003
2617
|
try {
|
|
2004
|
-
mtimeMs =
|
|
2618
|
+
mtimeMs = fs4.statSync(tsconfigPath).mtimeMs;
|
|
2005
2619
|
} catch {
|
|
2006
2620
|
return null;
|
|
2007
2621
|
}
|
|
@@ -2017,7 +2631,7 @@ function readTsconfig(rootDir) {
|
|
|
2017
2631
|
const config = rawPaths ? (() => {
|
|
2018
2632
|
const paths = {};
|
|
2019
2633
|
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
2020
|
-
paths[pattern] = targets.map((t) =>
|
|
2634
|
+
paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
|
|
2021
2635
|
}
|
|
2022
2636
|
return { baseUrl, paths };
|
|
2023
2637
|
})() : null;
|
|
@@ -2027,26 +2641,26 @@ function readTsconfig(rootDir) {
|
|
|
2027
2641
|
|
|
2028
2642
|
// src/cli/aliasPlugin.ts
|
|
2029
2643
|
function toProdImportPath(sourceFile, importer) {
|
|
2030
|
-
const importerDir =
|
|
2031
|
-
let rel =
|
|
2032
|
-
rel = rel.split(
|
|
2644
|
+
const importerDir = path7.dirname(importer);
|
|
2645
|
+
let rel = path7.relative(importerDir, sourceFile);
|
|
2646
|
+
rel = rel.split(path7.sep).join("/");
|
|
2033
2647
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2034
2648
|
return toProdExtension(rel);
|
|
2035
2649
|
}
|
|
2036
2650
|
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
2037
|
-
const appDirAbs = toRealPath(
|
|
2651
|
+
const appDirAbs = toRealPath(path7.resolve(rootDir, APP_DIR));
|
|
2038
2652
|
const sourceReal = toRealPath(sourceFile);
|
|
2039
|
-
let rel =
|
|
2040
|
-
rel = rel.split(
|
|
2653
|
+
let rel = path7.relative(appDirAbs, sourceReal);
|
|
2654
|
+
rel = rel.split(path7.sep).join("/");
|
|
2041
2655
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2042
2656
|
return toProdExtension(rel);
|
|
2043
2657
|
}
|
|
2044
2658
|
function toProdImportFromImporter(importer, rootDir, relFromDist) {
|
|
2045
|
-
const importerRel =
|
|
2046
|
-
const importerProdDir =
|
|
2659
|
+
const importerRel = path7.relative(toRealPath(path7.resolve(rootDir)), toRealPath(importer)).split(path7.sep).join("/");
|
|
2660
|
+
const importerProdDir = path7.posix.dirname(toProdExtension(importerRel));
|
|
2047
2661
|
if (importerProdDir === ".") return relFromDist;
|
|
2048
2662
|
const target = relFromDist.replace(/^\.\//, "");
|
|
2049
|
-
let rel =
|
|
2663
|
+
let rel = path7.posix.relative(importerProdDir, target);
|
|
2050
2664
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2051
2665
|
return rel;
|
|
2052
2666
|
}
|
|
@@ -2061,41 +2675,41 @@ var INDEX_EXTS = [
|
|
|
2061
2675
|
"/index.cjs"
|
|
2062
2676
|
];
|
|
2063
2677
|
function resolveRelativeSpecifier(importer, specifier) {
|
|
2064
|
-
const importerDir =
|
|
2065
|
-
const base =
|
|
2678
|
+
const importerDir = path7.dirname(importer);
|
|
2679
|
+
const base = path7.resolve(importerDir, specifier);
|
|
2066
2680
|
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2067
|
-
if (
|
|
2681
|
+
if (fs5.existsSync(base)) return base;
|
|
2068
2682
|
if (base.endsWith(".js")) {
|
|
2069
2683
|
for (const ext of [".ts", ".tsx", ".jsx"]) {
|
|
2070
2684
|
const file = base.slice(0, -3) + ext;
|
|
2071
|
-
if (
|
|
2685
|
+
if (fs5.existsSync(file)) return file;
|
|
2072
2686
|
}
|
|
2073
2687
|
}
|
|
2074
2688
|
return null;
|
|
2075
2689
|
}
|
|
2076
2690
|
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
2077
|
-
return
|
|
2691
|
+
return fs5.existsSync(base) ? base : null;
|
|
2078
2692
|
}
|
|
2079
2693
|
for (const ext of SOURCE_EXTS) {
|
|
2080
2694
|
const file = base + ext;
|
|
2081
|
-
if (
|
|
2695
|
+
if (fs5.existsSync(file)) return file;
|
|
2082
2696
|
}
|
|
2083
2697
|
for (const indexExt of INDEX_EXTS) {
|
|
2084
2698
|
const file = base + indexExt;
|
|
2085
|
-
if (
|
|
2699
|
+
if (fs5.existsSync(file)) return file;
|
|
2086
2700
|
}
|
|
2087
2701
|
return null;
|
|
2088
2702
|
}
|
|
2089
2703
|
function createAliasPlugin(config, options) {
|
|
2090
|
-
const appDirAbs = options?.rootDir ? toRealPath(
|
|
2704
|
+
const appDirAbs = options?.rootDir ? toRealPath(path7.resolve(options.rootDir, APP_DIR)) : null;
|
|
2091
2705
|
const probeCandidateFile = (candidate) => {
|
|
2092
2706
|
for (const ext of SOURCE_EXTS) {
|
|
2093
2707
|
const file = candidate + ext;
|
|
2094
|
-
if (
|
|
2708
|
+
if (fs5.existsSync(file)) return file;
|
|
2095
2709
|
}
|
|
2096
2710
|
for (const indexExt of INDEX_EXTS) {
|
|
2097
2711
|
const file = candidate + indexExt;
|
|
2098
|
-
if (
|
|
2712
|
+
if (fs5.existsSync(file)) return file;
|
|
2099
2713
|
}
|
|
2100
2714
|
return null;
|
|
2101
2715
|
};
|
|
@@ -2105,7 +2719,7 @@ function createAliasPlugin(config, options) {
|
|
|
2105
2719
|
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
2106
2720
|
let source;
|
|
2107
2721
|
try {
|
|
2108
|
-
source =
|
|
2722
|
+
source = fs5.readFileSync(args.path, "utf8");
|
|
2109
2723
|
} catch {
|
|
2110
2724
|
return void 0;
|
|
2111
2725
|
}
|
|
@@ -2221,7 +2835,7 @@ function buildAliasPlugins(rootDir) {
|
|
|
2221
2835
|
// src/cli/compileSourceFiles.ts
|
|
2222
2836
|
async function compileSourceFiles(options) {
|
|
2223
2837
|
const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
|
|
2224
|
-
const entryPoints = files ?? await
|
|
2838
|
+
const entryPoints = files ?? await fg2([`${APP_DIR}/**/*.ts`], {
|
|
2225
2839
|
cwd: rootDir,
|
|
2226
2840
|
onlyFiles: true,
|
|
2227
2841
|
absolute: true,
|
|
@@ -2230,11 +2844,11 @@ async function compileSourceFiles(options) {
|
|
|
2230
2844
|
if (entryPoints.length === 0) {
|
|
2231
2845
|
return { compiledFiles: [] };
|
|
2232
2846
|
}
|
|
2233
|
-
const absDist =
|
|
2234
|
-
await
|
|
2847
|
+
const absDist = path8.resolve(rootDir, dist);
|
|
2848
|
+
await fs6.promises.mkdir(absDist, { recursive: true });
|
|
2235
2849
|
const plugins = buildAliasPlugins(rootDir);
|
|
2236
2850
|
const esbuild = await import("esbuild");
|
|
2237
|
-
const outbase =
|
|
2851
|
+
const outbase = path8.resolve(rootDir, APP_DIR);
|
|
2238
2852
|
const result = await esbuild.build({
|
|
2239
2853
|
entryPoints,
|
|
2240
2854
|
outdir: absDist,
|
|
@@ -2254,10 +2868,10 @@ async function compileSourceFiles(options) {
|
|
|
2254
2868
|
if (atomicWrite && result.outputFiles) {
|
|
2255
2869
|
await Promise.all(
|
|
2256
2870
|
result.outputFiles.map(async (file) => {
|
|
2257
|
-
await
|
|
2871
|
+
await fs6.promises.mkdir(path8.dirname(file.path), { recursive: true });
|
|
2258
2872
|
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2259
|
-
await
|
|
2260
|
-
await
|
|
2873
|
+
await fs6.promises.writeFile(tmp, file.contents);
|
|
2874
|
+
await fs6.promises.rename(tmp, file.path);
|
|
2261
2875
|
})
|
|
2262
2876
|
);
|
|
2263
2877
|
}
|
|
@@ -2274,8 +2888,8 @@ init_generateSchemaFiles();
|
|
|
2274
2888
|
init_generateSchemaFiles();
|
|
2275
2889
|
|
|
2276
2890
|
// src/cli/collectImports.ts
|
|
2277
|
-
import
|
|
2278
|
-
import
|
|
2891
|
+
import path11 from "path";
|
|
2892
|
+
import fs8 from "fs";
|
|
2279
2893
|
var SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
2280
2894
|
function extractImportSpecifiers(source) {
|
|
2281
2895
|
const specifiers = [];
|
|
@@ -2287,10 +2901,10 @@ function extractImportSpecifiers(source) {
|
|
|
2287
2901
|
return specifiers;
|
|
2288
2902
|
}
|
|
2289
2903
|
function resolveSpecifierFromDir(dir, specifier) {
|
|
2290
|
-
return resolveRelativeSpecifier(
|
|
2904
|
+
return resolveRelativeSpecifier(path11.join(dir, "__faapi_probe__.ts"), specifier);
|
|
2291
2905
|
}
|
|
2292
2906
|
async function collectRelativeImports(entryFiles, rootDir) {
|
|
2293
|
-
const appDirAbs = toRealPath(
|
|
2907
|
+
const appDirAbs = toRealPath(path11.resolve(rootDir, "src"));
|
|
2294
2908
|
const tsconfig = readTsconfig(rootDir);
|
|
2295
2909
|
const visited = /* @__PURE__ */ new Set();
|
|
2296
2910
|
const insideFiles = /* @__PURE__ */ new Set();
|
|
@@ -2300,7 +2914,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
2300
2914
|
visited.add(filePath);
|
|
2301
2915
|
let source;
|
|
2302
2916
|
try {
|
|
2303
|
-
source = await
|
|
2917
|
+
source = await fs8.promises.readFile(filePath, "utf8");
|
|
2304
2918
|
} catch {
|
|
2305
2919
|
return;
|
|
2306
2920
|
}
|
|
@@ -2322,7 +2936,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
2322
2936
|
resolved = null;
|
|
2323
2937
|
}
|
|
2324
2938
|
if (!resolved) continue;
|
|
2325
|
-
if (!isInsideDir(toRealPath(resolved), toRealPath(
|
|
2939
|
+
if (!isInsideDir(toRealPath(resolved), toRealPath(path11.resolve(rootDir)))) continue;
|
|
2326
2940
|
if (isInsideDir(toRealPath(resolved), appDirAbs)) {
|
|
2327
2941
|
insideFiles.add(resolved);
|
|
2328
2942
|
} else {
|
|
@@ -2343,8 +2957,8 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
2343
2957
|
// src/cli/compileOnDemand.ts
|
|
2344
2958
|
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
2345
2959
|
try {
|
|
2346
|
-
const srcStat =
|
|
2347
|
-
const prodStat =
|
|
2960
|
+
const srcStat = fs9.statSync(sourceAbsPath);
|
|
2961
|
+
const prodStat = fs9.statSync(productAbsPath);
|
|
2348
2962
|
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
2349
2963
|
} catch {
|
|
2350
2964
|
return false;
|
|
@@ -2376,7 +2990,7 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
|
2376
2990
|
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
2377
2991
|
return false;
|
|
2378
2992
|
}
|
|
2379
|
-
if (!
|
|
2993
|
+
if (!fs9.existsSync(sourceAbsPath)) {
|
|
2380
2994
|
return false;
|
|
2381
2995
|
}
|
|
2382
2996
|
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
@@ -2426,11 +3040,11 @@ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
|
|
|
2426
3040
|
}
|
|
2427
3041
|
}
|
|
2428
3042
|
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2429
|
-
const rel =
|
|
3043
|
+
const rel = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2430
3044
|
if (!rel.startsWith("src/")) return null;
|
|
2431
3045
|
const relWithoutSrc = rel.slice(4);
|
|
2432
3046
|
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2433
|
-
return
|
|
3047
|
+
return path12.resolve(rootDir, dist, jsRel);
|
|
2434
3048
|
}
|
|
2435
3049
|
function clearGeneratedSchemas() {
|
|
2436
3050
|
state.generatedSchemas.clear();
|
|
@@ -2446,9 +3060,9 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
2446
3060
|
if (state.generatedSchemas.has(schemaPath)) {
|
|
2447
3061
|
return false;
|
|
2448
3062
|
}
|
|
2449
|
-
const prodAbsPath =
|
|
3063
|
+
const prodAbsPath = path12.resolve(rootDir, routeFilePath);
|
|
2450
3064
|
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
2451
|
-
if (!
|
|
3065
|
+
if (!fs9.existsSync(sourceAbsPath)) {
|
|
2452
3066
|
return false;
|
|
2453
3067
|
}
|
|
2454
3068
|
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
@@ -2459,7 +3073,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
2459
3073
|
if (fileRoutes.length === 0) {
|
|
2460
3074
|
return false;
|
|
2461
3075
|
}
|
|
2462
|
-
const sourceRelPath =
|
|
3076
|
+
const sourceRelPath = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2463
3077
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2464
3078
|
const generatePromise = (async () => {
|
|
2465
3079
|
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
@@ -2480,7 +3094,7 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
|
2480
3094
|
if (deleted.has(schemaPath)) continue;
|
|
2481
3095
|
deleted.add(schemaPath);
|
|
2482
3096
|
try {
|
|
2483
|
-
await
|
|
3097
|
+
await fs9.promises.unlink(schemaPath);
|
|
2484
3098
|
} catch {
|
|
2485
3099
|
}
|
|
2486
3100
|
}
|
|
@@ -2489,19 +3103,19 @@ var sourcePathCache = /* @__PURE__ */ new Map();
|
|
|
2489
3103
|
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2490
3104
|
const cached = sourcePathCache.get(prodAbsPath);
|
|
2491
3105
|
if (cached) return cached;
|
|
2492
|
-
const rel =
|
|
3106
|
+
const rel = path12.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
2493
3107
|
let relWithoutDist = rel;
|
|
2494
3108
|
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
2495
3109
|
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
2496
3110
|
}
|
|
2497
3111
|
const srcRel = `src/${relWithoutDist}`;
|
|
2498
3112
|
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2499
|
-
const tsAbs =
|
|
3113
|
+
const tsAbs = path12.resolve(rootDir, tsRel);
|
|
2500
3114
|
let result;
|
|
2501
|
-
if (
|
|
3115
|
+
if (fs9.existsSync(tsAbs)) {
|
|
2502
3116
|
result = tsAbs;
|
|
2503
3117
|
} else {
|
|
2504
|
-
result =
|
|
3118
|
+
result = path12.resolve(rootDir, srcRel);
|
|
2505
3119
|
}
|
|
2506
3120
|
sourcePathCache.set(prodAbsPath, result);
|
|
2507
3121
|
return result;
|
|
@@ -2519,7 +3133,7 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
|
2519
3133
|
const dist = getDevDist();
|
|
2520
3134
|
if (dist) {
|
|
2521
3135
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2522
|
-
if (sourcePath &&
|
|
3136
|
+
if (sourcePath && fs10.existsSync(sourcePath)) {
|
|
2523
3137
|
try {
|
|
2524
3138
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2525
3139
|
} catch (compileErr) {
|
|
@@ -2552,13 +3166,13 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
|
2552
3166
|
}
|
|
2553
3167
|
|
|
2554
3168
|
// src/loader/loadToolModule.ts
|
|
2555
|
-
import
|
|
3169
|
+
import fs11 from "fs";
|
|
2556
3170
|
async function loadToolModule(filePath, functionName, rootDir) {
|
|
2557
3171
|
if (isDevOnDemandEnabled() && rootDir) {
|
|
2558
3172
|
const dist = getDevDist();
|
|
2559
3173
|
if (dist) {
|
|
2560
3174
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2561
|
-
if (sourcePath &&
|
|
3175
|
+
if (sourcePath && fs11.existsSync(sourcePath)) {
|
|
2562
3176
|
try {
|
|
2563
3177
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2564
3178
|
} catch (compileErr) {
|
|
@@ -2590,7 +3204,7 @@ async function loadToolModule(filePath, functionName, rootDir) {
|
|
|
2590
3204
|
import { existsSync as existsSync2 } from "fs";
|
|
2591
3205
|
|
|
2592
3206
|
// src/cli/generateToolArtifacts.ts
|
|
2593
|
-
import
|
|
3207
|
+
import path13 from "path";
|
|
2594
3208
|
import { existsSync } from "fs";
|
|
2595
3209
|
|
|
2596
3210
|
// src/ast/extractToolMetadata.ts
|
|
@@ -2714,7 +3328,7 @@ function collectToolSchemaSources(tools, rootDir) {
|
|
|
2714
3328
|
const toolsByFile = /* @__PURE__ */ new Map();
|
|
2715
3329
|
for (const tool of tools) {
|
|
2716
3330
|
if (!tool.inputTypeName) continue;
|
|
2717
|
-
const absPath =
|
|
3331
|
+
const absPath = path13.resolve(rootDir, tool.filePath);
|
|
2718
3332
|
let list = toolsByFile.get(absPath);
|
|
2719
3333
|
if (!list) {
|
|
2720
3334
|
list = [];
|
|
@@ -2774,15 +3388,15 @@ function generateToolSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
|
2774
3388
|
}
|
|
2775
3389
|
async function maybeGenerateHelpers(allSourceCode, distDir) {
|
|
2776
3390
|
if (!usesCoerceHelpers(allSourceCode)) return;
|
|
2777
|
-
const helpersPath =
|
|
3391
|
+
const helpersPath = path13.resolve(distDir, HELPERS_FILENAME);
|
|
2778
3392
|
if (existsSync(helpersPath)) return;
|
|
2779
3393
|
await atomicWriteFile(helpersPath, generateHelpersFileSource());
|
|
2780
3394
|
}
|
|
2781
3395
|
async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
2782
3396
|
const metadata = [];
|
|
2783
|
-
const programByFile = createPrograms(tools.map((m) =>
|
|
3397
|
+
const programByFile = createPrograms(tools.map((m) => path13.resolve(rootDir, m.filePath)));
|
|
2784
3398
|
for (const manifest of tools) {
|
|
2785
|
-
const absPath =
|
|
3399
|
+
const absPath = path13.resolve(rootDir, manifest.filePath);
|
|
2786
3400
|
const program = programByFile.get(absPath);
|
|
2787
3401
|
const result = extractToolMetadata(program, absPath, manifest.functionName, {
|
|
2788
3402
|
name: manifest.name,
|
|
@@ -2793,7 +3407,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2793
3407
|
}
|
|
2794
3408
|
}
|
|
2795
3409
|
const serialized = serializeTools(metadata, dist);
|
|
2796
|
-
const toolsPath =
|
|
3410
|
+
const toolsPath = path13.resolve(rootDir, dist, TOOLS_FILE);
|
|
2797
3411
|
await writeToolsModule(serialized, toolsPath);
|
|
2798
3412
|
if (options?.skipSchema) {
|
|
2799
3413
|
return metadata;
|
|
@@ -2816,7 +3430,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2816
3430
|
}
|
|
2817
3431
|
const fileEntries = [];
|
|
2818
3432
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
2819
|
-
const relFile =
|
|
3433
|
+
const relFile = path13.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2820
3434
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
2821
3435
|
const resolver = resolversByFile.get(filePath);
|
|
2822
3436
|
let relForDir = relFile;
|
|
@@ -2834,7 +3448,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
2834
3448
|
fileEntries.push({ outputPath, source });
|
|
2835
3449
|
}
|
|
2836
3450
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2837
|
-
const distDir =
|
|
3451
|
+
const distDir = path13.resolve(rootDir, dist);
|
|
2838
3452
|
await maybeGenerateHelpers(allSourceCode, distDir);
|
|
2839
3453
|
await Promise.all(
|
|
2840
3454
|
fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
|
|
@@ -3043,16 +3657,16 @@ function helmet(options = {}) {
|
|
|
3043
3657
|
}
|
|
3044
3658
|
|
|
3045
3659
|
// src/config/loadConfig.ts
|
|
3046
|
-
import
|
|
3047
|
-
import
|
|
3660
|
+
import path14 from "path";
|
|
3661
|
+
import fs12 from "fs";
|
|
3048
3662
|
var CONFIG_PRODUCT_FILE = "faapi-config.js";
|
|
3049
3663
|
async function loadConfig(rootDir, dist) {
|
|
3050
|
-
const configProductPath =
|
|
3051
|
-
if (
|
|
3664
|
+
const configProductPath = path14.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
|
|
3665
|
+
if (fs12.existsSync(configProductPath)) {
|
|
3052
3666
|
const module = await importWithCacheBust(configProductPath);
|
|
3053
3667
|
return module.default ?? {};
|
|
3054
3668
|
}
|
|
3055
|
-
const hasSourceConfig =
|
|
3669
|
+
const hasSourceConfig = fs12.existsSync(path14.join(rootDir, "faapi.config.ts")) || fs12.existsSync(path14.join(rootDir, "faapi.config.js"));
|
|
3056
3670
|
if (hasSourceConfig) {
|
|
3057
3671
|
throw new Error(
|
|
3058
3672
|
`[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`
|
|
@@ -3062,8 +3676,8 @@ async function loadConfig(rootDir, dist) {
|
|
|
3062
3676
|
}
|
|
3063
3677
|
|
|
3064
3678
|
// src/cli/loadEnv.ts
|
|
3065
|
-
import
|
|
3066
|
-
import
|
|
3679
|
+
import fs13 from "fs";
|
|
3680
|
+
import path15 from "path";
|
|
3067
3681
|
function resolveEnv() {
|
|
3068
3682
|
return process.env.NODE_ENV || "development";
|
|
3069
3683
|
}
|
|
@@ -3130,9 +3744,9 @@ function loadEnv(rootDir) {
|
|
|
3130
3744
|
const files = getEnvFiles(env);
|
|
3131
3745
|
const merged = {};
|
|
3132
3746
|
for (const file of files) {
|
|
3133
|
-
const filePath =
|
|
3134
|
-
if (!
|
|
3135
|
-
const content =
|
|
3747
|
+
const filePath = path15.join(rootDir, file);
|
|
3748
|
+
if (!fs13.existsSync(filePath)) continue;
|
|
3749
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
3136
3750
|
const parsed = parseEnvFile(content, merged);
|
|
3137
3751
|
Object.assign(merged, parsed);
|
|
3138
3752
|
}
|
|
@@ -3143,67 +3757,9 @@ function loadEnv(rootDir) {
|
|
|
3143
3757
|
}
|
|
3144
3758
|
}
|
|
3145
3759
|
|
|
3146
|
-
// src/errors/FaapiError.ts
|
|
3147
|
-
var FaapiError = class extends Error {
|
|
3148
|
-
constructor(code, message, statusCode) {
|
|
3149
|
-
super(message);
|
|
3150
|
-
this.code = code;
|
|
3151
|
-
this.statusCode = statusCode;
|
|
3152
|
-
this.name = "FaapiError";
|
|
3153
|
-
}
|
|
3154
|
-
code;
|
|
3155
|
-
statusCode;
|
|
3156
|
-
};
|
|
3157
|
-
|
|
3158
|
-
// src/errors/httpErrors.ts
|
|
3159
|
-
function deriveStatusCode(issues) {
|
|
3160
|
-
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
3161
|
-
return has400 ? 400 : 422;
|
|
3162
|
-
}
|
|
3163
|
-
var ValidationError = class extends FaapiError {
|
|
3164
|
-
constructor(message, issues) {
|
|
3165
|
-
super("VALIDATION_ERROR", message, deriveStatusCode(issues));
|
|
3166
|
-
this.issues = issues;
|
|
3167
|
-
this.name = "ValidationError";
|
|
3168
|
-
}
|
|
3169
|
-
issues;
|
|
3170
|
-
};
|
|
3171
|
-
var RouteNotFoundError = class extends FaapiError {
|
|
3172
|
-
constructor(path24) {
|
|
3173
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${path24}`, 404);
|
|
3174
|
-
this.name = "RouteNotFoundError";
|
|
3175
|
-
}
|
|
3176
|
-
};
|
|
3177
|
-
var MethodNotAllowedError = class extends FaapiError {
|
|
3178
|
-
constructor(method, path24, allowedMethods) {
|
|
3179
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path24}`, 405);
|
|
3180
|
-
this.allowedMethods = allowedMethods;
|
|
3181
|
-
this.name = "MethodNotAllowedError";
|
|
3182
|
-
}
|
|
3183
|
-
allowedMethods;
|
|
3184
|
-
};
|
|
3185
|
-
var InternalError = class extends FaapiError {
|
|
3186
|
-
constructor(message) {
|
|
3187
|
-
super("INTERNAL_ERROR", message, 500);
|
|
3188
|
-
this.name = "InternalError";
|
|
3189
|
-
}
|
|
3190
|
-
};
|
|
3191
|
-
var ModuleLoadError = class extends FaapiError {
|
|
3192
|
-
constructor(filePath, reason) {
|
|
3193
|
-
super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
|
|
3194
|
-
this.name = "ModuleLoadError";
|
|
3195
|
-
}
|
|
3196
|
-
};
|
|
3197
|
-
var PayloadTooLargeError = class extends FaapiError {
|
|
3198
|
-
constructor(maxSize) {
|
|
3199
|
-
super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
|
|
3200
|
-
this.name = "PayloadTooLargeError";
|
|
3201
|
-
}
|
|
3202
|
-
};
|
|
3203
|
-
|
|
3204
3760
|
// src/cli/createAppCore.ts
|
|
3205
|
-
import
|
|
3206
|
-
import
|
|
3761
|
+
import fs18 from "fs";
|
|
3762
|
+
import path23 from "path";
|
|
3207
3763
|
import { PassThrough, Readable as Readable3 } from "stream";
|
|
3208
3764
|
|
|
3209
3765
|
// src/router/sortRoutes.ts
|
|
@@ -3256,7 +3812,7 @@ import {
|
|
|
3256
3812
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
3257
3813
|
import { readFileSync } from "fs";
|
|
3258
3814
|
import { Readable as Readable2 } from "stream";
|
|
3259
|
-
import
|
|
3815
|
+
import path17 from "path";
|
|
3260
3816
|
|
|
3261
3817
|
// src/router/matchRoute.ts
|
|
3262
3818
|
var httpIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -3298,18 +3854,18 @@ function getWsIndex(routes) {
|
|
|
3298
3854
|
wsIndexCache.set(routes, index);
|
|
3299
3855
|
return index;
|
|
3300
3856
|
}
|
|
3301
|
-
function matchRoute(routes, method,
|
|
3857
|
+
function matchRoute(routes, method, path28) {
|
|
3302
3858
|
const index = getHttpIndex(routes);
|
|
3303
3859
|
const upper = method.toUpperCase();
|
|
3304
|
-
const hit = matchByMethod(index, upper,
|
|
3860
|
+
const hit = matchByMethod(index, upper, path28);
|
|
3305
3861
|
if (hit) return hit;
|
|
3306
3862
|
if (upper === "HEAD") {
|
|
3307
|
-
return matchByMethod(index, "GET",
|
|
3863
|
+
return matchByMethod(index, "GET", path28);
|
|
3308
3864
|
}
|
|
3309
3865
|
return null;
|
|
3310
3866
|
}
|
|
3311
|
-
function matchByMethod(index, method,
|
|
3312
|
-
const staticHit = index.static.get(`${method}|${
|
|
3867
|
+
function matchByMethod(index, method, path28) {
|
|
3868
|
+
const staticHit = index.static.get(`${method}|${path28}`);
|
|
3313
3869
|
if (staticHit) {
|
|
3314
3870
|
return { route: staticHit, params: {} };
|
|
3315
3871
|
}
|
|
@@ -3318,31 +3874,31 @@ function matchByMethod(index, method, path24) {
|
|
|
3318
3874
|
if (route.method !== method) {
|
|
3319
3875
|
continue;
|
|
3320
3876
|
}
|
|
3321
|
-
const params = matchSegments(entry.segments,
|
|
3877
|
+
const params = matchSegments(entry.segments, path28, route.paramNames, route.isCatchAll);
|
|
3322
3878
|
if (params !== null) {
|
|
3323
3879
|
return { route, params };
|
|
3324
3880
|
}
|
|
3325
3881
|
}
|
|
3326
3882
|
return null;
|
|
3327
3883
|
}
|
|
3328
|
-
function matchWsRoute(wsRoutes,
|
|
3884
|
+
function matchWsRoute(wsRoutes, path28) {
|
|
3329
3885
|
const index = getWsIndex(wsRoutes);
|
|
3330
|
-
const staticHit = index.static.get(
|
|
3886
|
+
const staticHit = index.static.get(path28);
|
|
3331
3887
|
if (staticHit) {
|
|
3332
3888
|
return { route: staticHit, params: {} };
|
|
3333
3889
|
}
|
|
3334
3890
|
for (const route of index.dynamics) {
|
|
3335
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3891
|
+
const params = matchDynamicPath(route.urlPath, path28, route.paramNames, route.isCatchAll);
|
|
3336
3892
|
if (params !== null) {
|
|
3337
3893
|
return { route, params };
|
|
3338
3894
|
}
|
|
3339
3895
|
}
|
|
3340
3896
|
return null;
|
|
3341
3897
|
}
|
|
3342
|
-
function findAllowedMethods(routes,
|
|
3898
|
+
function findAllowedMethods(routes, path28) {
|
|
3343
3899
|
const index = getHttpIndex(routes);
|
|
3344
3900
|
const methods = /* @__PURE__ */ new Set();
|
|
3345
|
-
const staticMethods = index.methodsByStaticPath.get(
|
|
3901
|
+
const staticMethods = index.methodsByStaticPath.get(path28);
|
|
3346
3902
|
if (staticMethods) {
|
|
3347
3903
|
for (const method of staticMethods) {
|
|
3348
3904
|
methods.add(method);
|
|
@@ -3351,7 +3907,7 @@ function findAllowedMethods(routes, path24) {
|
|
|
3351
3907
|
for (const entry of index.dynamics) {
|
|
3352
3908
|
const params = matchSegments(
|
|
3353
3909
|
entry.segments,
|
|
3354
|
-
|
|
3910
|
+
path28,
|
|
3355
3911
|
entry.route.paramNames,
|
|
3356
3912
|
entry.route.isCatchAll
|
|
3357
3913
|
);
|
|
@@ -3364,11 +3920,11 @@ function findAllowedMethods(routes, path24) {
|
|
|
3364
3920
|
}
|
|
3365
3921
|
return Array.from(methods);
|
|
3366
3922
|
}
|
|
3367
|
-
function matchDynamicPath(pattern,
|
|
3368
|
-
return matchSegments(pattern.split("/").filter(Boolean),
|
|
3923
|
+
function matchDynamicPath(pattern, path28, paramNames, isCatchAll) {
|
|
3924
|
+
return matchSegments(pattern.split("/").filter(Boolean), path28, paramNames, isCatchAll);
|
|
3369
3925
|
}
|
|
3370
|
-
function matchSegments(patternSegments,
|
|
3371
|
-
const pathSegments =
|
|
3926
|
+
function matchSegments(patternSegments, path28, paramNames, isCatchAll) {
|
|
3927
|
+
const pathSegments = path28.split("/").filter(Boolean);
|
|
3372
3928
|
if (isCatchAll) {
|
|
3373
3929
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
3374
3930
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -3773,6 +4329,7 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
3773
4329
|
};
|
|
3774
4330
|
if (registries) {
|
|
3775
4331
|
ctx.registries = registries;
|
|
4332
|
+
ctx.tasks = registries.taskHandle.get(ctx);
|
|
3776
4333
|
}
|
|
3777
4334
|
const extend = config?.extendContext;
|
|
3778
4335
|
if (typeof extend === "function") {
|
|
@@ -4062,6 +4619,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
|
|
|
4062
4619
|
// Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
|
|
4063
4620
|
case "agent":
|
|
4064
4621
|
return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);
|
|
4622
|
+
// 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
|
|
4623
|
+
case "tasks":
|
|
4624
|
+
return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
|
|
4065
4625
|
default:
|
|
4066
4626
|
return void 0;
|
|
4067
4627
|
}
|
|
@@ -4256,9 +4816,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
4256
4816
|
function mapZodIssues(error) {
|
|
4257
4817
|
return error.issues.map((issue) => {
|
|
4258
4818
|
const code = mapZodCode(issue);
|
|
4259
|
-
const
|
|
4819
|
+
const path28 = issue.path.map(String).join(".") || "";
|
|
4260
4820
|
return {
|
|
4261
|
-
path:
|
|
4821
|
+
path: path28,
|
|
4262
4822
|
code,
|
|
4263
4823
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
4264
4824
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -4490,9 +5050,9 @@ function etag(options = {}) {
|
|
|
4490
5050
|
}
|
|
4491
5051
|
|
|
4492
5052
|
// src/server/handleWsUpgrade.ts
|
|
4493
|
-
import
|
|
5053
|
+
import fs14 from "fs";
|
|
4494
5054
|
import { WebSocketServer, WebSocket } from "ws";
|
|
4495
|
-
import
|
|
5055
|
+
import path16 from "path";
|
|
4496
5056
|
|
|
4497
5057
|
// src/server/serverUtils.ts
|
|
4498
5058
|
function nodeHttpToWebHeaders(req) {
|
|
@@ -4627,7 +5187,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
4627
5187
|
const dist = getDevDist();
|
|
4628
5188
|
if (dist) {
|
|
4629
5189
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
4630
|
-
if (sourcePath &&
|
|
5190
|
+
if (sourcePath && fs14.existsSync(sourcePath)) {
|
|
4631
5191
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
4632
5192
|
}
|
|
4633
5193
|
}
|
|
@@ -4741,7 +5301,7 @@ function attachWebSocket(options) {
|
|
|
4741
5301
|
const finalHandler = async () => {
|
|
4742
5302
|
let handlers;
|
|
4743
5303
|
try {
|
|
4744
|
-
const absoluteFilePath =
|
|
5304
|
+
const absoluteFilePath = path16.resolve(rootDir, route.filePath);
|
|
4745
5305
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
4746
5306
|
} catch (err) {
|
|
4747
5307
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -4994,7 +5554,7 @@ function getRoutePaths(route, rootDir, dist) {
|
|
|
4994
5554
|
let cached = routePathCache.get(route);
|
|
4995
5555
|
if (!cached) {
|
|
4996
5556
|
cached = {
|
|
4997
|
-
absFilePath:
|
|
5557
|
+
absFilePath: path17.resolve(rootDir, route.filePath),
|
|
4998
5558
|
schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
|
|
4999
5559
|
};
|
|
5000
5560
|
routePathCache.set(route, cached);
|
|
@@ -5116,8 +5676,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
5116
5676
|
}
|
|
5117
5677
|
|
|
5118
5678
|
// src/cli/generateRoutes.ts
|
|
5119
|
-
import
|
|
5120
|
-
import
|
|
5679
|
+
import fs15 from "fs";
|
|
5680
|
+
import path18 from "path";
|
|
5121
5681
|
async function hydrateRoutes(manifest) {
|
|
5122
5682
|
const hydrateRoute = (serialized) => ({
|
|
5123
5683
|
method: serialized.method,
|
|
@@ -5142,7 +5702,7 @@ async function hydrateRoutes(manifest) {
|
|
|
5142
5702
|
}
|
|
5143
5703
|
|
|
5144
5704
|
// src/cli/generateAgentArtifacts.ts
|
|
5145
|
-
import
|
|
5705
|
+
import path19 from "path";
|
|
5146
5706
|
|
|
5147
5707
|
// src/ast/extractAgentMetadata.ts
|
|
5148
5708
|
import ts10 from "typescript";
|
|
@@ -5415,9 +5975,9 @@ function validateAgentList(metadata) {
|
|
|
5415
5975
|
}
|
|
5416
5976
|
async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
5417
5977
|
const metadata = [];
|
|
5418
|
-
const programByFile = createPrograms(agents.map((m) =>
|
|
5978
|
+
const programByFile = createPrograms(agents.map((m) => path19.resolve(rootDir, m.filePath)));
|
|
5419
5979
|
for (const manifest of agents) {
|
|
5420
|
-
const absPath =
|
|
5980
|
+
const absPath = path19.resolve(rootDir, manifest.filePath);
|
|
5421
5981
|
const program = programByFile.get(absPath);
|
|
5422
5982
|
const result = extractAgentMetadata(program, absPath, {
|
|
5423
5983
|
name: manifest.name,
|
|
@@ -5433,24 +5993,202 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
|
5433
5993
|
}
|
|
5434
5994
|
validateAgentList(metadata);
|
|
5435
5995
|
const serialized = serializeAgents(metadata, dist);
|
|
5436
|
-
const agentsPath =
|
|
5996
|
+
const agentsPath = path19.resolve(rootDir, dist, AGENTS_FILE);
|
|
5437
5997
|
await writeAgentsModule(serialized, agentsPath);
|
|
5438
5998
|
return metadata;
|
|
5439
5999
|
}
|
|
5440
6000
|
|
|
6001
|
+
// src/cli/generateTaskArtifacts.ts
|
|
6002
|
+
import path20 from "path";
|
|
6003
|
+
init_createProgram();
|
|
6004
|
+
init_atomicWrite();
|
|
6005
|
+
init_generateSchemaFiles();
|
|
6006
|
+
init_extractHandlerTypes();
|
|
6007
|
+
init_generateZodSchema();
|
|
6008
|
+
var TASKS_FILE = "faapi-tasks.js";
|
|
6009
|
+
function serializeTasks(tasks, dist = "dist") {
|
|
6010
|
+
return tasks.map((t) => ({
|
|
6011
|
+
name: t.name,
|
|
6012
|
+
filePath: toProdFilePath(t.filePath, dist),
|
|
6013
|
+
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
6014
|
+
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
6015
|
+
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
6016
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
|
|
6017
|
+
}));
|
|
6018
|
+
}
|
|
6019
|
+
async function writeTasksModule(manifest, outputPath) {
|
|
6020
|
+
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
6021
|
+
export const tasks = ${JSON.stringify(manifest, null, 2)};
|
|
6022
|
+
`;
|
|
6023
|
+
await atomicWriteFile(outputPath, content);
|
|
6024
|
+
}
|
|
6025
|
+
function hydrateTasks(manifest) {
|
|
6026
|
+
return manifest.map((t) => ({
|
|
6027
|
+
name: t.name,
|
|
6028
|
+
filePath: t.filePath,
|
|
6029
|
+
cron: t.cron ?? void 0,
|
|
6030
|
+
concurrency: t.concurrency ?? void 0,
|
|
6031
|
+
retries: t.retries ?? void 0,
|
|
6032
|
+
timeoutMs: t.timeoutMs ?? void 0
|
|
6033
|
+
}));
|
|
6034
|
+
}
|
|
6035
|
+
function collectTaskSchemaSources(tasks, rootDir) {
|
|
6036
|
+
const tasksByFile = /* @__PURE__ */ new Map();
|
|
6037
|
+
for (const task of tasks) {
|
|
6038
|
+
if (!task.inputTypeName) continue;
|
|
6039
|
+
const absPath = path20.resolve(rootDir, task.filePath);
|
|
6040
|
+
let list = tasksByFile.get(absPath);
|
|
6041
|
+
if (!list) {
|
|
6042
|
+
list = [];
|
|
6043
|
+
tasksByFile.set(absPath, list);
|
|
6044
|
+
}
|
|
6045
|
+
list.push(task);
|
|
6046
|
+
}
|
|
6047
|
+
const programByFile = createPrograms([...tasksByFile.keys()]);
|
|
6048
|
+
const resolversByFile = /* @__PURE__ */ new Map();
|
|
6049
|
+
for (const filePath of tasksByFile.keys()) {
|
|
6050
|
+
resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
|
|
6051
|
+
}
|
|
6052
|
+
const sources = [];
|
|
6053
|
+
for (const [filePath, fileTasks] of tasksByFile) {
|
|
6054
|
+
const program = programByFile.get(filePath);
|
|
6055
|
+
for (const task of fileTasks) {
|
|
6056
|
+
const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
|
|
6057
|
+
sources.push({
|
|
6058
|
+
name: task.taskName,
|
|
6059
|
+
filePath,
|
|
6060
|
+
schemaName: task.inputTypeName,
|
|
6061
|
+
typeInfo
|
|
6062
|
+
});
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
return { sources, resolversByFile };
|
|
6066
|
+
}
|
|
6067
|
+
function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
6068
|
+
const lines = ["import { z } from 'zod';"];
|
|
6069
|
+
const schemaBlocks = [];
|
|
6070
|
+
for (const source of sources) {
|
|
6071
|
+
if (!source.typeInfo) continue;
|
|
6072
|
+
const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
|
|
6073
|
+
const schemaCode = generateZodSchemaSource(
|
|
6074
|
+
source.typeInfo,
|
|
6075
|
+
resolveType,
|
|
6076
|
+
source.schemaName,
|
|
6077
|
+
false
|
|
6078
|
+
).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
|
|
6079
|
+
block.push(schemaCode);
|
|
6080
|
+
block.push("");
|
|
6081
|
+
schemaBlocks.push(block.join("\n"));
|
|
6082
|
+
}
|
|
6083
|
+
const allSchemaCode = schemaBlocks.join("\n");
|
|
6084
|
+
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
6085
|
+
lines.push(
|
|
6086
|
+
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
6087
|
+
);
|
|
6088
|
+
}
|
|
6089
|
+
lines.push("");
|
|
6090
|
+
lines.push(...schemaBlocks);
|
|
6091
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
6092
|
+
}
|
|
6093
|
+
async function generateTaskArtifacts(manifests, rootDir, dist) {
|
|
6094
|
+
const metadata = [];
|
|
6095
|
+
if (manifests.length > 0) {
|
|
6096
|
+
const programByFile = createPrograms(manifests.map((m) => path20.resolve(rootDir, m.filePath)));
|
|
6097
|
+
for (const manifest of manifests) {
|
|
6098
|
+
const absPath = path20.resolve(rootDir, manifest.filePath);
|
|
6099
|
+
const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
|
|
6100
|
+
name: manifest.name,
|
|
6101
|
+
filePath: manifest.filePath
|
|
6102
|
+
});
|
|
6103
|
+
if (result) {
|
|
6104
|
+
metadata.push({ ...result, taskName: manifest.name });
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
const serialized = serializeTasks(manifests, dist);
|
|
6109
|
+
const tasksPath = path20.resolve(rootDir, dist, TASKS_FILE);
|
|
6110
|
+
await writeTasksModule(serialized, tasksPath);
|
|
6111
|
+
if (metadata.length === 0) {
|
|
6112
|
+
return hydrateTasks(serialized);
|
|
6113
|
+
}
|
|
6114
|
+
const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
|
|
6115
|
+
if (sources.length === 0) {
|
|
6116
|
+
return hydrateTasks(serialized);
|
|
6117
|
+
}
|
|
6118
|
+
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
6119
|
+
for (const source of sources) {
|
|
6120
|
+
let list = sourcesByFile.get(source.filePath);
|
|
6121
|
+
if (!list) {
|
|
6122
|
+
list = [];
|
|
6123
|
+
sourcesByFile.set(source.filePath, list);
|
|
6124
|
+
}
|
|
6125
|
+
list.push(source);
|
|
6126
|
+
}
|
|
6127
|
+
const fileEntries = [];
|
|
6128
|
+
for (const [filePath, fileSources] of sourcesByFile) {
|
|
6129
|
+
const relFile = path20.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
6130
|
+
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
6131
|
+
const resolver = resolversByFile.get(filePath);
|
|
6132
|
+
let relForDir = relFile;
|
|
6133
|
+
if (relForDir.startsWith("src/")) {
|
|
6134
|
+
relForDir = relForDir.slice(4);
|
|
6135
|
+
}
|
|
6136
|
+
const dirIdx = relForDir.lastIndexOf("/");
|
|
6137
|
+
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
6138
|
+
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
6139
|
+
const source = generateTaskSchemaFileSource(
|
|
6140
|
+
fileSources,
|
|
6141
|
+
(name) => resolver?.resolve(name)?.runtimeType,
|
|
6142
|
+
helpersImportPath
|
|
6143
|
+
);
|
|
6144
|
+
fileEntries.push({ outputPath, source });
|
|
6145
|
+
}
|
|
6146
|
+
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
6147
|
+
if (usesCoerceHelpers(allSourceCode)) {
|
|
6148
|
+
const helpersPath = path20.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
6149
|
+
const { existsSync: existsSync3 } = await import("fs");
|
|
6150
|
+
if (!existsSync3(helpersPath)) {
|
|
6151
|
+
await atomicWriteFile(helpersPath, generateHelpersFileSource());
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
await Promise.all(
|
|
6155
|
+
fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
|
|
6156
|
+
);
|
|
6157
|
+
return hydrateTasks(serialized);
|
|
6158
|
+
}
|
|
6159
|
+
|
|
6160
|
+
// src/task/idleTaskDriver.ts
|
|
6161
|
+
function createIdleTaskDriver() {
|
|
6162
|
+
return {
|
|
6163
|
+
enqueue() {
|
|
6164
|
+
return Promise.reject(
|
|
6165
|
+
new Error(
|
|
6166
|
+
"[faapi] New task(s) were registered without a queue driver. Set config.task.driver to 'pgboss' or 'bullmq' (install the matching @faapi/task-* package) and restart the server."
|
|
6167
|
+
)
|
|
6168
|
+
);
|
|
6169
|
+
},
|
|
6170
|
+
startWorker() {
|
|
6171
|
+
},
|
|
6172
|
+
async stop() {
|
|
6173
|
+
},
|
|
6174
|
+
async stopWorkers() {
|
|
6175
|
+
}
|
|
6176
|
+
};
|
|
6177
|
+
}
|
|
6178
|
+
|
|
5441
6179
|
// src/cli/loadPlugins.ts
|
|
5442
|
-
import
|
|
5443
|
-
import
|
|
5444
|
-
import { pathToFileURL as
|
|
6180
|
+
import path22 from "path";
|
|
6181
|
+
import fs17 from "fs";
|
|
6182
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
5445
6183
|
|
|
5446
6184
|
// src/cli/compileConfig.ts
|
|
5447
|
-
import
|
|
5448
|
-
import
|
|
6185
|
+
import path21 from "path";
|
|
6186
|
+
import fs16 from "fs";
|
|
5449
6187
|
async function compileProjectModules(entryPoints, rootDir, dist) {
|
|
5450
6188
|
const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
|
|
5451
6189
|
const esbuild = await import("esbuild");
|
|
5452
6190
|
const aliasPlugins = buildAliasPlugins(rootDir);
|
|
5453
|
-
const absDist =
|
|
6191
|
+
const absDist = path21.resolve(rootDir, dist);
|
|
5454
6192
|
await esbuild.build({
|
|
5455
6193
|
entryPoints: [...entryPoints, ...outsideFiles],
|
|
5456
6194
|
outdir: absDist,
|
|
@@ -5464,7 +6202,7 @@ async function compileProjectModules(entryPoints, rootDir, dist) {
|
|
|
5464
6202
|
logLevel: "silent"
|
|
5465
6203
|
});
|
|
5466
6204
|
if (insideFiles.length > 0) {
|
|
5467
|
-
const appOutbase =
|
|
6205
|
+
const appOutbase = path21.resolve(rootDir, "src");
|
|
5468
6206
|
await esbuild.build({
|
|
5469
6207
|
entryPoints: insideFiles,
|
|
5470
6208
|
outdir: absDist,
|
|
@@ -5545,38 +6283,38 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
|
|
|
5545
6283
|
var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
|
|
5546
6284
|
var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
|
|
5547
6285
|
function resolveLocalPluginSource(specifier, baseDir) {
|
|
5548
|
-
const base =
|
|
5549
|
-
if ((TS_EXTS.test(base) || JS_EXTS.test(base)) &&
|
|
6286
|
+
const base = path22.isAbsolute(specifier) ? specifier : path22.resolve(baseDir, specifier);
|
|
6287
|
+
if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs17.existsSync(base)) return base;
|
|
5550
6288
|
for (const ext of [".ts", ".js"]) {
|
|
5551
|
-
if (
|
|
6289
|
+
if (fs17.existsSync(base + ext)) return base + ext;
|
|
5552
6290
|
}
|
|
5553
6291
|
for (const indexExt of ["/index.ts", "/index.js"]) {
|
|
5554
|
-
if (
|
|
6292
|
+
if (fs17.existsSync(base + indexExt)) return base + indexExt;
|
|
5555
6293
|
}
|
|
5556
6294
|
return null;
|
|
5557
6295
|
}
|
|
5558
6296
|
function mirrorProductPath(sourcePath, baseDir, distDir) {
|
|
5559
|
-
let rel =
|
|
6297
|
+
let rel = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
5560
6298
|
if (rel.startsWith("src/")) rel = rel.slice(4);
|
|
5561
|
-
return
|
|
6299
|
+
return path22.resolve(distDir, rel.replace(TS_EXTS, ".js"));
|
|
5562
6300
|
}
|
|
5563
6301
|
function isSourceNewer(sourcePath, productPath) {
|
|
5564
6302
|
try {
|
|
5565
|
-
return
|
|
6303
|
+
return fs17.statSync(sourcePath).mtimeMs > fs17.statSync(productPath).mtimeMs;
|
|
5566
6304
|
} catch {
|
|
5567
6305
|
return true;
|
|
5568
6306
|
}
|
|
5569
6307
|
}
|
|
5570
6308
|
async function importPluginModule(specifier, baseDir, dist) {
|
|
5571
6309
|
const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
|
|
5572
|
-
if (!isRelative && !
|
|
6310
|
+
if (!isRelative && !path22.isAbsolute(specifier)) {
|
|
5573
6311
|
return import(specifier);
|
|
5574
6312
|
}
|
|
5575
|
-
const distDir = dist ?
|
|
6313
|
+
const distDir = dist ? path22.resolve(baseDir, dist) : null;
|
|
5576
6314
|
const sourcePath = resolveLocalPluginSource(specifier, baseDir);
|
|
5577
6315
|
const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
|
|
5578
|
-
if (productPath !== null &&
|
|
5579
|
-
return import(
|
|
6316
|
+
if (productPath !== null && fs17.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
|
|
6317
|
+
return import(pathToFileURL3(productPath).href);
|
|
5580
6318
|
}
|
|
5581
6319
|
if (sourcePath && TS_EXTS.test(sourcePath)) {
|
|
5582
6320
|
if (!isDevOnDemandEnabled() || !distDir) {
|
|
@@ -5584,25 +6322,25 @@ async function importPluginModule(specifier, baseDir, dist) {
|
|
|
5584
6322
|
`Local plugin "${specifier}" has no up-to-date build artifact (dist is stale or missing). Run "faapi build" first, or use "faapi dev" for on-demand compilation.`
|
|
5585
6323
|
);
|
|
5586
6324
|
}
|
|
5587
|
-
const relFromRoot =
|
|
6325
|
+
const relFromRoot = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
5588
6326
|
if (relFromRoot.startsWith("src/")) {
|
|
5589
6327
|
await ensureCompiled(sourcePath, baseDir, dist);
|
|
5590
6328
|
} else {
|
|
5591
6329
|
await compileProjectModules([sourcePath], baseDir, dist);
|
|
5592
6330
|
}
|
|
5593
|
-
if (productPath &&
|
|
5594
|
-
return import(
|
|
6331
|
+
if (productPath && fs17.existsSync(productPath)) {
|
|
6332
|
+
return import(pathToFileURL3(productPath).href);
|
|
5595
6333
|
}
|
|
5596
6334
|
}
|
|
5597
6335
|
if (sourcePath && JS_EXTS.test(sourcePath)) {
|
|
5598
|
-
return import(
|
|
6336
|
+
return import(pathToFileURL3(sourcePath).href);
|
|
5599
6337
|
}
|
|
5600
6338
|
const candidates = [
|
|
5601
6339
|
`${specifier}.ts`,
|
|
5602
6340
|
`${specifier}/index.ts`,
|
|
5603
6341
|
`${specifier}.js`,
|
|
5604
6342
|
`${specifier}/index.js`
|
|
5605
|
-
].map((c) =>
|
|
6343
|
+
].map((c) => path22.isAbsolute(c) ? c : path22.join(baseDir, c));
|
|
5606
6344
|
throw new Error(
|
|
5607
6345
|
`Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
|
|
5608
6346
|
${candidates.join("\n ")}
|
|
@@ -5633,8 +6371,8 @@ var ROUTES_FILE = "faapi-routes.js";
|
|
|
5633
6371
|
var TOOLS_FILE2 = "faapi-tools.js";
|
|
5634
6372
|
var AGENTS_FILE2 = "faapi-agents.js";
|
|
5635
6373
|
async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
|
|
5636
|
-
const toolsPath =
|
|
5637
|
-
if (!
|
|
6374
|
+
const toolsPath = path23.resolve(rootDir, dist, TOOLS_FILE2);
|
|
6375
|
+
if (!fs18.existsSync(toolsPath)) {
|
|
5638
6376
|
return [];
|
|
5639
6377
|
}
|
|
5640
6378
|
const serialized = await importWithCacheBust(toolsPath);
|
|
@@ -5643,8 +6381,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
|
|
|
5643
6381
|
return hydrated;
|
|
5644
6382
|
}
|
|
5645
6383
|
async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
|
|
5646
|
-
const agentsPath =
|
|
5647
|
-
if (!
|
|
6384
|
+
const agentsPath = path23.resolve(rootDir, dist, AGENTS_FILE2);
|
|
6385
|
+
if (!fs18.existsSync(agentsPath)) {
|
|
5648
6386
|
return [];
|
|
5649
6387
|
}
|
|
5650
6388
|
const serialized = await importWithCacheBust(agentsPath);
|
|
@@ -5652,6 +6390,22 @@ async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistrie
|
|
|
5652
6390
|
registries.agent.hydrate(hydrated);
|
|
5653
6391
|
return hydrated;
|
|
5654
6392
|
}
|
|
6393
|
+
function getTaskDriverOptions(config) {
|
|
6394
|
+
if (!config?.task) return void 0;
|
|
6395
|
+
if (config.task.driver === "pgboss") return config.task.pgboss;
|
|
6396
|
+
if (config.task.driver === "bullmq") return config.task.bullmq;
|
|
6397
|
+
return void 0;
|
|
6398
|
+
}
|
|
6399
|
+
async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
|
|
6400
|
+
const tasksPath = path23.resolve(rootDir, dist, TASKS_FILE);
|
|
6401
|
+
if (!fs18.existsSync(tasksPath)) {
|
|
6402
|
+
return [];
|
|
6403
|
+
}
|
|
6404
|
+
const serialized = await importWithCacheBust(tasksPath);
|
|
6405
|
+
const hydrated = hydrateTasks(serialized.tasks ?? []);
|
|
6406
|
+
registries.task.hydrate(hydrated);
|
|
6407
|
+
return hydrated;
|
|
6408
|
+
}
|
|
5655
6409
|
var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
|
|
5656
6410
|
function getCurrentApp() {
|
|
5657
6411
|
return globalThis[APP_INSTANCE_KEY] ?? null;
|
|
@@ -5703,7 +6457,8 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
5703
6457
|
"logger",
|
|
5704
6458
|
"http2",
|
|
5705
6459
|
"trustedProxy",
|
|
5706
|
-
"response"
|
|
6460
|
+
"response",
|
|
6461
|
+
"task"
|
|
5707
6462
|
]);
|
|
5708
6463
|
function isFaapiConfigKey(key) {
|
|
5709
6464
|
return FAAPI_CONFIG_KEYS.has(key);
|
|
@@ -5711,8 +6466,8 @@ function isFaapiConfigKey(key) {
|
|
|
5711
6466
|
async function createAppBase(options) {
|
|
5712
6467
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
5713
6468
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
5714
|
-
const routesPath =
|
|
5715
|
-
if (!
|
|
6469
|
+
const routesPath = path23.resolve(rootDir, dist, ROUTES_FILE);
|
|
6470
|
+
if (!fs18.existsSync(routesPath)) {
|
|
5716
6471
|
throw new Error(
|
|
5717
6472
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
5718
6473
|
);
|
|
@@ -5734,6 +6489,29 @@ async function createAppBase(options) {
|
|
|
5734
6489
|
const registries = createAppRegistries();
|
|
5735
6490
|
const tools = await loadAndHydrateTools(rootDir, dist, registries);
|
|
5736
6491
|
const agents = await loadAndHydrateAgents(rootDir, dist, registries);
|
|
6492
|
+
const taskMetas = await loadAndHydrateTasks(rootDir, dist, registries);
|
|
6493
|
+
const taskDriver = taskMetas.length ? await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config)) : createIdleTaskDriver();
|
|
6494
|
+
const taskQueue = createTaskQueue({
|
|
6495
|
+
registry: registries.task,
|
|
6496
|
+
rootDir,
|
|
6497
|
+
config,
|
|
6498
|
+
driver: taskDriver,
|
|
6499
|
+
onFailed: config?.task?.onFailed
|
|
6500
|
+
});
|
|
6501
|
+
const cronScheduler = createCronScheduler(
|
|
6502
|
+
registries.task,
|
|
6503
|
+
(name, opts) => taskQueue.enqueue(name, void 0, opts)
|
|
6504
|
+
);
|
|
6505
|
+
const taskEnabled = process.env.FAAPI_TASKS_DISABLED === "1" ? false : config?.task?.enabled ?? true;
|
|
6506
|
+
const stopTaskRuntime = async () => {
|
|
6507
|
+
cronScheduler.stop();
|
|
6508
|
+
await taskQueue.stop(config?.task?.shutdownTimeoutMs ?? 1e4);
|
|
6509
|
+
};
|
|
6510
|
+
if (taskEnabled) {
|
|
6511
|
+
taskQueue.start();
|
|
6512
|
+
cronScheduler.start();
|
|
6513
|
+
}
|
|
6514
|
+
registries.taskHandle.register(() => taskQueue);
|
|
5737
6515
|
const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
|
|
5738
6516
|
const { server, routesRef } = createServer({
|
|
5739
6517
|
routes: sorted,
|
|
@@ -5775,14 +6553,22 @@ async function createAppBase(options) {
|
|
|
5775
6553
|
routes: sorted,
|
|
5776
6554
|
wsRoutes,
|
|
5777
6555
|
rootDir,
|
|
6556
|
+
tasks: taskQueue,
|
|
5778
6557
|
async listen(listenPort) {
|
|
5779
6558
|
const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
|
|
5780
6559
|
const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
|
|
5781
6560
|
if (config?.lifecycle?.onBoot) {
|
|
5782
6561
|
try {
|
|
5783
|
-
await config.lifecycle.onBoot({
|
|
6562
|
+
await config.lifecycle.onBoot({
|
|
6563
|
+
rootDir,
|
|
6564
|
+
routes: sorted,
|
|
6565
|
+
server,
|
|
6566
|
+
registries,
|
|
6567
|
+
tasks: taskQueue
|
|
6568
|
+
});
|
|
5784
6569
|
console.log("- onBoot hook executed");
|
|
5785
6570
|
} catch (err) {
|
|
6571
|
+
await stopTaskRuntime();
|
|
5786
6572
|
const message = err instanceof Error ? err.message : String(err);
|
|
5787
6573
|
console.error(`[faapi] onBoot hook failed: ${message}`);
|
|
5788
6574
|
throw err;
|
|
@@ -5830,9 +6616,22 @@ async function createAppBase(options) {
|
|
|
5830
6616
|
console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
|
|
5831
6617
|
}
|
|
5832
6618
|
}
|
|
6619
|
+
if (taskMetas.length > 0) {
|
|
6620
|
+
console.log(`- Loaded ${taskMetas.length} task(s):`);
|
|
6621
|
+
for (const taskMeta of taskMetas) {
|
|
6622
|
+
const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
|
|
6623
|
+
console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
5833
6626
|
registerDefaultShutdownHandlers();
|
|
5834
6627
|
if (config?.lifecycle?.onReady) {
|
|
5835
|
-
await config.lifecycle.onReady({
|
|
6628
|
+
await config.lifecycle.onReady({
|
|
6629
|
+
rootDir,
|
|
6630
|
+
routes: sorted,
|
|
6631
|
+
server,
|
|
6632
|
+
registries,
|
|
6633
|
+
tasks: taskQueue
|
|
6634
|
+
});
|
|
5836
6635
|
console.log("- onReady hook executed");
|
|
5837
6636
|
}
|
|
5838
6637
|
app.server = server;
|
|
@@ -5925,13 +6724,22 @@ async function createAppBase(options) {
|
|
|
5925
6724
|
closed = true;
|
|
5926
6725
|
const s = server;
|
|
5927
6726
|
s.closeIdleConnections?.();
|
|
6727
|
+
await stopTaskRuntime();
|
|
5928
6728
|
if (config?.lifecycle?.onClose) {
|
|
5929
|
-
await config.lifecycle.onClose({
|
|
6729
|
+
await config.lifecycle.onClose({
|
|
6730
|
+
rootDir,
|
|
6731
|
+
routes: sorted,
|
|
6732
|
+
server,
|
|
6733
|
+
registries,
|
|
6734
|
+
tasks: taskQueue
|
|
6735
|
+
});
|
|
5930
6736
|
}
|
|
5931
6737
|
registries.tool.clear();
|
|
5932
6738
|
registries.agent.clear();
|
|
5933
6739
|
registries.skill.clear();
|
|
6740
|
+
registries.task.clear();
|
|
5934
6741
|
registries.agentHandle.clear();
|
|
6742
|
+
registries.taskHandle.clear();
|
|
5935
6743
|
if (!server.listening) {
|
|
5936
6744
|
app.server = null;
|
|
5937
6745
|
if (getCurrentApp() === app) setCurrentApp(null);
|
|
@@ -5968,6 +6776,7 @@ async function createAppBase(options) {
|
|
|
5968
6776
|
registries,
|
|
5969
6777
|
dist,
|
|
5970
6778
|
patterns: ROUTE_PATTERNS,
|
|
6779
|
+
taskQueue,
|
|
5971
6780
|
server,
|
|
5972
6781
|
routesRef,
|
|
5973
6782
|
config,
|
|
@@ -5983,19 +6792,22 @@ async function createAppBase(options) {
|
|
|
5983
6792
|
return { app, ctx };
|
|
5984
6793
|
}
|
|
5985
6794
|
|
|
6795
|
+
// src/cli/createDevApp.ts
|
|
6796
|
+
import path27 from "path";
|
|
6797
|
+
|
|
5986
6798
|
// src/router/scanRoutes.ts
|
|
5987
|
-
import
|
|
5988
|
-
import
|
|
5989
|
-
import
|
|
6799
|
+
import fg3 from "fast-glob";
|
|
6800
|
+
import path24 from "path";
|
|
6801
|
+
import fs19 from "fs";
|
|
5990
6802
|
|
|
5991
6803
|
// src/router/constants.ts
|
|
5992
6804
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
5993
6805
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
5994
6806
|
|
|
5995
6807
|
// src/utils/normalizePath.ts
|
|
5996
|
-
function normalizePath(
|
|
5997
|
-
if (!
|
|
5998
|
-
let result =
|
|
6808
|
+
function normalizePath(path28) {
|
|
6809
|
+
if (!path28) return "";
|
|
6810
|
+
let result = path28.replace(/\\/g, "/");
|
|
5999
6811
|
result = result.replace(/\/+/g, "/");
|
|
6000
6812
|
result = result.replace(/\/+$/, "");
|
|
6001
6813
|
if (result && !result.startsWith("/")) {
|
|
@@ -6056,34 +6868,34 @@ function extractExportsFromSource(source) {
|
|
|
6056
6868
|
return names;
|
|
6057
6869
|
}
|
|
6058
6870
|
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
6059
|
-
const routeDir =
|
|
6060
|
-
const resolvedRoot =
|
|
6871
|
+
const routeDir = path24.dirname(routeFilePath);
|
|
6872
|
+
const resolvedRoot = path24.resolve(rootDir);
|
|
6061
6873
|
const paths = [];
|
|
6062
|
-
let currentDir =
|
|
6874
|
+
let currentDir = path24.resolve(rootDir, routeDir);
|
|
6063
6875
|
while (true) {
|
|
6064
6876
|
if (dist) {
|
|
6065
|
-
const mwTsPath =
|
|
6066
|
-
const mwJsPath =
|
|
6067
|
-
const absTsPath =
|
|
6068
|
-
const absJsPath =
|
|
6069
|
-
const absMwPath =
|
|
6877
|
+
const mwTsPath = path24.join(currentDir, "middlewares.ts");
|
|
6878
|
+
const mwJsPath = path24.join(currentDir, "middlewares.js");
|
|
6879
|
+
const absTsPath = path24.resolve(rootDir, mwTsPath);
|
|
6880
|
+
const absJsPath = path24.resolve(rootDir, mwJsPath);
|
|
6881
|
+
const absMwPath = fs19.existsSync(absTsPath) ? absTsPath : fs19.existsSync(absJsPath) ? absJsPath : null;
|
|
6070
6882
|
if (absMwPath) {
|
|
6071
|
-
const relMwPath =
|
|
6072
|
-
const prodAbsPath =
|
|
6883
|
+
const relMwPath = path24.relative(rootDir, absMwPath);
|
|
6884
|
+
const prodAbsPath = path24.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
6073
6885
|
paths.push(prodAbsPath);
|
|
6074
6886
|
}
|
|
6075
6887
|
} else {
|
|
6076
6888
|
for (const ext of [".ts", ".js"]) {
|
|
6077
|
-
const mwPath =
|
|
6078
|
-
const absMwPath =
|
|
6079
|
-
if (
|
|
6889
|
+
const mwPath = path24.join(currentDir, `middlewares${ext}`);
|
|
6890
|
+
const absMwPath = path24.resolve(rootDir, mwPath);
|
|
6891
|
+
if (fs19.existsSync(absMwPath)) {
|
|
6080
6892
|
paths.push(absMwPath);
|
|
6081
6893
|
break;
|
|
6082
6894
|
}
|
|
6083
6895
|
}
|
|
6084
6896
|
}
|
|
6085
6897
|
if (currentDir === resolvedRoot) break;
|
|
6086
|
-
const parentDir =
|
|
6898
|
+
const parentDir = path24.dirname(currentDir);
|
|
6087
6899
|
if (parentDir === currentDir) break;
|
|
6088
6900
|
currentDir = parentDir;
|
|
6089
6901
|
}
|
|
@@ -6091,7 +6903,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
|
6091
6903
|
return paths;
|
|
6092
6904
|
}
|
|
6093
6905
|
async function scanRoutes(rootDir, patterns, dist) {
|
|
6094
|
-
const files = await
|
|
6906
|
+
const files = await fg3(patterns, {
|
|
6095
6907
|
cwd: rootDir,
|
|
6096
6908
|
onlyFiles: true,
|
|
6097
6909
|
absolute: false
|
|
@@ -6102,7 +6914,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
6102
6914
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
6103
6915
|
const fileName = normalizedFile.split("/").pop();
|
|
6104
6916
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
6105
|
-
const absPath =
|
|
6917
|
+
const absPath = path24.resolve(rootDir, normalizedFile);
|
|
6106
6918
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
6107
6919
|
const paramNames = extractParamNames(urlPath);
|
|
6108
6920
|
const isDynamic = paramNames.length > 0;
|
|
@@ -6115,7 +6927,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
6115
6927
|
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
6116
6928
|
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
6117
6929
|
}
|
|
6118
|
-
const source = await
|
|
6930
|
+
const source = await fs19.promises.readFile(absPath, "utf8").catch(() => "");
|
|
6119
6931
|
const exportNames = extractExportsFromSource(source);
|
|
6120
6932
|
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
6121
6933
|
for (const method of methods) {
|
|
@@ -6150,9 +6962,9 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
6150
6962
|
}
|
|
6151
6963
|
|
|
6152
6964
|
// src/tools/scanTools.ts
|
|
6153
|
-
import
|
|
6154
|
-
import
|
|
6155
|
-
import
|
|
6965
|
+
import fg4 from "fast-glob";
|
|
6966
|
+
import path25 from "path";
|
|
6967
|
+
import fs20 from "fs";
|
|
6156
6968
|
var TOOL_PATTERNS = ["src/tools/**/*.ts"];
|
|
6157
6969
|
var TOOL_EXPORT_RE = new RegExp(
|
|
6158
6970
|
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
|
|
@@ -6189,7 +7001,7 @@ function buildToolName(namespace, functionName) {
|
|
|
6189
7001
|
return namespace ? `${namespace}.${functionName}` : functionName;
|
|
6190
7002
|
}
|
|
6191
7003
|
async function scanTools(rootDir, patterns) {
|
|
6192
|
-
const files = await
|
|
7004
|
+
const files = await fg4(patterns, {
|
|
6193
7005
|
cwd: rootDir,
|
|
6194
7006
|
onlyFiles: true,
|
|
6195
7007
|
absolute: false
|
|
@@ -6202,8 +7014,8 @@ async function scanTools(rootDir, patterns) {
|
|
|
6202
7014
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
6203
7015
|
continue;
|
|
6204
7016
|
}
|
|
6205
|
-
const absPath =
|
|
6206
|
-
const source = await
|
|
7017
|
+
const absPath = path25.resolve(rootDir, normalizedFile);
|
|
7018
|
+
const source = await fs20.promises.readFile(absPath, "utf8").catch(() => "");
|
|
6207
7019
|
const exportNames = extractToolExportsFromSource(source);
|
|
6208
7020
|
const namespace = filePathToToolNamespace(normalizedFile);
|
|
6209
7021
|
for (const fnName of exportNames) {
|
|
@@ -6226,9 +7038,9 @@ async function scanTools(rootDir, patterns) {
|
|
|
6226
7038
|
}
|
|
6227
7039
|
|
|
6228
7040
|
// src/agents/scanAgents.ts
|
|
6229
|
-
import
|
|
6230
|
-
import
|
|
6231
|
-
import
|
|
7041
|
+
import fg5 from "fast-glob";
|
|
7042
|
+
import path26 from "path";
|
|
7043
|
+
import fs21 from "fs";
|
|
6232
7044
|
var DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
|
|
6233
7045
|
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
6234
7046
|
function extractAgentNameFromPath(filePath) {
|
|
@@ -6247,7 +7059,7 @@ function detectAgentExports(source) {
|
|
|
6247
7059
|
};
|
|
6248
7060
|
}
|
|
6249
7061
|
async function scanAgents(rootDir, patterns) {
|
|
6250
|
-
const files = await
|
|
7062
|
+
const files = await fg5(patterns, {
|
|
6251
7063
|
cwd: rootDir,
|
|
6252
7064
|
onlyFiles: true,
|
|
6253
7065
|
absolute: false
|
|
@@ -6260,8 +7072,8 @@ async function scanAgents(rootDir, patterns) {
|
|
|
6260
7072
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
6261
7073
|
continue;
|
|
6262
7074
|
}
|
|
6263
|
-
const absPath =
|
|
6264
|
-
const source = await
|
|
7075
|
+
const absPath = path26.resolve(rootDir, normalizedFile);
|
|
7076
|
+
const source = await fs21.promises.readFile(absPath, "utf8").catch(() => "");
|
|
6265
7077
|
const { hasRun } = detectAgentExports(source);
|
|
6266
7078
|
const name = extractAgentNameFromPath(normalizedFile);
|
|
6267
7079
|
const prevFile = seen.get(name);
|
|
@@ -6326,6 +7138,19 @@ async function createDevApp(options) {
|
|
|
6326
7138
|
await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
|
|
6327
7139
|
await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
|
|
6328
7140
|
};
|
|
7141
|
+
devApp.reloadTasks = async () => {
|
|
7142
|
+
setLoadTimestamp(Date.now());
|
|
7143
|
+
invalidateProgramCache();
|
|
7144
|
+
const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
|
|
7145
|
+
for (const task of tasks) {
|
|
7146
|
+
await ensureCompiled(path27.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
|
|
7147
|
+
}
|
|
7148
|
+
await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
|
|
7149
|
+
ctx.taskQueue.invalidateModules();
|
|
7150
|
+
ctx.taskQueue.invalidateSchemas();
|
|
7151
|
+
await ctx.taskQueue.reload();
|
|
7152
|
+
await loadAndHydrateTasks(ctx.rootDir, ctx.dist, ctx.registries);
|
|
7153
|
+
};
|
|
6329
7154
|
return devApp;
|
|
6330
7155
|
}
|
|
6331
7156
|
|
|
@@ -6341,16 +7166,20 @@ export {
|
|
|
6341
7166
|
ModuleLoadError,
|
|
6342
7167
|
RouteNotFoundError,
|
|
6343
7168
|
SchemaExtractionError,
|
|
7169
|
+
TASK_PATTERNS,
|
|
6344
7170
|
ValidationError,
|
|
6345
7171
|
clearAgentHandleFactory,
|
|
6346
7172
|
collectRouteSchemaSources,
|
|
6347
7173
|
cors,
|
|
6348
7174
|
createProdApp as createApp,
|
|
6349
7175
|
createAppRegistries,
|
|
7176
|
+
createCronScheduler,
|
|
6350
7177
|
createDevApp,
|
|
6351
7178
|
createProdApp,
|
|
6352
7179
|
createProgram,
|
|
6353
7180
|
createPrograms,
|
|
7181
|
+
createTaskQueue,
|
|
7182
|
+
createTaskRegistry,
|
|
6354
7183
|
extractTypeInfo,
|
|
6355
7184
|
getAgent,
|
|
6356
7185
|
getAgentEntry,
|
|
@@ -6366,6 +7195,7 @@ export {
|
|
|
6366
7195
|
loadAgentModule,
|
|
6367
7196
|
loadConfig,
|
|
6368
7197
|
loadEnv,
|
|
7198
|
+
loadTaskDriver,
|
|
6369
7199
|
loadToolModule,
|
|
6370
7200
|
loadToolSchema,
|
|
6371
7201
|
logger,
|
|
@@ -6374,6 +7204,7 @@ export {
|
|
|
6374
7204
|
resolveAgentTools,
|
|
6375
7205
|
resolveSubAgents,
|
|
6376
7206
|
resolveTypeNode,
|
|
7207
|
+
scanTasks,
|
|
6377
7208
|
upsertSkill
|
|
6378
7209
|
};
|
|
6379
7210
|
//# sourceMappingURL=index.js.map
|