@objectstack/runtime 4.0.1 → 4.0.3

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