@objectstack/runtime 4.0.1 → 4.0.3
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +19 -0
- package/dist/index.cjs +281 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.js +281 -10
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/app-plugin.test.ts +8 -3
- package/src/app-plugin.ts +4 -8
- package/src/dispatcher-plugin.ts +141 -2
- package/src/http-dispatcher.root.test.ts +5 -2
- package/src/http-dispatcher.ts +240 -19
- package/vitest.config.ts +26 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/runtime@4.0.
|
|
2
|
+
> @objectstack/runtime@4.0.3 build /home/runner/work/framework/framework/packages/runtime
|
|
3
3
|
> tsup --config ../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Using tsup config: /home/runner/work/
|
|
8
|
+
[34mCLI[39m Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mESM[39m [1mdist/index.js [22m[32m98.02 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m212.66 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 153ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m100.63 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m212.72 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 153ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 3771ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m26.00 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m26.00 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @objectstack/runtime
|
|
2
2
|
|
|
3
|
+
## 4.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @objectstack/spec@4.0.3
|
|
8
|
+
- @objectstack/core@4.0.3
|
|
9
|
+
- @objectstack/types@4.0.3
|
|
10
|
+
- @objectstack/rest@4.0.3
|
|
11
|
+
|
|
12
|
+
## 4.0.2
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [5f659e9]
|
|
17
|
+
- @objectstack/spec@4.0.2
|
|
18
|
+
- @objectstack/core@4.0.2
|
|
19
|
+
- @objectstack/rest@4.0.2
|
|
20
|
+
- @objectstack/types@4.0.2
|
|
21
|
+
|
|
3
22
|
## 4.0.0
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -646,9 +646,8 @@ var AppPlugin = class {
|
|
|
646
646
|
pluginName: this.name,
|
|
647
647
|
version: this.version
|
|
648
648
|
});
|
|
649
|
-
const serviceName = `app.${appId}`;
|
|
650
649
|
const servicePayload = this.bundle.manifest ? { ...this.bundle.manifest, ...this.bundle } : this.bundle;
|
|
651
|
-
ctx.
|
|
650
|
+
ctx.getService("manifest").register(servicePayload);
|
|
652
651
|
};
|
|
653
652
|
this.start = async (ctx) => {
|
|
654
653
|
const sys = this.bundle.manifest || this.bundle;
|
|
@@ -829,6 +828,7 @@ var AppPlugin = class {
|
|
|
829
828
|
// src/http-dispatcher.ts
|
|
830
829
|
var import_core2 = require("@objectstack/core");
|
|
831
830
|
var import_system = require("@objectstack/spec/system");
|
|
831
|
+
var import_shared = require("@objectstack/spec/shared");
|
|
832
832
|
function randomUUID() {
|
|
833
833
|
if (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function") {
|
|
834
834
|
return globalThis.crypto.randomUUID();
|
|
@@ -856,6 +856,24 @@ var HttpDispatcher = class {
|
|
|
856
856
|
body: { success: false, error: { message, code, details } }
|
|
857
857
|
};
|
|
858
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* 404 Route Not Found — no route is registered for this path.
|
|
861
|
+
*/
|
|
862
|
+
routeNotFound(route) {
|
|
863
|
+
return {
|
|
864
|
+
status: 404,
|
|
865
|
+
body: {
|
|
866
|
+
success: false,
|
|
867
|
+
error: {
|
|
868
|
+
code: 404,
|
|
869
|
+
message: `Route Not Found: ${route}`,
|
|
870
|
+
type: "ROUTE_NOT_FOUND",
|
|
871
|
+
route,
|
|
872
|
+
hint: "No route is registered for this path. Check the API discovery endpoint for available routes."
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
}
|
|
859
877
|
ensureBroker() {
|
|
860
878
|
if (!this.kernel.broker) {
|
|
861
879
|
throw { statusCode: 500, message: "Kernel Broker not available" };
|
|
@@ -937,12 +955,14 @@ var HttpDispatcher = class {
|
|
|
937
955
|
const svcAvailable = (route, provider) => ({
|
|
938
956
|
enabled: true,
|
|
939
957
|
status: "available",
|
|
958
|
+
handlerReady: true,
|
|
940
959
|
route,
|
|
941
960
|
provider
|
|
942
961
|
});
|
|
943
962
|
const svcUnavailable = (name) => ({
|
|
944
963
|
enabled: false,
|
|
945
964
|
status: "unavailable",
|
|
965
|
+
handlerReady: false,
|
|
946
966
|
message: `Install a ${name} plugin to enable`
|
|
947
967
|
});
|
|
948
968
|
let locale = { default: "en", supported: ["en"], timezone: "UTC" };
|
|
@@ -975,7 +995,7 @@ var HttpDispatcher = class {
|
|
|
975
995
|
},
|
|
976
996
|
services: {
|
|
977
997
|
// Kernel-provided (always available via protocol implementation)
|
|
978
|
-
metadata: { enabled: true, status: "degraded", route: routes.metadata, provider: "kernel", message: "In-memory registry; DB persistence pending" },
|
|
998
|
+
metadata: { enabled: true, status: "degraded", handlerReady: true, route: routes.metadata, provider: "kernel", message: "In-memory registry; DB persistence pending" },
|
|
979
999
|
data: svcAvailable(routes.data, "kernel"),
|
|
980
1000
|
// Plugin-provided — only available when a plugin registers the service
|
|
981
1001
|
auth: hasAuth ? svcAvailable(routes.auth) : svcUnavailable("auth"),
|
|
@@ -1093,18 +1113,66 @@ var HttpDispatcher = class {
|
|
|
1093
1113
|
const broker = this.kernel.broker ?? null;
|
|
1094
1114
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
1095
1115
|
if (parts[0] === "types") {
|
|
1116
|
+
console.log("[HttpDispatcher] Attempting to resolve MetadataService...");
|
|
1117
|
+
console.log("[HttpDispatcher] Available kernel methods:", {
|
|
1118
|
+
hasGetServiceAsync: typeof this.kernel.getServiceAsync === "function",
|
|
1119
|
+
hasGetService: typeof this.kernel.getService === "function",
|
|
1120
|
+
hasContext: !!this.kernel.context,
|
|
1121
|
+
hasContextGetService: typeof this.kernel.context?.getService === "function"
|
|
1122
|
+
});
|
|
1123
|
+
let metadataService = null;
|
|
1124
|
+
if (typeof this.kernel.getServiceAsync === "function") {
|
|
1125
|
+
try {
|
|
1126
|
+
metadataService = await this.kernel.getServiceAsync("metadata");
|
|
1127
|
+
console.log('[HttpDispatcher] kernel.getServiceAsync("metadata") returned:', !!metadataService);
|
|
1128
|
+
} catch (e) {
|
|
1129
|
+
console.log('[HttpDispatcher] kernel.getServiceAsync("metadata") failed:', e.message);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (!metadataService && typeof this.kernel.getService === "function") {
|
|
1133
|
+
try {
|
|
1134
|
+
metadataService = await this.kernel.getService("metadata");
|
|
1135
|
+
console.log('[HttpDispatcher] kernel.getService("metadata") returned:', !!metadataService);
|
|
1136
|
+
} catch (e) {
|
|
1137
|
+
console.log('[HttpDispatcher] kernel.getService("metadata") failed:', e.message);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (!metadataService && this.kernel.context?.getService) {
|
|
1141
|
+
try {
|
|
1142
|
+
metadataService = await this.kernel.context.getService("metadata");
|
|
1143
|
+
console.log('[HttpDispatcher] kernel.context.getService("metadata") returned:', !!metadataService);
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
console.log('[HttpDispatcher] kernel.context.getService("metadata") failed:', e.message);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
console.log("[HttpDispatcher] Final metadataService:", !!metadataService, "has getRegisteredTypes:", typeof metadataService?.getRegisteredTypes);
|
|
1149
|
+
if (metadataService && typeof metadataService.getRegisteredTypes === "function") {
|
|
1150
|
+
try {
|
|
1151
|
+
const types = await metadataService.getRegisteredTypes();
|
|
1152
|
+
console.log("[HttpDispatcher] MetadataService.getRegisteredTypes() returned:", types);
|
|
1153
|
+
return { handled: true, response: this.success({ types }) };
|
|
1154
|
+
} catch (e) {
|
|
1155
|
+
console.warn("[HttpDispatcher] MetadataService.getRegisteredTypes() failed:", e.message, e.stack);
|
|
1156
|
+
}
|
|
1157
|
+
} else {
|
|
1158
|
+
console.log("[HttpDispatcher] MetadataService not available or missing getRegisteredTypes, falling back to protocol service");
|
|
1159
|
+
}
|
|
1096
1160
|
const protocol = await this.resolveService("protocol");
|
|
1097
1161
|
if (protocol && typeof protocol.getMetaTypes === "function") {
|
|
1098
1162
|
const result = await protocol.getMetaTypes({});
|
|
1163
|
+
console.log("[HttpDispatcher] Protocol service returned types:", result);
|
|
1099
1164
|
return { handled: true, response: this.success(result) };
|
|
1100
1165
|
}
|
|
1101
1166
|
if (broker) {
|
|
1102
1167
|
try {
|
|
1103
1168
|
const data = await broker.call("metadata.types", {}, { request: context.request });
|
|
1169
|
+
console.log("[HttpDispatcher] Broker returned types:", data);
|
|
1104
1170
|
return { handled: true, response: this.success(data) };
|
|
1105
|
-
} catch {
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
console.log("[HttpDispatcher] Broker call failed:", e);
|
|
1106
1173
|
}
|
|
1107
1174
|
}
|
|
1175
|
+
console.warn("[HttpDispatcher] Falling back to hardcoded defaults for metadata types");
|
|
1108
1176
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
1109
1177
|
}
|
|
1110
1178
|
if (parts.length === 3 && parts[2] === "published" && (!method || method === "GET")) {
|
|
@@ -1127,6 +1195,7 @@ var HttpDispatcher = class {
|
|
|
1127
1195
|
}
|
|
1128
1196
|
if (parts.length === 2) {
|
|
1129
1197
|
const [type, name] = parts;
|
|
1198
|
+
const packageId = query?.package || void 0;
|
|
1130
1199
|
if (method === "PUT" && body) {
|
|
1131
1200
|
const protocol = await this.resolveService("protocol");
|
|
1132
1201
|
if (protocol && typeof protocol.saveMetaItem === "function") {
|
|
@@ -1160,11 +1229,11 @@ var HttpDispatcher = class {
|
|
|
1160
1229
|
}
|
|
1161
1230
|
return { handled: true, response: this.error("Not found", 404) };
|
|
1162
1231
|
}
|
|
1163
|
-
const singularType =
|
|
1232
|
+
const singularType = (0, import_shared.pluralToSingular)(type);
|
|
1164
1233
|
const protocol = await this.resolveService("protocol");
|
|
1165
1234
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
1166
1235
|
try {
|
|
1167
|
-
const data = await protocol.getMetaItem({ type: singularType, name });
|
|
1236
|
+
const data = await protocol.getMetaItem({ type: singularType, name, packageId });
|
|
1168
1237
|
return { handled: true, response: this.success(data) };
|
|
1169
1238
|
} catch (e) {
|
|
1170
1239
|
}
|
|
@@ -1192,6 +1261,18 @@ var HttpDispatcher = class {
|
|
|
1192
1261
|
} catch {
|
|
1193
1262
|
}
|
|
1194
1263
|
}
|
|
1264
|
+
const metadataService = await this.getService(import_system.CoreServiceName.enum.metadata);
|
|
1265
|
+
if (metadataService && typeof metadataService.list === "function") {
|
|
1266
|
+
try {
|
|
1267
|
+
const items = await metadataService.list(typeOrName);
|
|
1268
|
+
if (items && items.length > 0) {
|
|
1269
|
+
return { handled: true, response: this.success({ type: typeOrName, items }) };
|
|
1270
|
+
}
|
|
1271
|
+
} catch (e) {
|
|
1272
|
+
const sanitizedType = String(typeOrName).replace(/[\r\n\t]/g, "");
|
|
1273
|
+
console.debug(`[HttpDispatcher] MetadataService.list() failed for type:`, sanitizedType, "error:", e.message);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1195
1276
|
if (broker) {
|
|
1196
1277
|
try {
|
|
1197
1278
|
if (typeOrName === "objects") {
|
|
@@ -1752,19 +1833,108 @@ var HttpDispatcher = class {
|
|
|
1752
1833
|
capitalize(s) {
|
|
1753
1834
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1754
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Handle AI service routes (/ai/chat, /ai/models, /ai/conversations, etc.)
|
|
1838
|
+
* Resolves the AI service and its built-in route handlers, then dispatches.
|
|
1839
|
+
*/
|
|
1840
|
+
async handleAI(subPath, method, body, query, _context) {
|
|
1841
|
+
let aiService;
|
|
1842
|
+
try {
|
|
1843
|
+
aiService = await this.resolveService("ai");
|
|
1844
|
+
} catch {
|
|
1845
|
+
}
|
|
1846
|
+
if (!aiService) {
|
|
1847
|
+
return {
|
|
1848
|
+
handled: true,
|
|
1849
|
+
response: {
|
|
1850
|
+
status: 404,
|
|
1851
|
+
body: { success: false, error: { message: "AI service is not configured", code: 404 } }
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
const fullPath = `/api/v1${subPath}`;
|
|
1856
|
+
const matchRoute = (pattern, path) => {
|
|
1857
|
+
const patternParts = pattern.split("/");
|
|
1858
|
+
const pathParts = path.split("/");
|
|
1859
|
+
if (patternParts.length !== pathParts.length) return null;
|
|
1860
|
+
const params = {};
|
|
1861
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
1862
|
+
if (patternParts[i].startsWith(":")) {
|
|
1863
|
+
params[patternParts[i].substring(1)] = pathParts[i];
|
|
1864
|
+
} else if (patternParts[i] !== pathParts[i]) {
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return params;
|
|
1869
|
+
};
|
|
1870
|
+
const routes = this.kernel.__aiRoutes;
|
|
1871
|
+
if (!routes) {
|
|
1872
|
+
return {
|
|
1873
|
+
handled: true,
|
|
1874
|
+
response: {
|
|
1875
|
+
status: 503,
|
|
1876
|
+
body: { success: false, error: { message: "AI service routes not yet initialized", code: 503 } }
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
for (const route of routes) {
|
|
1881
|
+
if (route.method !== method) continue;
|
|
1882
|
+
const params = matchRoute(route.path, fullPath);
|
|
1883
|
+
if (params === null) continue;
|
|
1884
|
+
const result = await route.handler({ body, params, query });
|
|
1885
|
+
if (result.stream && result.events) {
|
|
1886
|
+
return {
|
|
1887
|
+
handled: true,
|
|
1888
|
+
result: {
|
|
1889
|
+
type: "stream",
|
|
1890
|
+
contentType: result.vercelDataStream ? "text/plain; charset=utf-8" : "text/event-stream",
|
|
1891
|
+
events: result.events,
|
|
1892
|
+
vercelDataStream: result.vercelDataStream,
|
|
1893
|
+
headers: {
|
|
1894
|
+
"Content-Type": result.vercelDataStream ? "text/plain; charset=utf-8" : "text/event-stream",
|
|
1895
|
+
"Cache-Control": "no-cache",
|
|
1896
|
+
"Connection": "keep-alive"
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
return {
|
|
1902
|
+
handled: true,
|
|
1903
|
+
response: {
|
|
1904
|
+
status: result.status,
|
|
1905
|
+
body: result.body
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
return {
|
|
1910
|
+
handled: true,
|
|
1911
|
+
response: this.routeNotFound(subPath)
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1755
1914
|
/**
|
|
1756
1915
|
* Main Dispatcher Entry Point
|
|
1757
1916
|
* Routes the request to the appropriate handler based on path and precedence
|
|
1758
1917
|
*/
|
|
1759
|
-
async dispatch(method, path, body, query, context) {
|
|
1918
|
+
async dispatch(method, path, body, query, context, prefix) {
|
|
1760
1919
|
const cleanPath = path.replace(/\/$/, "");
|
|
1761
1920
|
if ((cleanPath === "/discovery" || cleanPath === "") && method === "GET") {
|
|
1762
|
-
const info = await this.getDiscoveryInfo("");
|
|
1921
|
+
const info = await this.getDiscoveryInfo(prefix ?? "");
|
|
1763
1922
|
return {
|
|
1764
1923
|
handled: true,
|
|
1765
1924
|
response: this.success(info)
|
|
1766
1925
|
};
|
|
1767
1926
|
}
|
|
1927
|
+
if (cleanPath === "/health" && method === "GET") {
|
|
1928
|
+
return {
|
|
1929
|
+
handled: true,
|
|
1930
|
+
response: this.success({
|
|
1931
|
+
status: "ok",
|
|
1932
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1933
|
+
version: "1.0.0",
|
|
1934
|
+
uptime: typeof process !== "undefined" ? process.uptime() : void 0
|
|
1935
|
+
})
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1768
1938
|
if (cleanPath.startsWith("/auth")) {
|
|
1769
1939
|
return this.handleAuth(cleanPath.substring(5), method, body, context);
|
|
1770
1940
|
}
|
|
@@ -1795,6 +1965,9 @@ var HttpDispatcher = class {
|
|
|
1795
1965
|
if (cleanPath.startsWith("/i18n")) {
|
|
1796
1966
|
return this.handleI18n(cleanPath.substring(5), method, query, context);
|
|
1797
1967
|
}
|
|
1968
|
+
if (cleanPath.startsWith("/ai")) {
|
|
1969
|
+
return this.handleAI(cleanPath, method, body, query, context);
|
|
1970
|
+
}
|
|
1798
1971
|
if (cleanPath === "/openapi.json" && method === "GET") {
|
|
1799
1972
|
const broker = this.ensureBroker();
|
|
1800
1973
|
try {
|
|
@@ -1805,7 +1978,10 @@ var HttpDispatcher = class {
|
|
|
1805
1978
|
}
|
|
1806
1979
|
const result = await this.handleApiEndpoint(cleanPath, method, body, query, context);
|
|
1807
1980
|
if (result.handled) return result;
|
|
1808
|
-
return {
|
|
1981
|
+
return {
|
|
1982
|
+
handled: true,
|
|
1983
|
+
response: this.routeNotFound(cleanPath)
|
|
1984
|
+
};
|
|
1809
1985
|
}
|
|
1810
1986
|
/**
|
|
1811
1987
|
* Handles Custom API Endpoints defined in metadata
|
|
@@ -1863,6 +2039,64 @@ var HttpDispatcher = class {
|
|
|
1863
2039
|
};
|
|
1864
2040
|
|
|
1865
2041
|
// src/dispatcher-plugin.ts
|
|
2042
|
+
function mountRouteOnServer(route, server, routePath) {
|
|
2043
|
+
const handler = async (req, res) => {
|
|
2044
|
+
try {
|
|
2045
|
+
const result = await route.handler({
|
|
2046
|
+
body: req.body,
|
|
2047
|
+
params: req.params,
|
|
2048
|
+
query: req.query
|
|
2049
|
+
});
|
|
2050
|
+
if (result.stream && result.events) {
|
|
2051
|
+
res.status(result.status);
|
|
2052
|
+
if (result.headers) {
|
|
2053
|
+
for (const [k, v] of Object.entries(result.headers)) {
|
|
2054
|
+
res.header(k, String(v));
|
|
2055
|
+
}
|
|
2056
|
+
} else {
|
|
2057
|
+
res.header("Content-Type", "text/event-stream");
|
|
2058
|
+
res.header("Cache-Control", "no-cache");
|
|
2059
|
+
res.header("Connection", "keep-alive");
|
|
2060
|
+
}
|
|
2061
|
+
if (typeof res.write === "function" && typeof res.end === "function") {
|
|
2062
|
+
for await (const event of result.events) {
|
|
2063
|
+
res.write(typeof event === "string" ? event : `data: ${JSON.stringify(event)}
|
|
2064
|
+
|
|
2065
|
+
`);
|
|
2066
|
+
}
|
|
2067
|
+
res.end();
|
|
2068
|
+
} else {
|
|
2069
|
+
const events = [];
|
|
2070
|
+
for await (const event of result.events) {
|
|
2071
|
+
events.push(event);
|
|
2072
|
+
}
|
|
2073
|
+
res.json({ events });
|
|
2074
|
+
}
|
|
2075
|
+
} else {
|
|
2076
|
+
res.status(result.status);
|
|
2077
|
+
if (result.body !== void 0) {
|
|
2078
|
+
res.json(result.body);
|
|
2079
|
+
} else {
|
|
2080
|
+
res.end();
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
} catch (err) {
|
|
2084
|
+
errorResponse(err, res);
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
const m = route.method.toLowerCase();
|
|
2088
|
+
if (m === "get" && typeof server.get === "function") {
|
|
2089
|
+
server.get(routePath, handler);
|
|
2090
|
+
return true;
|
|
2091
|
+
} else if (m === "post" && typeof server.post === "function") {
|
|
2092
|
+
server.post(routePath, handler);
|
|
2093
|
+
return true;
|
|
2094
|
+
} else if (m === "delete" && typeof server.delete === "function") {
|
|
2095
|
+
server.delete(routePath, handler);
|
|
2096
|
+
return true;
|
|
2097
|
+
}
|
|
2098
|
+
return false;
|
|
2099
|
+
}
|
|
1866
2100
|
function sendResult(result, res) {
|
|
1867
2101
|
if (result.handled) {
|
|
1868
2102
|
if (result.response) {
|
|
@@ -1880,7 +2114,15 @@ function sendResult(result, res) {
|
|
|
1880
2114
|
return;
|
|
1881
2115
|
}
|
|
1882
2116
|
}
|
|
1883
|
-
res.status(404).json({
|
|
2117
|
+
res.status(404).json({
|
|
2118
|
+
success: false,
|
|
2119
|
+
error: {
|
|
2120
|
+
message: "Not Found",
|
|
2121
|
+
code: 404,
|
|
2122
|
+
type: "ROUTE_NOT_FOUND",
|
|
2123
|
+
hint: "No handler matched this request. Check the API discovery endpoint for available routes."
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
1884
2126
|
}
|
|
1885
2127
|
function errorResponse(err, res) {
|
|
1886
2128
|
const code = err.statusCode || 500;
|
|
@@ -1912,6 +2154,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
1912
2154
|
server.get(`${prefix}/discovery`, async (_req, res) => {
|
|
1913
2155
|
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
1914
2156
|
});
|
|
2157
|
+
server.get(`${prefix}/health`, async (_req, res) => {
|
|
2158
|
+
try {
|
|
2159
|
+
const result = await dispatcher.dispatch("GET", "/health", void 0, {}, { request: _req });
|
|
2160
|
+
sendResult(result, res);
|
|
2161
|
+
} catch (err) {
|
|
2162
|
+
errorResponse(err, res);
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
1915
2165
|
server.post(`${prefix}/auth/login`, async (req, res) => {
|
|
1916
2166
|
try {
|
|
1917
2167
|
const result = await dispatcher.handleAuth("login", "POST", req.body, { request: req });
|
|
@@ -2137,6 +2387,27 @@ function createDispatcherPlugin(config = {}) {
|
|
|
2137
2387
|
}
|
|
2138
2388
|
});
|
|
2139
2389
|
ctx.logger.info("Dispatcher bridge routes registered", { prefix });
|
|
2390
|
+
ctx.hook("ai:routes", async (routes) => {
|
|
2391
|
+
if (!server) return;
|
|
2392
|
+
for (const route of routes) {
|
|
2393
|
+
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
2394
|
+
mountRouteOnServer(route, server, routePath);
|
|
2395
|
+
}
|
|
2396
|
+
ctx.logger.info(`[Dispatcher] Registered ${routes.length} AI routes`);
|
|
2397
|
+
});
|
|
2398
|
+
const cachedRoutes = kernel.__aiRoutes;
|
|
2399
|
+
if (cachedRoutes && Array.isArray(cachedRoutes) && cachedRoutes.length > 0) {
|
|
2400
|
+
let registered = 0;
|
|
2401
|
+
for (const route of cachedRoutes) {
|
|
2402
|
+
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
2403
|
+
if (mountRouteOnServer(route, server, routePath)) {
|
|
2404
|
+
registered++;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
if (registered > 0) {
|
|
2408
|
+
ctx.logger.info(`[Dispatcher] Recovered ${registered} cached AI routes (hook timing fallback)`);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2140
2411
|
}
|
|
2141
2412
|
};
|
|
2142
2413
|
}
|