@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.cjs
CHANGED
|
@@ -665,6 +665,13 @@ var AppPlugin = class {
|
|
|
665
665
|
return;
|
|
666
666
|
}
|
|
667
667
|
ctx.logger.debug("Retrieved ObjectQL engine service", { appId });
|
|
668
|
+
if (this.bundle.datasourceMapping && Array.isArray(this.bundle.datasourceMapping)) {
|
|
669
|
+
ctx.logger.info("Configuring datasource mapping rules", {
|
|
670
|
+
appId,
|
|
671
|
+
ruleCount: this.bundle.datasourceMapping.length
|
|
672
|
+
});
|
|
673
|
+
ql.setDatasourceMapping(this.bundle.datasourceMapping);
|
|
674
|
+
}
|
|
668
675
|
const runtime = this.bundle.default || this.bundle;
|
|
669
676
|
if (runtime && typeof runtime.onEnable === "function") {
|
|
670
677
|
ctx.logger.info("Executing runtime.onEnable", {
|
|
@@ -828,6 +835,7 @@ var AppPlugin = class {
|
|
|
828
835
|
// src/http-dispatcher.ts
|
|
829
836
|
var import_core2 = require("@objectstack/core");
|
|
830
837
|
var import_system = require("@objectstack/spec/system");
|
|
838
|
+
var import_shared = require("@objectstack/spec/shared");
|
|
831
839
|
function randomUUID() {
|
|
832
840
|
if (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function") {
|
|
833
841
|
return globalThis.crypto.randomUUID();
|
|
@@ -839,7 +847,7 @@ function randomUUID() {
|
|
|
839
847
|
});
|
|
840
848
|
}
|
|
841
849
|
var HttpDispatcher = class {
|
|
842
|
-
// Casting to any to access dynamic props like
|
|
850
|
+
// Casting to any to access dynamic props like services, graphql
|
|
843
851
|
constructor(kernel) {
|
|
844
852
|
this.kernel = kernel;
|
|
845
853
|
}
|
|
@@ -873,11 +881,73 @@ var HttpDispatcher = class {
|
|
|
873
881
|
}
|
|
874
882
|
};
|
|
875
883
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
884
|
+
/**
|
|
885
|
+
* Direct data service dispatch — replaces broker.call('data.*').
|
|
886
|
+
* Tries protocol service first (supports expand/populate), falls back to ObjectQL.
|
|
887
|
+
*/
|
|
888
|
+
async callData(action, params) {
|
|
889
|
+
const protocol = await this.resolveService("protocol");
|
|
890
|
+
const qlService = await this.getObjectQLService();
|
|
891
|
+
const ql = qlService ?? await this.resolveService("objectql");
|
|
892
|
+
if (action === "create") {
|
|
893
|
+
if (ql) {
|
|
894
|
+
const res = await ql.insert(params.object, params.data);
|
|
895
|
+
const record = { ...params.data, ...res };
|
|
896
|
+
return { object: params.object, id: record.id, record };
|
|
897
|
+
}
|
|
898
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
899
|
+
}
|
|
900
|
+
if (action === "get") {
|
|
901
|
+
if (protocol && typeof protocol.getData === "function") {
|
|
902
|
+
return await protocol.getData({ object: params.object, id: params.id, expand: params.expand, select: params.select });
|
|
903
|
+
}
|
|
904
|
+
if (ql) {
|
|
905
|
+
let all = await ql.find(params.object);
|
|
906
|
+
if (!all) all = [];
|
|
907
|
+
const match = all.find((i) => i.id === params.id);
|
|
908
|
+
return match ? { object: params.object, id: params.id, record: match } : null;
|
|
909
|
+
}
|
|
910
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
911
|
+
}
|
|
912
|
+
if (action === "update") {
|
|
913
|
+
if (ql && params.id) {
|
|
914
|
+
let all = await ql.find(params.object);
|
|
915
|
+
if (all && all.value) all = all.value;
|
|
916
|
+
if (!all) all = [];
|
|
917
|
+
const existing = all.find((i) => i.id === params.id);
|
|
918
|
+
if (!existing) throw new Error("[ObjectStack] Not Found");
|
|
919
|
+
await ql.update(params.object, params.data, { where: { id: params.id } });
|
|
920
|
+
return { object: params.object, id: params.id, record: { ...existing, ...params.data } };
|
|
921
|
+
}
|
|
922
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
923
|
+
}
|
|
924
|
+
if (action === "delete") {
|
|
925
|
+
if (ql) {
|
|
926
|
+
await ql.delete(params.object, { where: { id: params.id } });
|
|
927
|
+
return { object: params.object, id: params.id, deleted: true };
|
|
928
|
+
}
|
|
929
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
930
|
+
}
|
|
931
|
+
if (action === "query" || action === "find") {
|
|
932
|
+
if (protocol && typeof protocol.findData === "function") {
|
|
933
|
+
const query = params.query || (() => {
|
|
934
|
+
const { object, ...rest } = params;
|
|
935
|
+
return rest;
|
|
936
|
+
})();
|
|
937
|
+
return await protocol.findData({ object: params.object, query });
|
|
938
|
+
}
|
|
939
|
+
if (ql) {
|
|
940
|
+
let all = await ql.find(params.object);
|
|
941
|
+
if (!Array.isArray(all) && all && all.value) all = all.value;
|
|
942
|
+
if (!all) all = [];
|
|
943
|
+
return { object: params.object, records: all, total: all.length };
|
|
944
|
+
}
|
|
945
|
+
throw { statusCode: 503, message: "Data service not available" };
|
|
946
|
+
}
|
|
947
|
+
if (action === "batch") {
|
|
948
|
+
return { object: params.object, results: [] };
|
|
949
|
+
}
|
|
950
|
+
throw { statusCode: 400, message: `Unknown data action: ${action}` };
|
|
881
951
|
}
|
|
882
952
|
/**
|
|
883
953
|
* Generates the discovery JSON response for the API root.
|
|
@@ -1041,18 +1111,6 @@ var HttpDispatcher = class {
|
|
|
1041
1111
|
return { handled: true, result: response };
|
|
1042
1112
|
}
|
|
1043
1113
|
const normalizedPath = path.replace(/^\/+/, "");
|
|
1044
|
-
if (normalizedPath === "login" && method.toUpperCase() === "POST") {
|
|
1045
|
-
try {
|
|
1046
|
-
const broker = this.ensureBroker();
|
|
1047
|
-
const data = await broker.call("auth.login", body, { request: context.request });
|
|
1048
|
-
return { handled: true, response: { status: 200, body: data } };
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
const statusCode = error?.statusCode ?? error?.status;
|
|
1051
|
-
if (statusCode !== 500 || !error?.message?.includes("Broker not available")) {
|
|
1052
|
-
throw error;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
1114
|
return this.mockAuthFallback(normalizedPath, method, body);
|
|
1057
1115
|
}
|
|
1058
1116
|
/**
|
|
@@ -1108,22 +1166,23 @@ var HttpDispatcher = class {
|
|
|
1108
1166
|
* Standard: /metadata/:type/:name
|
|
1109
1167
|
* Fallback for backward compat: /metadata (all objects), /metadata/:objectName (get object)
|
|
1110
1168
|
*/
|
|
1111
|
-
async handleMetadata(path,
|
|
1112
|
-
const broker = this.kernel.broker ?? null;
|
|
1169
|
+
async handleMetadata(path, _context, method, body, query) {
|
|
1113
1170
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
1114
1171
|
if (parts[0] === "types") {
|
|
1172
|
+
const metadataService = await this.resolveService("metadata");
|
|
1173
|
+
if (metadataService && typeof metadataService.getRegisteredTypes === "function") {
|
|
1174
|
+
try {
|
|
1175
|
+
const types = await metadataService.getRegisteredTypes();
|
|
1176
|
+
return { handled: true, response: this.success({ types }) };
|
|
1177
|
+
} catch (e) {
|
|
1178
|
+
console.warn("[HttpDispatcher] MetadataService.getRegisteredTypes() failed:", e.message);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1115
1181
|
const protocol = await this.resolveService("protocol");
|
|
1116
1182
|
if (protocol && typeof protocol.getMetaTypes === "function") {
|
|
1117
1183
|
const result = await protocol.getMetaTypes({});
|
|
1118
1184
|
return { handled: true, response: this.success(result) };
|
|
1119
1185
|
}
|
|
1120
|
-
if (broker) {
|
|
1121
|
-
try {
|
|
1122
|
-
const data = await broker.call("metadata.types", {}, { request: context.request });
|
|
1123
|
-
return { handled: true, response: this.success(data) };
|
|
1124
|
-
} catch {
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
1186
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
1128
1187
|
}
|
|
1129
1188
|
if (parts.length === 3 && parts[2] === "published" && (!method || method === "GET")) {
|
|
@@ -1134,12 +1193,12 @@ var HttpDispatcher = class {
|
|
|
1134
1193
|
if (data === void 0) return { handled: true, response: this.error("Not found", 404) };
|
|
1135
1194
|
return { handled: true, response: this.success(data) };
|
|
1136
1195
|
}
|
|
1137
|
-
|
|
1196
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1197
|
+
if (metaSvc && typeof metaSvc.getPublished === "function") {
|
|
1138
1198
|
try {
|
|
1139
|
-
const
|
|
1140
|
-
return { handled: true, response: this.success(
|
|
1141
|
-
} catch
|
|
1142
|
-
return { handled: true, response: this.error(e.message, 404) };
|
|
1199
|
+
const fallbackData = await metaSvc.getPublished(type, name);
|
|
1200
|
+
if (fallbackData !== void 0) return { handled: true, response: this.success(fallbackData) };
|
|
1201
|
+
} catch {
|
|
1143
1202
|
}
|
|
1144
1203
|
}
|
|
1145
1204
|
return { handled: true, response: this.error("Not found", 404) };
|
|
@@ -1157,9 +1216,10 @@ var HttpDispatcher = class {
|
|
|
1157
1216
|
return { handled: true, response: this.error(e.message, 400) };
|
|
1158
1217
|
}
|
|
1159
1218
|
}
|
|
1160
|
-
|
|
1219
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1220
|
+
if (metaSvc && typeof metaSvc.saveItem === "function") {
|
|
1161
1221
|
try {
|
|
1162
|
-
const data = await
|
|
1222
|
+
const data = await metaSvc.saveItem(type, name, body);
|
|
1163
1223
|
return { handled: true, response: this.success(data) };
|
|
1164
1224
|
} catch (e) {
|
|
1165
1225
|
return { handled: true, response: this.error(e.message || "Save not supported", 501) };
|
|
@@ -1169,10 +1229,6 @@ var HttpDispatcher = class {
|
|
|
1169
1229
|
}
|
|
1170
1230
|
try {
|
|
1171
1231
|
if (type === "objects" || type === "object") {
|
|
1172
|
-
if (broker) {
|
|
1173
|
-
const data = await broker.call("metadata.getObject", { objectName: name }, { request: context.request });
|
|
1174
|
-
return { handled: true, response: this.success(data) };
|
|
1175
|
-
}
|
|
1176
1232
|
const qlService = await this.getObjectQLService();
|
|
1177
1233
|
if (qlService?.registry) {
|
|
1178
1234
|
const data = qlService.registry.getObject(name);
|
|
@@ -1180,7 +1236,7 @@ var HttpDispatcher = class {
|
|
|
1180
1236
|
}
|
|
1181
1237
|
return { handled: true, response: this.error("Not found", 404) };
|
|
1182
1238
|
}
|
|
1183
|
-
const singularType =
|
|
1239
|
+
const singularType = (0, import_shared.pluralToSingular)(type);
|
|
1184
1240
|
const protocol = await this.resolveService("protocol");
|
|
1185
1241
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
1186
1242
|
try {
|
|
@@ -1189,10 +1245,13 @@ var HttpDispatcher = class {
|
|
|
1189
1245
|
} catch (e) {
|
|
1190
1246
|
}
|
|
1191
1247
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1248
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1249
|
+
if (metaSvc && typeof metaSvc.getItem === "function") {
|
|
1250
|
+
try {
|
|
1251
|
+
const data = await metaSvc.getItem(singularType, name);
|
|
1252
|
+
if (data) return { handled: true, response: this.success(data) };
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1196
1255
|
}
|
|
1197
1256
|
return { handled: true, response: this.error("Not found", 404) };
|
|
1198
1257
|
} catch (e) {
|
|
@@ -1212,23 +1271,19 @@ var HttpDispatcher = class {
|
|
|
1212
1271
|
} catch {
|
|
1213
1272
|
}
|
|
1214
1273
|
}
|
|
1215
|
-
|
|
1274
|
+
const metadataService = await this.getService(import_system.CoreServiceName.enum.metadata);
|
|
1275
|
+
if (metadataService && typeof metadataService.list === "function") {
|
|
1216
1276
|
try {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1277
|
+
let items = await metadataService.list(typeOrName);
|
|
1278
|
+
if (packageId && items && items.length > 0) {
|
|
1279
|
+
items = items.filter((item) => item?._packageId === packageId);
|
|
1220
1280
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
return { handled: true, response: this.success(data) };
|
|
1281
|
+
if (items && items.length > 0) {
|
|
1282
|
+
return { handled: true, response: this.success({ type: typeOrName, items }) };
|
|
1224
1283
|
}
|
|
1225
|
-
} catch {
|
|
1226
|
-
}
|
|
1227
|
-
try {
|
|
1228
|
-
const data = await broker.call("metadata.getObject", { objectName: typeOrName }, { request: context.request });
|
|
1229
|
-
return { handled: true, response: this.success(data) };
|
|
1230
1284
|
} catch (e) {
|
|
1231
|
-
|
|
1285
|
+
const sanitizedType = String(typeOrName).replace(/[\r\n\t]/g, "");
|
|
1286
|
+
console.debug(`[HttpDispatcher] MetadataService.list() failed for type:`, sanitizedType, "error:", e.message);
|
|
1232
1287
|
}
|
|
1233
1288
|
}
|
|
1234
1289
|
const qlService = await this.getObjectQLService();
|
|
@@ -1247,18 +1302,19 @@ var HttpDispatcher = class {
|
|
|
1247
1302
|
return { handled: true, response: this.error("Not found", 404) };
|
|
1248
1303
|
}
|
|
1249
1304
|
if (parts.length === 0) {
|
|
1305
|
+
const metadataService = await this.resolveService("metadata");
|
|
1306
|
+
if (metadataService && typeof metadataService.getRegisteredTypes === "function") {
|
|
1307
|
+
try {
|
|
1308
|
+
const types = await metadataService.getRegisteredTypes();
|
|
1309
|
+
return { handled: true, response: this.success({ types }) };
|
|
1310
|
+
} catch {
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1250
1313
|
const protocol = await this.resolveService("protocol");
|
|
1251
1314
|
if (protocol && typeof protocol.getMetaTypes === "function") {
|
|
1252
1315
|
const result = await protocol.getMetaTypes({});
|
|
1253
1316
|
return { handled: true, response: this.success(result) };
|
|
1254
1317
|
}
|
|
1255
|
-
if (broker) {
|
|
1256
|
-
try {
|
|
1257
|
-
const data = await broker.call("metadata.types", {}, { request: context.request });
|
|
1258
|
-
return { handled: true, response: this.success(data) };
|
|
1259
|
-
} catch {
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
1318
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
1263
1319
|
}
|
|
1264
1320
|
return { handled: false };
|
|
@@ -1267,8 +1323,7 @@ var HttpDispatcher = class {
|
|
|
1267
1323
|
* Handles Data requests
|
|
1268
1324
|
* path: sub-path after /data/ (e.g. "contacts", "contacts/123", "contacts/query")
|
|
1269
1325
|
*/
|
|
1270
|
-
async handleData(path, method, body, query,
|
|
1271
|
-
const broker = this.ensureBroker();
|
|
1326
|
+
async handleData(path, method, body, query, _context) {
|
|
1272
1327
|
const parts = path.replace(/^\/+/, "").split("/");
|
|
1273
1328
|
const objectName = parts[0];
|
|
1274
1329
|
if (!objectName) {
|
|
@@ -1278,11 +1333,11 @@ var HttpDispatcher = class {
|
|
|
1278
1333
|
if (parts.length > 1) {
|
|
1279
1334
|
const action = parts[1];
|
|
1280
1335
|
if (action === "query" && m === "POST") {
|
|
1281
|
-
const result = await
|
|
1336
|
+
const result = await this.callData("query", { object: objectName, ...body });
|
|
1282
1337
|
return { handled: true, response: this.success(result) };
|
|
1283
1338
|
}
|
|
1284
1339
|
if (action === "batch" && m === "POST") {
|
|
1285
|
-
const result = await
|
|
1340
|
+
const result = await this.callData("batch", { object: objectName, ...body });
|
|
1286
1341
|
return { handled: true, response: this.success(result) };
|
|
1287
1342
|
}
|
|
1288
1343
|
if (parts.length === 2 && m === "GET") {
|
|
@@ -1291,17 +1346,17 @@ var HttpDispatcher = class {
|
|
|
1291
1346
|
const allowedParams = {};
|
|
1292
1347
|
if (select != null) allowedParams.select = select;
|
|
1293
1348
|
if (expand != null) allowedParams.expand = expand;
|
|
1294
|
-
const result = await
|
|
1349
|
+
const result = await this.callData("get", { object: objectName, id, ...allowedParams });
|
|
1295
1350
|
return { handled: true, response: this.success(result) };
|
|
1296
1351
|
}
|
|
1297
1352
|
if (parts.length === 2 && m === "PATCH") {
|
|
1298
1353
|
const id = parts[1];
|
|
1299
|
-
const result = await
|
|
1354
|
+
const result = await this.callData("update", { object: objectName, id, data: body });
|
|
1300
1355
|
return { handled: true, response: this.success(result) };
|
|
1301
1356
|
}
|
|
1302
1357
|
if (parts.length === 2 && m === "DELETE") {
|
|
1303
1358
|
const id = parts[1];
|
|
1304
|
-
const result = await
|
|
1359
|
+
const result = await this.callData("delete", { object: objectName, id });
|
|
1305
1360
|
return { handled: true, response: this.success(result) };
|
|
1306
1361
|
}
|
|
1307
1362
|
} else {
|
|
@@ -1328,11 +1383,11 @@ var HttpDispatcher = class {
|
|
|
1328
1383
|
normalized.offset = normalized.skip;
|
|
1329
1384
|
delete normalized.skip;
|
|
1330
1385
|
}
|
|
1331
|
-
const result = await
|
|
1386
|
+
const result = await this.callData("query", { object: objectName, query: normalized });
|
|
1332
1387
|
return { handled: true, response: this.success(result) };
|
|
1333
1388
|
}
|
|
1334
1389
|
if (m === "POST") {
|
|
1335
|
-
const result = await
|
|
1390
|
+
const result = await this.callData("create", { object: objectName, data: body });
|
|
1336
1391
|
const res = this.success(result);
|
|
1337
1392
|
res.status = 201;
|
|
1338
1393
|
return { handled: true, response: res };
|
|
@@ -1434,18 +1489,14 @@ var HttpDispatcher = class {
|
|
|
1434
1489
|
* - POST /packages/:id/publish → publish a package (metadata snapshot)
|
|
1435
1490
|
* - POST /packages/:id/revert → revert a package to last published state
|
|
1436
1491
|
*
|
|
1437
|
-
* Uses ObjectQL SchemaRegistry directly (via the 'objectql' service)
|
|
1438
|
-
* with broker fallback for backward compatibility.
|
|
1492
|
+
* Uses ObjectQL SchemaRegistry directly (via the 'objectql' service).
|
|
1439
1493
|
*/
|
|
1440
|
-
async handlePackages(path, method, body, query,
|
|
1494
|
+
async handlePackages(path, method, body, query, _context) {
|
|
1441
1495
|
const m = method.toUpperCase();
|
|
1442
1496
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
1443
1497
|
const qlService = await this.getObjectQLService();
|
|
1444
1498
|
const registry = qlService?.registry;
|
|
1445
1499
|
if (!registry) {
|
|
1446
|
-
if (this.kernel.broker) {
|
|
1447
|
-
return this.handlePackagesViaBroker(parts, m, body, query, context);
|
|
1448
|
-
}
|
|
1449
1500
|
return { handled: true, response: this.error("Package service not available", 503) };
|
|
1450
1501
|
}
|
|
1451
1502
|
try {
|
|
@@ -1484,10 +1535,6 @@ var HttpDispatcher = class {
|
|
|
1484
1535
|
const result = await metadataService.publishPackage(id, body || {});
|
|
1485
1536
|
return { handled: true, response: this.success(result) };
|
|
1486
1537
|
}
|
|
1487
|
-
if (this.kernel.broker) {
|
|
1488
|
-
const result = await this.kernel.broker.call("metadata.publishPackage", { packageId: id, ...body }, { request: context.request });
|
|
1489
|
-
return { handled: true, response: this.success(result) };
|
|
1490
|
-
}
|
|
1491
1538
|
return { handled: true, response: this.error("Metadata service not available", 503) };
|
|
1492
1539
|
}
|
|
1493
1540
|
if (parts.length === 2 && parts[1] === "revert" && m === "POST") {
|
|
@@ -1497,10 +1544,6 @@ var HttpDispatcher = class {
|
|
|
1497
1544
|
await metadataService.revertPackage(id);
|
|
1498
1545
|
return { handled: true, response: this.success({ success: true }) };
|
|
1499
1546
|
}
|
|
1500
|
-
if (this.kernel.broker) {
|
|
1501
|
-
await this.kernel.broker.call("metadata.revertPackage", { packageId: id }, { request: context.request });
|
|
1502
|
-
return { handled: true, response: this.success({ success: true }) };
|
|
1503
|
-
}
|
|
1504
1547
|
return { handled: true, response: this.error("Metadata service not available", 503) };
|
|
1505
1548
|
}
|
|
1506
1549
|
if (parts.length === 1 && m === "GET") {
|
|
@@ -1520,47 +1563,6 @@ var HttpDispatcher = class {
|
|
|
1520
1563
|
}
|
|
1521
1564
|
return { handled: false };
|
|
1522
1565
|
}
|
|
1523
|
-
/**
|
|
1524
|
-
* Fallback: handle packages via broker (for backward compatibility)
|
|
1525
|
-
*/
|
|
1526
|
-
async handlePackagesViaBroker(parts, m, body, query, context) {
|
|
1527
|
-
const broker = this.kernel.broker;
|
|
1528
|
-
try {
|
|
1529
|
-
if (parts.length === 0 && m === "GET") {
|
|
1530
|
-
const result = await broker.call("package.list", query || {}, { request: context.request });
|
|
1531
|
-
return { handled: true, response: this.success(result) };
|
|
1532
|
-
}
|
|
1533
|
-
if (parts.length === 0 && m === "POST") {
|
|
1534
|
-
const result = await broker.call("package.install", body, { request: context.request });
|
|
1535
|
-
const res = this.success(result);
|
|
1536
|
-
res.status = 201;
|
|
1537
|
-
return { handled: true, response: res };
|
|
1538
|
-
}
|
|
1539
|
-
if (parts.length === 2 && parts[1] === "enable" && m === "PATCH") {
|
|
1540
|
-
const id = decodeURIComponent(parts[0]);
|
|
1541
|
-
const result = await broker.call("package.enable", { id }, { request: context.request });
|
|
1542
|
-
return { handled: true, response: this.success(result) };
|
|
1543
|
-
}
|
|
1544
|
-
if (parts.length === 2 && parts[1] === "disable" && m === "PATCH") {
|
|
1545
|
-
const id = decodeURIComponent(parts[0]);
|
|
1546
|
-
const result = await broker.call("package.disable", { id }, { request: context.request });
|
|
1547
|
-
return { handled: true, response: this.success(result) };
|
|
1548
|
-
}
|
|
1549
|
-
if (parts.length === 1 && m === "GET") {
|
|
1550
|
-
const id = decodeURIComponent(parts[0]);
|
|
1551
|
-
const result = await broker.call("package.get", { id }, { request: context.request });
|
|
1552
|
-
return { handled: true, response: this.success(result) };
|
|
1553
|
-
}
|
|
1554
|
-
if (parts.length === 1 && m === "DELETE") {
|
|
1555
|
-
const id = decodeURIComponent(parts[0]);
|
|
1556
|
-
const result = await broker.call("package.uninstall", { id }, { request: context.request });
|
|
1557
|
-
return { handled: true, response: this.success(result) };
|
|
1558
|
-
}
|
|
1559
|
-
} catch (e) {
|
|
1560
|
-
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
1561
|
-
}
|
|
1562
|
-
return { handled: false };
|
|
1563
|
-
}
|
|
1564
1566
|
/**
|
|
1565
1567
|
* Handles Storage requests
|
|
1566
1568
|
* path: sub-path after /storage/
|
|
@@ -1769,9 +1771,6 @@ var HttpDispatcher = class {
|
|
|
1769
1771
|
}
|
|
1770
1772
|
return null;
|
|
1771
1773
|
}
|
|
1772
|
-
capitalize(s) {
|
|
1773
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1774
|
-
}
|
|
1775
1774
|
/**
|
|
1776
1775
|
* Handle AI service routes (/ai/chat, /ai/models, /ai/conversations, etc.)
|
|
1777
1776
|
* Resolves the AI service and its built-in route handlers, then dispatches.
|
|
@@ -1908,10 +1907,12 @@ var HttpDispatcher = class {
|
|
|
1908
1907
|
return this.handleAI(cleanPath, method, body, query, context);
|
|
1909
1908
|
}
|
|
1910
1909
|
if (cleanPath === "/openapi.json" && method === "GET") {
|
|
1911
|
-
const broker = this.ensureBroker();
|
|
1912
1910
|
try {
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1911
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1912
|
+
if (metaSvc && typeof metaSvc.generateOpenApi === "function") {
|
|
1913
|
+
const result2 = await metaSvc.generateOpenApi({});
|
|
1914
|
+
return { handled: true, response: this.success(result2) };
|
|
1915
|
+
}
|
|
1915
1916
|
} catch (e) {
|
|
1916
1917
|
}
|
|
1917
1918
|
}
|
|
@@ -1926,37 +1927,48 @@ var HttpDispatcher = class {
|
|
|
1926
1927
|
* Handles Custom API Endpoints defined in metadata
|
|
1927
1928
|
*/
|
|
1928
1929
|
async handleApiEndpoint(path, method, body, query, context) {
|
|
1929
|
-
const broker = this.ensureBroker();
|
|
1930
1930
|
try {
|
|
1931
|
-
const
|
|
1931
|
+
const metaSvc = await this.resolveService("metadata");
|
|
1932
|
+
if (!metaSvc || typeof metaSvc.matchEndpoint !== "function") {
|
|
1933
|
+
return { handled: false };
|
|
1934
|
+
}
|
|
1935
|
+
const endpoint = await metaSvc.matchEndpoint({ path, method });
|
|
1932
1936
|
if (endpoint) {
|
|
1933
1937
|
if (endpoint.type === "flow") {
|
|
1934
|
-
const
|
|
1938
|
+
const automationSvc = await this.resolveService("automation");
|
|
1939
|
+
if (!automationSvc || typeof automationSvc.runFlow !== "function") {
|
|
1940
|
+
return { handled: true, response: this.error("Automation service not available", 503) };
|
|
1941
|
+
}
|
|
1942
|
+
const result = await automationSvc.runFlow({
|
|
1935
1943
|
flowId: endpoint.target,
|
|
1936
1944
|
inputs: { ...query, ...body, _request: context.request }
|
|
1937
1945
|
});
|
|
1938
1946
|
return { handled: true, response: this.success(result) };
|
|
1939
1947
|
}
|
|
1940
1948
|
if (endpoint.type === "script") {
|
|
1941
|
-
const
|
|
1949
|
+
const automationSvc = await this.resolveService("automation");
|
|
1950
|
+
if (!automationSvc || typeof automationSvc.runScript !== "function") {
|
|
1951
|
+
return { handled: true, response: this.error("Automation service not available", 503) };
|
|
1952
|
+
}
|
|
1953
|
+
const result = await automationSvc.runScript({
|
|
1942
1954
|
scriptName: endpoint.target,
|
|
1943
1955
|
context: { ...query, ...body, request: context.request }
|
|
1944
|
-
}
|
|
1956
|
+
});
|
|
1945
1957
|
return { handled: true, response: this.success(result) };
|
|
1946
1958
|
}
|
|
1947
1959
|
if (endpoint.type === "object_operation") {
|
|
1948
1960
|
if (endpoint.objectParams) {
|
|
1949
1961
|
const { object, operation } = endpoint.objectParams;
|
|
1950
1962
|
if (operation === "find") {
|
|
1951
|
-
const result = await
|
|
1963
|
+
const result = await this.callData("query", { object, query });
|
|
1952
1964
|
return { handled: true, response: this.success(result.records, { total: result.total }) };
|
|
1953
1965
|
}
|
|
1954
1966
|
if (operation === "get" && query.id) {
|
|
1955
|
-
const result = await
|
|
1967
|
+
const result = await this.callData("get", { object, id: query.id });
|
|
1956
1968
|
return { handled: true, response: this.success(result) };
|
|
1957
1969
|
}
|
|
1958
1970
|
if (operation === "create") {
|
|
1959
|
-
const result = await
|
|
1971
|
+
const result = await this.callData("create", { object, data: body });
|
|
1960
1972
|
return { handled: true, response: this.success(result) };
|
|
1961
1973
|
}
|
|
1962
1974
|
}
|
|
@@ -1978,6 +1990,64 @@ var HttpDispatcher = class {
|
|
|
1978
1990
|
};
|
|
1979
1991
|
|
|
1980
1992
|
// src/dispatcher-plugin.ts
|
|
1993
|
+
function mountRouteOnServer(route, server, routePath) {
|
|
1994
|
+
const handler = async (req, res) => {
|
|
1995
|
+
try {
|
|
1996
|
+
const result = await route.handler({
|
|
1997
|
+
body: req.body,
|
|
1998
|
+
params: req.params,
|
|
1999
|
+
query: req.query
|
|
2000
|
+
});
|
|
2001
|
+
if (result.stream && result.events) {
|
|
2002
|
+
res.status(result.status);
|
|
2003
|
+
if (result.headers) {
|
|
2004
|
+
for (const [k, v] of Object.entries(result.headers)) {
|
|
2005
|
+
res.header(k, String(v));
|
|
2006
|
+
}
|
|
2007
|
+
} else {
|
|
2008
|
+
res.header("Content-Type", "text/event-stream");
|
|
2009
|
+
res.header("Cache-Control", "no-cache");
|
|
2010
|
+
res.header("Connection", "keep-alive");
|
|
2011
|
+
}
|
|
2012
|
+
if (typeof res.write === "function" && typeof res.end === "function") {
|
|
2013
|
+
for await (const event of result.events) {
|
|
2014
|
+
res.write(typeof event === "string" ? event : `data: ${JSON.stringify(event)}
|
|
2015
|
+
|
|
2016
|
+
`);
|
|
2017
|
+
}
|
|
2018
|
+
res.end();
|
|
2019
|
+
} else {
|
|
2020
|
+
const events = [];
|
|
2021
|
+
for await (const event of result.events) {
|
|
2022
|
+
events.push(event);
|
|
2023
|
+
}
|
|
2024
|
+
res.json({ events });
|
|
2025
|
+
}
|
|
2026
|
+
} else {
|
|
2027
|
+
res.status(result.status);
|
|
2028
|
+
if (result.body !== void 0) {
|
|
2029
|
+
res.json(result.body);
|
|
2030
|
+
} else {
|
|
2031
|
+
res.end();
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
} catch (err) {
|
|
2035
|
+
errorResponse(err, res);
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
const m = route.method.toLowerCase();
|
|
2039
|
+
if (m === "get" && typeof server.get === "function") {
|
|
2040
|
+
server.get(routePath, handler);
|
|
2041
|
+
return true;
|
|
2042
|
+
} else if (m === "post" && typeof server.post === "function") {
|
|
2043
|
+
server.post(routePath, handler);
|
|
2044
|
+
return true;
|
|
2045
|
+
} else if (m === "delete" && typeof server.delete === "function") {
|
|
2046
|
+
server.delete(routePath, handler);
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2049
|
+
return false;
|
|
2050
|
+
}
|
|
1981
2051
|
function sendResult(result, res) {
|
|
1982
2052
|
if (result.handled) {
|
|
1983
2053
|
if (result.response) {
|
|
@@ -2272,61 +2342,23 @@ function createDispatcherPlugin(config = {}) {
|
|
|
2272
2342
|
if (!server) return;
|
|
2273
2343
|
for (const route of routes) {
|
|
2274
2344
|
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
2275
|
-
|
|
2276
|
-
try {
|
|
2277
|
-
const result = await route.handler({
|
|
2278
|
-
body: req.body,
|
|
2279
|
-
params: req.params,
|
|
2280
|
-
query: req.query
|
|
2281
|
-
});
|
|
2282
|
-
if (result.stream && result.events) {
|
|
2283
|
-
res.status(result.status);
|
|
2284
|
-
if (result.headers) {
|
|
2285
|
-
for (const [k, v] of Object.entries(result.headers)) {
|
|
2286
|
-
res.header(k, v);
|
|
2287
|
-
}
|
|
2288
|
-
} else {
|
|
2289
|
-
res.header("Content-Type", "text/event-stream");
|
|
2290
|
-
res.header("Cache-Control", "no-cache");
|
|
2291
|
-
res.header("Connection", "keep-alive");
|
|
2292
|
-
}
|
|
2293
|
-
if (typeof res.write === "function" && typeof res.end === "function") {
|
|
2294
|
-
for await (const event of result.events) {
|
|
2295
|
-
res.write(typeof event === "string" ? event : `data: ${JSON.stringify(event)}
|
|
2296
|
-
|
|
2297
|
-
`);
|
|
2298
|
-
}
|
|
2299
|
-
res.end();
|
|
2300
|
-
} else {
|
|
2301
|
-
const events = [];
|
|
2302
|
-
for await (const event of result.events) {
|
|
2303
|
-
events.push(event);
|
|
2304
|
-
}
|
|
2305
|
-
res.json({ events });
|
|
2306
|
-
}
|
|
2307
|
-
} else {
|
|
2308
|
-
res.status(result.status);
|
|
2309
|
-
if (result.body !== void 0) {
|
|
2310
|
-
res.json(result.body);
|
|
2311
|
-
} else {
|
|
2312
|
-
res.end();
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
} catch (err) {
|
|
2316
|
-
errorResponse(err, res);
|
|
2317
|
-
}
|
|
2318
|
-
};
|
|
2319
|
-
const m = route.method.toLowerCase();
|
|
2320
|
-
if (m === "get" && typeof server.get === "function") {
|
|
2321
|
-
server.get(routePath, handler);
|
|
2322
|
-
} else if (m === "post" && typeof server.post === "function") {
|
|
2323
|
-
server.post(routePath, handler);
|
|
2324
|
-
} else if (m === "delete" && typeof server.delete === "function") {
|
|
2325
|
-
server.delete(routePath, handler);
|
|
2326
|
-
}
|
|
2345
|
+
mountRouteOnServer(route, server, routePath);
|
|
2327
2346
|
}
|
|
2328
2347
|
ctx.logger.info(`[Dispatcher] Registered ${routes.length} AI routes`);
|
|
2329
2348
|
});
|
|
2349
|
+
const cachedRoutes = kernel.__aiRoutes;
|
|
2350
|
+
if (cachedRoutes && Array.isArray(cachedRoutes) && cachedRoutes.length > 0) {
|
|
2351
|
+
let registered = 0;
|
|
2352
|
+
for (const route of cachedRoutes) {
|
|
2353
|
+
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
2354
|
+
if (mountRouteOnServer(route, server, routePath)) {
|
|
2355
|
+
registered++;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
if (registered > 0) {
|
|
2359
|
+
ctx.logger.info(`[Dispatcher] Recovered ${registered} cached AI routes (hook timing fallback)`);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2330
2362
|
}
|
|
2331
2363
|
};
|
|
2332
2364
|
}
|