@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/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 broker, services, graphql
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
- ensureBroker() {
830
- if (!this.kernel.broker) {
831
- throw { statusCode: 500, message: "Kernel Broker not available" };
832
- }
833
- return this.kernel.broker;
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, context, method, body, query) {
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
- if (broker) {
1149
+ const metaSvc = await this.resolveService("metadata");
1150
+ if (metaSvc && typeof metaSvc.getPublished === "function") {
1091
1151
  try {
1092
- const data = await broker.call("metadata.getPublished", { type, name }, { request: context.request });
1093
- return { handled: true, response: this.success(data) };
1094
- } catch (e) {
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
- if (broker) {
1172
+ const metaSvc = await this.resolveService("metadata");
1173
+ if (metaSvc && typeof metaSvc.saveItem === "function") {
1114
1174
  try {
1115
- const data = await broker.call("metadata.saveItem", { type, name, item: body }, { request: context.request });
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 = type.endsWith("s") ? type.slice(0, -1) : type;
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
- if (broker) {
1146
- const method2 = `metadata.get${this.capitalize(singularType)}`;
1147
- const data = await broker.call(method2, { name }, { request: context.request });
1148
- return { handled: true, response: this.success(data) };
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
- if (broker) {
1227
+ const metadataService = await this.getService(CoreServiceName.enum.metadata);
1228
+ if (metadataService && typeof metadataService.list === "function") {
1169
1229
  try {
1170
- if (typeOrName === "objects") {
1171
- const data2 = await broker.call("metadata.objects", { packageId }, { request: context.request });
1172
- return { handled: true, response: this.success(data2) };
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
- const data = await broker.call(`metadata.${typeOrName}`, { packageId }, { request: context.request });
1175
- if (data !== null && data !== void 0) {
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
- return { handled: true, response: this.error(e.message, 404) };
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, context) {
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 broker.call("data.query", { object: objectName, ...body }, { request: context.request });
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 broker.call("data.batch", { object: objectName, ...body }, { request: context.request });
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 broker.call("data.get", { object: objectName, id, ...allowedParams }, { request: context.request });
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 broker.call("data.update", { object: objectName, id, data: body }, { request: context.request });
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 broker.call("data.delete", { object: objectName, id }, { request: context.request });
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 broker.call("data.query", { object: objectName, query: normalized }, { request: context.request });
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 broker.call("data.create", { object: objectName, data: body }, { request: context.request });
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, context) {
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 result2 = await broker.call("metadata.generateOpenApi", {}, { request: context.request });
1867
- return { handled: true, response: this.success(result2) };
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 endpoint = await broker.call("metadata.matchEndpoint", { path, method });
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 result = await broker.call("automation.runFlow", {
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 result = await broker.call("automation.runScript", {
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
- }, { request: context.request });
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 broker.call("data.query", { object, query }, { request: context.request });
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 broker.call("data.get", { object, id: query.id }, { request: context.request });
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 broker.call("data.create", { object, data: body }, { request: context.request });
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
- const handler = async (req, res) => {
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
  }