@objectstack/runtime 4.0.2 → 4.0.4
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 +10 -10
- package/CHANGELOG.md +19 -0
- package/dist/index.cjs +225 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -11
- package/dist/index.d.ts +9 -11
- package/dist/index.js +225 -193
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/app-plugin.ts +9 -0
- package/src/dispatcher-plugin.ts +93 -59
- package/src/http-dispatcher.root.test.ts +0 -3
- package/src/http-dispatcher.test.ts +105 -100
- package/src/http-dispatcher.ts +183 -187
- package/vitest.config.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -618,6 +618,13 @@ var AppPlugin = class {
|
|
|
618
618
|
return;
|
|
619
619
|
}
|
|
620
620
|
ctx.logger.debug("Retrieved ObjectQL engine service", { appId });
|
|
621
|
+
if (this.bundle.datasourceMapping && Array.isArray(this.bundle.datasourceMapping)) {
|
|
622
|
+
ctx.logger.info("Configuring datasource mapping rules", {
|
|
623
|
+
appId,
|
|
624
|
+
ruleCount: this.bundle.datasourceMapping.length
|
|
625
|
+
});
|
|
626
|
+
ql.setDatasourceMapping(this.bundle.datasourceMapping);
|
|
627
|
+
}
|
|
621
628
|
const runtime = this.bundle.default || this.bundle;
|
|
622
629
|
if (runtime && typeof runtime.onEnable === "function") {
|
|
623
630
|
ctx.logger.info("Executing runtime.onEnable", {
|
|
@@ -781,6 +788,7 @@ var AppPlugin = class {
|
|
|
781
788
|
// src/http-dispatcher.ts
|
|
782
789
|
import { getEnv, resolveLocale } from "@objectstack/core";
|
|
783
790
|
import { CoreServiceName } from "@objectstack/spec/system";
|
|
791
|
+
import { pluralToSingular } from "@objectstack/spec/shared";
|
|
784
792
|
function randomUUID() {
|
|
785
793
|
if (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function") {
|
|
786
794
|
return globalThis.crypto.randomUUID();
|
|
@@ -792,7 +800,7 @@ function randomUUID() {
|
|
|
792
800
|
});
|
|
793
801
|
}
|
|
794
802
|
var HttpDispatcher = class {
|
|
795
|
-
// Casting to any to access dynamic props like
|
|
803
|
+
// Casting to any to access dynamic props like services, graphql
|
|
796
804
|
constructor(kernel) {
|
|
797
805
|
this.kernel = kernel;
|
|
798
806
|
}
|
|
@@ -826,11 +834,73 @@ var HttpDispatcher = class {
|
|
|
826
834
|
}
|
|
827
835
|
};
|
|
828
836
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
837
|
+
/**
|
|
838
|
+
* Direct data service dispatch — replaces broker.call('data.*').
|
|
839
|
+
* Tries protocol service first (supports expand/populate), falls back to ObjectQL.
|
|
840
|
+
*/
|
|
841
|
+
async callData(action, params) {
|
|
842
|
+
const protocol = await this.resolveService("protocol");
|
|
843
|
+
const qlService = await this.getObjectQLService();
|
|
844
|
+
const ql = qlService ?? await this.resolveService("objectql");
|
|
845
|
+
if (action === "create") {
|
|
846
|
+
if (ql) {
|
|
847
|
+
const res = await ql.insert(params.object, params.data);
|
|
848
|
+
const record = { ...params.data, ...res };
|
|
849
|
+
return { object: params.object, id: record.id, record };
|
|
850
|
+
}
|
|
851
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
852
|
+
}
|
|
853
|
+
if (action === "get") {
|
|
854
|
+
if (protocol && typeof protocol.getData === "function") {
|
|
855
|
+
return await protocol.getData({ object: params.object, id: params.id, expand: params.expand, select: params.select });
|
|
856
|
+
}
|
|
857
|
+
if (ql) {
|
|
858
|
+
let all = await ql.find(params.object);
|
|
859
|
+
if (!all) all = [];
|
|
860
|
+
const match = all.find((i) => i.id === params.id);
|
|
861
|
+
return match ? { object: params.object, id: params.id, record: match } : null;
|
|
862
|
+
}
|
|
863
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
864
|
+
}
|
|
865
|
+
if (action === "update") {
|
|
866
|
+
if (ql && params.id) {
|
|
867
|
+
let all = await ql.find(params.object);
|
|
868
|
+
if (all && all.value) all = all.value;
|
|
869
|
+
if (!all) all = [];
|
|
870
|
+
const existing = all.find((i) => i.id === params.id);
|
|
871
|
+
if (!existing) throw new Error("[ObjectStack] Not Found");
|
|
872
|
+
await ql.update(params.object, params.data, { where: { id: params.id } });
|
|
873
|
+
return { object: params.object, id: params.id, record: { ...existing, ...params.data } };
|
|
874
|
+
}
|
|
875
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
876
|
+
}
|
|
877
|
+
if (action === "delete") {
|
|
878
|
+
if (ql) {
|
|
879
|
+
await ql.delete(params.object, { where: { id: params.id } });
|
|
880
|
+
return { object: params.object, id: params.id, deleted: true };
|
|
881
|
+
}
|
|
882
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
883
|
+
}
|
|
884
|
+
if (action === "query" || action === "find") {
|
|
885
|
+
if (protocol && typeof protocol.findData === "function") {
|
|
886
|
+
const query = params.query || (() => {
|
|
887
|
+
const { object, ...rest } = params;
|
|
888
|
+
return rest;
|
|
889
|
+
})();
|
|
890
|
+
return await protocol.findData({ object: params.object, query });
|
|
891
|
+
}
|
|
892
|
+
if (ql) {
|
|
893
|
+
let all = await ql.find(params.object);
|
|
894
|
+
if (!Array.isArray(all) && all && all.value) all = all.value;
|
|
895
|
+
if (!all) all = [];
|
|
896
|
+
return { object: params.object, records: all, total: all.length };
|
|
897
|
+
}
|
|
898
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
899
|
+
}
|
|
900
|
+
if (action === "batch") {
|
|
901
|
+
return { object: params.object, results: [] };
|
|
902
|
+
}
|
|
903
|
+
throw { statusCode: 400, message: `Unknown data action: ${action}` };
|
|
834
904
|
}
|
|
835
905
|
/**
|
|
836
906
|
* Generates the discovery JSON response for the API root.
|
|
@@ -994,18 +1064,6 @@ var HttpDispatcher = class {
|
|
|
994
1064
|
return { handled: true, result: response };
|
|
995
1065
|
}
|
|
996
1066
|
const normalizedPath = path.replace(/^\/+/, "");
|
|
997
|
-
if (normalizedPath === "login" && method.toUpperCase() === "POST") {
|
|
998
|
-
try {
|
|
999
|
-
const broker = this.ensureBroker();
|
|
1000
|
-
const data = await broker.call("auth.login", body, { request: context.request });
|
|
1001
|
-
return { handled: true, response: { status: 200, body: data } };
|
|
1002
|
-
} catch (error) {
|
|
1003
|
-
const statusCode = error?.statusCode ?? error?.status;
|
|
1004
|
-
if (statusCode !== 500 || !error?.message?.includes("Broker not available")) {
|
|
1005
|
-
throw error;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
1067
|
return this.mockAuthFallback(normalizedPath, method, body);
|
|
1010
1068
|
}
|
|
1011
1069
|
/**
|
|
@@ -1061,22 +1119,23 @@ var HttpDispatcher = class {
|
|
|
1061
1119
|
* Standard: /metadata/:type/:name
|
|
1062
1120
|
* Fallback for backward compat: /metadata (all objects), /metadata/:objectName (get object)
|
|
1063
1121
|
*/
|
|
1064
|
-
async handleMetadata(path,
|
|
1065
|
-
const broker = this.kernel.broker ?? null;
|
|
1122
|
+
async handleMetadata(path, _context, method, body, query) {
|
|
1066
1123
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
1067
1124
|
if (parts[0] === "types") {
|
|
1125
|
+
const metadataService = await this.resolveService("metadata");
|
|
1126
|
+
if (metadataService && typeof metadataService.getRegisteredTypes === "function") {
|
|
1127
|
+
try {
|
|
1128
|
+
const types = await metadataService.getRegisteredTypes();
|
|
1129
|
+
return { handled: true, response: this.success({ types }) };
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
console.warn("[HttpDispatcher] MetadataService.getRegisteredTypes() failed:", e.message);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1068
1134
|
const protocol = await this.resolveService("protocol");
|
|
1069
1135
|
if (protocol && typeof protocol.getMetaTypes === "function") {
|
|
1070
1136
|
const result = await protocol.getMetaTypes({});
|
|
1071
1137
|
return { handled: true, response: this.success(result) };
|
|
1072
1138
|
}
|
|
1073
|
-
if (broker) {
|
|
1074
|
-
try {
|
|
1075
|
-
const data = await broker.call("metadata.types", {}, { request: context.request });
|
|
1076
|
-
return { handled: true, response: this.success(data) };
|
|
1077
|
-
} catch {
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
1139
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
1081
1140
|
}
|
|
1082
1141
|
if (parts.length === 3 && parts[2] === "published" && (!method || method === "GET")) {
|
|
@@ -1087,12 +1146,12 @@ var HttpDispatcher = class {
|
|
|
1087
1146
|
if (data === void 0) return { handled: true, response: this.error("Not found", 404) };
|
|
1088
1147
|
return { handled: true, response: this.success(data) };
|
|
1089
1148
|
}
|
|
1090
|
-
|
|
1149
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1150
|
+
if (metaSvc && typeof metaSvc.getPublished === "function") {
|
|
1091
1151
|
try {
|
|
1092
|
-
const
|
|
1093
|
-
return { handled: true, response: this.success(
|
|
1094
|
-
} catch
|
|
1095
|
-
return { handled: true, response: this.error(e.message, 404) };
|
|
1152
|
+
const fallbackData = await metaSvc.getPublished(type, name);
|
|
1153
|
+
if (fallbackData !== void 0) return { handled: true, response: this.success(fallbackData) };
|
|
1154
|
+
} catch {
|
|
1096
1155
|
}
|
|
1097
1156
|
}
|
|
1098
1157
|
return { handled: true, response: this.error("Not found", 404) };
|
|
@@ -1110,9 +1169,10 @@ var HttpDispatcher = class {
|
|
|
1110
1169
|
return { handled: true, response: this.error(e.message, 400) };
|
|
1111
1170
|
}
|
|
1112
1171
|
}
|
|
1113
|
-
|
|
1172
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1173
|
+
if (metaSvc && typeof metaSvc.saveItem === "function") {
|
|
1114
1174
|
try {
|
|
1115
|
-
const data = await
|
|
1175
|
+
const data = await metaSvc.saveItem(type, name, body);
|
|
1116
1176
|
return { handled: true, response: this.success(data) };
|
|
1117
1177
|
} catch (e) {
|
|
1118
1178
|
return { handled: true, response: this.error(e.message || "Save not supported", 501) };
|
|
@@ -1122,10 +1182,6 @@ var HttpDispatcher = class {
|
|
|
1122
1182
|
}
|
|
1123
1183
|
try {
|
|
1124
1184
|
if (type === "objects" || type === "object") {
|
|
1125
|
-
if (broker) {
|
|
1126
|
-
const data = await broker.call("metadata.getObject", { objectName: name }, { request: context.request });
|
|
1127
|
-
return { handled: true, response: this.success(data) };
|
|
1128
|
-
}
|
|
1129
1185
|
const qlService = await this.getObjectQLService();
|
|
1130
1186
|
if (qlService?.registry) {
|
|
1131
1187
|
const data = qlService.registry.getObject(name);
|
|
@@ -1133,7 +1189,7 @@ var HttpDispatcher = class {
|
|
|
1133
1189
|
}
|
|
1134
1190
|
return { handled: true, response: this.error("Not found", 404) };
|
|
1135
1191
|
}
|
|
1136
|
-
const singularType =
|
|
1192
|
+
const singularType = pluralToSingular(type);
|
|
1137
1193
|
const protocol = await this.resolveService("protocol");
|
|
1138
1194
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
1139
1195
|
try {
|
|
@@ -1142,10 +1198,13 @@ var HttpDispatcher = class {
|
|
|
1142
1198
|
} catch (e) {
|
|
1143
1199
|
}
|
|
1144
1200
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1201
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1202
|
+
if (metaSvc && typeof metaSvc.getItem === "function") {
|
|
1203
|
+
try {
|
|
1204
|
+
const data = await metaSvc.getItem(singularType, name);
|
|
1205
|
+
if (data) return { handled: true, response: this.success(data) };
|
|
1206
|
+
} catch {
|
|
1207
|
+
}
|
|
1149
1208
|
}
|
|
1150
1209
|
return { handled: true, response: this.error("Not found", 404) };
|
|
1151
1210
|
} catch (e) {
|
|
@@ -1165,23 +1224,19 @@ var HttpDispatcher = class {
|
|
|
1165
1224
|
} catch {
|
|
1166
1225
|
}
|
|
1167
1226
|
}
|
|
1168
|
-
|
|
1227
|
+
const metadataService = await this.getService(CoreServiceName.enum.metadata);
|
|
1228
|
+
if (metadataService && typeof metadataService.list === "function") {
|
|
1169
1229
|
try {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1230
|
+
let items = await metadataService.list(typeOrName);
|
|
1231
|
+
if (packageId && items && items.length > 0) {
|
|
1232
|
+
items = items.filter((item) => item?._packageId === packageId);
|
|
1173
1233
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
return { handled: true, response: this.success(data) };
|
|
1234
|
+
if (items && items.length > 0) {
|
|
1235
|
+
return { handled: true, response: this.success({ type: typeOrName, items }) };
|
|
1177
1236
|
}
|
|
1178
|
-
} catch {
|
|
1179
|
-
}
|
|
1180
|
-
try {
|
|
1181
|
-
const data = await broker.call("metadata.getObject", { objectName: typeOrName }, { request: context.request });
|
|
1182
|
-
return { handled: true, response: this.success(data) };
|
|
1183
1237
|
} catch (e) {
|
|
1184
|
-
|
|
1238
|
+
const sanitizedType = String(typeOrName).replace(/[\r\n\t]/g, "");
|
|
1239
|
+
console.debug(`[HttpDispatcher] MetadataService.list() failed for type:`, sanitizedType, "error:", e.message);
|
|
1185
1240
|
}
|
|
1186
1241
|
}
|
|
1187
1242
|
const qlService = await this.getObjectQLService();
|
|
@@ -1200,18 +1255,19 @@ var HttpDispatcher = class {
|
|
|
1200
1255
|
return { handled: true, response: this.error("Not found", 404) };
|
|
1201
1256
|
}
|
|
1202
1257
|
if (parts.length === 0) {
|
|
1258
|
+
const metadataService = await this.resolveService("metadata");
|
|
1259
|
+
if (metadataService && typeof metadataService.getRegisteredTypes === "function") {
|
|
1260
|
+
try {
|
|
1261
|
+
const types = await metadataService.getRegisteredTypes();
|
|
1262
|
+
return { handled: true, response: this.success({ types }) };
|
|
1263
|
+
} catch {
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1203
1266
|
const protocol = await this.resolveService("protocol");
|
|
1204
1267
|
if (protocol && typeof protocol.getMetaTypes === "function") {
|
|
1205
1268
|
const result = await protocol.getMetaTypes({});
|
|
1206
1269
|
return { handled: true, response: this.success(result) };
|
|
1207
1270
|
}
|
|
1208
|
-
if (broker) {
|
|
1209
|
-
try {
|
|
1210
|
-
const data = await broker.call("metadata.types", {}, { request: context.request });
|
|
1211
|
-
return { handled: true, response: this.success(data) };
|
|
1212
|
-
} catch {
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
1271
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
1216
1272
|
}
|
|
1217
1273
|
return { handled: false };
|
|
@@ -1220,8 +1276,7 @@ var HttpDispatcher = class {
|
|
|
1220
1276
|
* Handles Data requests
|
|
1221
1277
|
* path: sub-path after /data/ (e.g. "contacts", "contacts/123", "contacts/query")
|
|
1222
1278
|
*/
|
|
1223
|
-
async handleData(path, method, body, query,
|
|
1224
|
-
const broker = this.ensureBroker();
|
|
1279
|
+
async handleData(path, method, body, query, _context) {
|
|
1225
1280
|
const parts = path.replace(/^\/+/, "").split("/");
|
|
1226
1281
|
const objectName = parts[0];
|
|
1227
1282
|
if (!objectName) {
|
|
@@ -1231,11 +1286,11 @@ var HttpDispatcher = class {
|
|
|
1231
1286
|
if (parts.length > 1) {
|
|
1232
1287
|
const action = parts[1];
|
|
1233
1288
|
if (action === "query" && m === "POST") {
|
|
1234
|
-
const result = await
|
|
1289
|
+
const result = await this.callData("query", { object: objectName, ...body });
|
|
1235
1290
|
return { handled: true, response: this.success(result) };
|
|
1236
1291
|
}
|
|
1237
1292
|
if (action === "batch" && m === "POST") {
|
|
1238
|
-
const result = await
|
|
1293
|
+
const result = await this.callData("batch", { object: objectName, ...body });
|
|
1239
1294
|
return { handled: true, response: this.success(result) };
|
|
1240
1295
|
}
|
|
1241
1296
|
if (parts.length === 2 && m === "GET") {
|
|
@@ -1244,17 +1299,17 @@ var HttpDispatcher = class {
|
|
|
1244
1299
|
const allowedParams = {};
|
|
1245
1300
|
if (select != null) allowedParams.select = select;
|
|
1246
1301
|
if (expand != null) allowedParams.expand = expand;
|
|
1247
|
-
const result = await
|
|
1302
|
+
const result = await this.callData("get", { object: objectName, id, ...allowedParams });
|
|
1248
1303
|
return { handled: true, response: this.success(result) };
|
|
1249
1304
|
}
|
|
1250
1305
|
if (parts.length === 2 && m === "PATCH") {
|
|
1251
1306
|
const id = parts[1];
|
|
1252
|
-
const result = await
|
|
1307
|
+
const result = await this.callData("update", { object: objectName, id, data: body });
|
|
1253
1308
|
return { handled: true, response: this.success(result) };
|
|
1254
1309
|
}
|
|
1255
1310
|
if (parts.length === 2 && m === "DELETE") {
|
|
1256
1311
|
const id = parts[1];
|
|
1257
|
-
const result = await
|
|
1312
|
+
const result = await this.callData("delete", { object: objectName, id });
|
|
1258
1313
|
return { handled: true, response: this.success(result) };
|
|
1259
1314
|
}
|
|
1260
1315
|
} else {
|
|
@@ -1281,11 +1336,11 @@ var HttpDispatcher = class {
|
|
|
1281
1336
|
normalized.offset = normalized.skip;
|
|
1282
1337
|
delete normalized.skip;
|
|
1283
1338
|
}
|
|
1284
|
-
const result = await
|
|
1339
|
+
const result = await this.callData("query", { object: objectName, query: normalized });
|
|
1285
1340
|
return { handled: true, response: this.success(result) };
|
|
1286
1341
|
}
|
|
1287
1342
|
if (m === "POST") {
|
|
1288
|
-
const result = await
|
|
1343
|
+
const result = await this.callData("create", { object: objectName, data: body });
|
|
1289
1344
|
const res = this.success(result);
|
|
1290
1345
|
res.status = 201;
|
|
1291
1346
|
return { handled: true, response: res };
|
|
@@ -1387,18 +1442,14 @@ var HttpDispatcher = class {
|
|
|
1387
1442
|
* - POST /packages/:id/publish → publish a package (metadata snapshot)
|
|
1388
1443
|
* - POST /packages/:id/revert → revert a package to last published state
|
|
1389
1444
|
*
|
|
1390
|
-
* Uses ObjectQL SchemaRegistry directly (via the 'objectql' service)
|
|
1391
|
-
* with broker fallback for backward compatibility.
|
|
1445
|
+
* Uses ObjectQL SchemaRegistry directly (via the 'objectql' service).
|
|
1392
1446
|
*/
|
|
1393
|
-
async handlePackages(path, method, body, query,
|
|
1447
|
+
async handlePackages(path, method, body, query, _context) {
|
|
1394
1448
|
const m = method.toUpperCase();
|
|
1395
1449
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
1396
1450
|
const qlService = await this.getObjectQLService();
|
|
1397
1451
|
const registry = qlService?.registry;
|
|
1398
1452
|
if (!registry) {
|
|
1399
|
-
if (this.kernel.broker) {
|
|
1400
|
-
return this.handlePackagesViaBroker(parts, m, body, query, context);
|
|
1401
|
-
}
|
|
1402
1453
|
return { handled: true, response: this.error("Package service not available", 503) };
|
|
1403
1454
|
}
|
|
1404
1455
|
try {
|
|
@@ -1437,10 +1488,6 @@ var HttpDispatcher = class {
|
|
|
1437
1488
|
const result = await metadataService.publishPackage(id, body || {});
|
|
1438
1489
|
return { handled: true, response: this.success(result) };
|
|
1439
1490
|
}
|
|
1440
|
-
if (this.kernel.broker) {
|
|
1441
|
-
const result = await this.kernel.broker.call("metadata.publishPackage", { packageId: id, ...body }, { request: context.request });
|
|
1442
|
-
return { handled: true, response: this.success(result) };
|
|
1443
|
-
}
|
|
1444
1491
|
return { handled: true, response: this.error("Metadata service not available", 503) };
|
|
1445
1492
|
}
|
|
1446
1493
|
if (parts.length === 2 && parts[1] === "revert" && m === "POST") {
|
|
@@ -1450,10 +1497,6 @@ var HttpDispatcher = class {
|
|
|
1450
1497
|
await metadataService.revertPackage(id);
|
|
1451
1498
|
return { handled: true, response: this.success({ success: true }) };
|
|
1452
1499
|
}
|
|
1453
|
-
if (this.kernel.broker) {
|
|
1454
|
-
await this.kernel.broker.call("metadata.revertPackage", { packageId: id }, { request: context.request });
|
|
1455
|
-
return { handled: true, response: this.success({ success: true }) };
|
|
1456
|
-
}
|
|
1457
1500
|
return { handled: true, response: this.error("Metadata service not available", 503) };
|
|
1458
1501
|
}
|
|
1459
1502
|
if (parts.length === 1 && m === "GET") {
|
|
@@ -1473,47 +1516,6 @@ var HttpDispatcher = class {
|
|
|
1473
1516
|
}
|
|
1474
1517
|
return { handled: false };
|
|
1475
1518
|
}
|
|
1476
|
-
/**
|
|
1477
|
-
* Fallback: handle packages via broker (for backward compatibility)
|
|
1478
|
-
*/
|
|
1479
|
-
async handlePackagesViaBroker(parts, m, body, query, context) {
|
|
1480
|
-
const broker = this.kernel.broker;
|
|
1481
|
-
try {
|
|
1482
|
-
if (parts.length === 0 && m === "GET") {
|
|
1483
|
-
const result = await broker.call("package.list", query || {}, { request: context.request });
|
|
1484
|
-
return { handled: true, response: this.success(result) };
|
|
1485
|
-
}
|
|
1486
|
-
if (parts.length === 0 && m === "POST") {
|
|
1487
|
-
const result = await broker.call("package.install", body, { request: context.request });
|
|
1488
|
-
const res = this.success(result);
|
|
1489
|
-
res.status = 201;
|
|
1490
|
-
return { handled: true, response: res };
|
|
1491
|
-
}
|
|
1492
|
-
if (parts.length === 2 && parts[1] === "enable" && m === "PATCH") {
|
|
1493
|
-
const id = decodeURIComponent(parts[0]);
|
|
1494
|
-
const result = await broker.call("package.enable", { id }, { request: context.request });
|
|
1495
|
-
return { handled: true, response: this.success(result) };
|
|
1496
|
-
}
|
|
1497
|
-
if (parts.length === 2 && parts[1] === "disable" && m === "PATCH") {
|
|
1498
|
-
const id = decodeURIComponent(parts[0]);
|
|
1499
|
-
const result = await broker.call("package.disable", { id }, { request: context.request });
|
|
1500
|
-
return { handled: true, response: this.success(result) };
|
|
1501
|
-
}
|
|
1502
|
-
if (parts.length === 1 && m === "GET") {
|
|
1503
|
-
const id = decodeURIComponent(parts[0]);
|
|
1504
|
-
const result = await broker.call("package.get", { id }, { request: context.request });
|
|
1505
|
-
return { handled: true, response: this.success(result) };
|
|
1506
|
-
}
|
|
1507
|
-
if (parts.length === 1 && m === "DELETE") {
|
|
1508
|
-
const id = decodeURIComponent(parts[0]);
|
|
1509
|
-
const result = await broker.call("package.uninstall", { id }, { request: context.request });
|
|
1510
|
-
return { handled: true, response: this.success(result) };
|
|
1511
|
-
}
|
|
1512
|
-
} catch (e) {
|
|
1513
|
-
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
1514
|
-
}
|
|
1515
|
-
return { handled: false };
|
|
1516
|
-
}
|
|
1517
1519
|
/**
|
|
1518
1520
|
* Handles Storage requests
|
|
1519
1521
|
* path: sub-path after /storage/
|
|
@@ -1722,9 +1724,6 @@ var HttpDispatcher = class {
|
|
|
1722
1724
|
}
|
|
1723
1725
|
return null;
|
|
1724
1726
|
}
|
|
1725
|
-
capitalize(s) {
|
|
1726
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1727
|
-
}
|
|
1728
1727
|
/**
|
|
1729
1728
|
* Handle AI service routes (/ai/chat, /ai/models, /ai/conversations, etc.)
|
|
1730
1729
|
* Resolves the AI service and its built-in route handlers, then dispatches.
|
|
@@ -1861,10 +1860,12 @@ var HttpDispatcher = class {
|
|
|
1861
1860
|
return this.handleAI(cleanPath, method, body, query, context);
|
|
1862
1861
|
}
|
|
1863
1862
|
if (cleanPath === "/openapi.json" && method === "GET") {
|
|
1864
|
-
const broker = this.ensureBroker();
|
|
1865
1863
|
try {
|
|
1866
|
-
const
|
|
1867
|
-
|
|
1864
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1865
|
+
if (metaSvc && typeof metaSvc.generateOpenApi === "function") {
|
|
1866
|
+
const result2 = await metaSvc.generateOpenApi({});
|
|
1867
|
+
return { handled: true, response: this.success(result2) };
|
|
1868
|
+
}
|
|
1868
1869
|
} catch (e) {
|
|
1869
1870
|
}
|
|
1870
1871
|
}
|
|
@@ -1879,37 +1880,48 @@ var HttpDispatcher = class {
|
|
|
1879
1880
|
* Handles Custom API Endpoints defined in metadata
|
|
1880
1881
|
*/
|
|
1881
1882
|
async handleApiEndpoint(path, method, body, query, context) {
|
|
1882
|
-
const broker = this.ensureBroker();
|
|
1883
1883
|
try {
|
|
1884
|
-
const
|
|
1884
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1885
|
+
if (!metaSvc || typeof metaSvc.matchEndpoint !== "function") {
|
|
1886
|
+
return { handled: false };
|
|
1887
|
+
}
|
|
1888
|
+
const endpoint = await metaSvc.matchEndpoint({ path, method });
|
|
1885
1889
|
if (endpoint) {
|
|
1886
1890
|
if (endpoint.type === "flow") {
|
|
1887
|
-
const
|
|
1891
|
+
const automationSvc = await this.resolveService("automation");
|
|
1892
|
+
if (!automationSvc || typeof automationSvc.runFlow !== "function") {
|
|
1893
|
+
return { handled: true, response: this.error("Automation service not available", 503) };
|
|
1894
|
+
}
|
|
1895
|
+
const result = await automationSvc.runFlow({
|
|
1888
1896
|
flowId: endpoint.target,
|
|
1889
1897
|
inputs: { ...query, ...body, _request: context.request }
|
|
1890
1898
|
});
|
|
1891
1899
|
return { handled: true, response: this.success(result) };
|
|
1892
1900
|
}
|
|
1893
1901
|
if (endpoint.type === "script") {
|
|
1894
|
-
const
|
|
1902
|
+
const automationSvc = await this.resolveService("automation");
|
|
1903
|
+
if (!automationSvc || typeof automationSvc.runScript !== "function") {
|
|
1904
|
+
return { handled: true, response: this.error("Automation service not available", 503) };
|
|
1905
|
+
}
|
|
1906
|
+
const result = await automationSvc.runScript({
|
|
1895
1907
|
scriptName: endpoint.target,
|
|
1896
1908
|
context: { ...query, ...body, request: context.request }
|
|
1897
|
-
}
|
|
1909
|
+
});
|
|
1898
1910
|
return { handled: true, response: this.success(result) };
|
|
1899
1911
|
}
|
|
1900
1912
|
if (endpoint.type === "object_operation") {
|
|
1901
1913
|
if (endpoint.objectParams) {
|
|
1902
1914
|
const { object, operation } = endpoint.objectParams;
|
|
1903
1915
|
if (operation === "find") {
|
|
1904
|
-
const result = await
|
|
1916
|
+
const result = await this.callData("query", { object, query });
|
|
1905
1917
|
return { handled: true, response: this.success(result.records, { total: result.total }) };
|
|
1906
1918
|
}
|
|
1907
1919
|
if (operation === "get" && query.id) {
|
|
1908
|
-
const result = await
|
|
1920
|
+
const result = await this.callData("get", { object, id: query.id });
|
|
1909
1921
|
return { handled: true, response: this.success(result) };
|
|
1910
1922
|
}
|
|
1911
1923
|
if (operation === "create") {
|
|
1912
|
-
const result = await
|
|
1924
|
+
const result = await this.callData("create", { object, data: body });
|
|
1913
1925
|
return { handled: true, response: this.success(result) };
|
|
1914
1926
|
}
|
|
1915
1927
|
}
|
|
@@ -1931,6 +1943,64 @@ var HttpDispatcher = class {
|
|
|
1931
1943
|
};
|
|
1932
1944
|
|
|
1933
1945
|
// src/dispatcher-plugin.ts
|
|
1946
|
+
function mountRouteOnServer(route, server, routePath) {
|
|
1947
|
+
const handler = async (req, res) => {
|
|
1948
|
+
try {
|
|
1949
|
+
const result = await route.handler({
|
|
1950
|
+
body: req.body,
|
|
1951
|
+
params: req.params,
|
|
1952
|
+
query: req.query
|
|
1953
|
+
});
|
|
1954
|
+
if (result.stream && result.events) {
|
|
1955
|
+
res.status(result.status);
|
|
1956
|
+
if (result.headers) {
|
|
1957
|
+
for (const [k, v] of Object.entries(result.headers)) {
|
|
1958
|
+
res.header(k, String(v));
|
|
1959
|
+
}
|
|
1960
|
+
} else {
|
|
1961
|
+
res.header("Content-Type", "text/event-stream");
|
|
1962
|
+
res.header("Cache-Control", "no-cache");
|
|
1963
|
+
res.header("Connection", "keep-alive");
|
|
1964
|
+
}
|
|
1965
|
+
if (typeof res.write === "function" && typeof res.end === "function") {
|
|
1966
|
+
for await (const event of result.events) {
|
|
1967
|
+
res.write(typeof event === "string" ? event : `data: ${JSON.stringify(event)}
|
|
1968
|
+
|
|
1969
|
+
`);
|
|
1970
|
+
}
|
|
1971
|
+
res.end();
|
|
1972
|
+
} else {
|
|
1973
|
+
const events = [];
|
|
1974
|
+
for await (const event of result.events) {
|
|
1975
|
+
events.push(event);
|
|
1976
|
+
}
|
|
1977
|
+
res.json({ events });
|
|
1978
|
+
}
|
|
1979
|
+
} else {
|
|
1980
|
+
res.status(result.status);
|
|
1981
|
+
if (result.body !== void 0) {
|
|
1982
|
+
res.json(result.body);
|
|
1983
|
+
} else {
|
|
1984
|
+
res.end();
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
} catch (err) {
|
|
1988
|
+
errorResponse(err, res);
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
const m = route.method.toLowerCase();
|
|
1992
|
+
if (m === "get" && typeof server.get === "function") {
|
|
1993
|
+
server.get(routePath, handler);
|
|
1994
|
+
return true;
|
|
1995
|
+
} else if (m === "post" && typeof server.post === "function") {
|
|
1996
|
+
server.post(routePath, handler);
|
|
1997
|
+
return true;
|
|
1998
|
+
} else if (m === "delete" && typeof server.delete === "function") {
|
|
1999
|
+
server.delete(routePath, handler);
|
|
2000
|
+
return true;
|
|
2001
|
+
}
|
|
2002
|
+
return false;
|
|
2003
|
+
}
|
|
1934
2004
|
function sendResult(result, res) {
|
|
1935
2005
|
if (result.handled) {
|
|
1936
2006
|
if (result.response) {
|
|
@@ -2225,61 +2295,23 @@ function createDispatcherPlugin(config = {}) {
|
|
|
2225
2295
|
if (!server) return;
|
|
2226
2296
|
for (const route of routes) {
|
|
2227
2297
|
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
2228
|
-
|
|
2229
|
-
try {
|
|
2230
|
-
const result = await route.handler({
|
|
2231
|
-
body: req.body,
|
|
2232
|
-
params: req.params,
|
|
2233
|
-
query: req.query
|
|
2234
|
-
});
|
|
2235
|
-
if (result.stream && result.events) {
|
|
2236
|
-
res.status(result.status);
|
|
2237
|
-
if (result.headers) {
|
|
2238
|
-
for (const [k, v] of Object.entries(result.headers)) {
|
|
2239
|
-
res.header(k, v);
|
|
2240
|
-
}
|
|
2241
|
-
} else {
|
|
2242
|
-
res.header("Content-Type", "text/event-stream");
|
|
2243
|
-
res.header("Cache-Control", "no-cache");
|
|
2244
|
-
res.header("Connection", "keep-alive");
|
|
2245
|
-
}
|
|
2246
|
-
if (typeof res.write === "function" && typeof res.end === "function") {
|
|
2247
|
-
for await (const event of result.events) {
|
|
2248
|
-
res.write(typeof event === "string" ? event : `data: ${JSON.stringify(event)}
|
|
2249
|
-
|
|
2250
|
-
`);
|
|
2251
|
-
}
|
|
2252
|
-
res.end();
|
|
2253
|
-
} else {
|
|
2254
|
-
const events = [];
|
|
2255
|
-
for await (const event of result.events) {
|
|
2256
|
-
events.push(event);
|
|
2257
|
-
}
|
|
2258
|
-
res.json({ events });
|
|
2259
|
-
}
|
|
2260
|
-
} else {
|
|
2261
|
-
res.status(result.status);
|
|
2262
|
-
if (result.body !== void 0) {
|
|
2263
|
-
res.json(result.body);
|
|
2264
|
-
} else {
|
|
2265
|
-
res.end();
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
} catch (err) {
|
|
2269
|
-
errorResponse(err, res);
|
|
2270
|
-
}
|
|
2271
|
-
};
|
|
2272
|
-
const m = route.method.toLowerCase();
|
|
2273
|
-
if (m === "get" && typeof server.get === "function") {
|
|
2274
|
-
server.get(routePath, handler);
|
|
2275
|
-
} else if (m === "post" && typeof server.post === "function") {
|
|
2276
|
-
server.post(routePath, handler);
|
|
2277
|
-
} else if (m === "delete" && typeof server.delete === "function") {
|
|
2278
|
-
server.delete(routePath, handler);
|
|
2279
|
-
}
|
|
2298
|
+
mountRouteOnServer(route, server, routePath);
|
|
2280
2299
|
}
|
|
2281
2300
|
ctx.logger.info(`[Dispatcher] Registered ${routes.length} AI routes`);
|
|
2282
2301
|
});
|
|
2302
|
+
const cachedRoutes = kernel.__aiRoutes;
|
|
2303
|
+
if (cachedRoutes && Array.isArray(cachedRoutes) && cachedRoutes.length > 0) {
|
|
2304
|
+
let registered = 0;
|
|
2305
|
+
for (const route of cachedRoutes) {
|
|
2306
|
+
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
2307
|
+
if (mountRouteOnServer(route, server, routePath)) {
|
|
2308
|
+
registered++;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
if (registered > 0) {
|
|
2312
|
+
ctx.logger.info(`[Dispatcher] Recovered ${registered} cached AI routes (hook timing fallback)`);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2283
2315
|
}
|
|
2284
2316
|
};
|
|
2285
2317
|
}
|