@objectstack/runtime 4.0.0 → 4.0.2

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.0 build /home/runner/work/spec/spec/packages/runtime
2
+ > @objectstack/runtime@4.0.2 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
- ESM dist/index.js 88.17 KB
14
- ESM dist/index.js.map 190.67 KB
15
- ESM ⚡️ Build success in 223ms
16
- CJS dist/index.cjs 90.76 KB
17
- CJS dist/index.cjs.map 190.74 KB
18
- CJS ⚡️ Build success in 232ms
13
+ ESM dist/index.js 94.32 KB
14
+ ESM dist/index.js.map 204.99 KB
15
+ ESM ⚡️ Build success in 155ms
16
+ CJS dist/index.cjs 96.91 KB
17
+ CJS dist/index.cjs.map 205.06 KB
18
+ CJS ⚡️ Build success in 156ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 5188ms
21
- DTS dist/index.d.ts 24.35 KB
22
- DTS dist/index.d.cts 24.35 KB
20
+ DTS ⚡️ Build success in 3326ms
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,15 @@
1
1
  # @objectstack/runtime
2
2
 
3
+ ## 4.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [5f659e9]
8
+ - @objectstack/spec@4.0.2
9
+ - @objectstack/core@4.0.2
10
+ - @objectstack/rest@4.0.2
11
+ - @objectstack/types@4.0.2
12
+
3
13
  ## 4.0.0
4
14
 
5
15
  ### 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;
@@ -856,6 +855,24 @@ var HttpDispatcher = class {
856
855
  body: { success: false, error: { message, code, details } }
857
856
  };
858
857
  }
858
+ /**
859
+ * 404 Route Not Found — no route is registered for this path.
860
+ */
861
+ routeNotFound(route) {
862
+ return {
863
+ status: 404,
864
+ body: {
865
+ success: false,
866
+ error: {
867
+ code: 404,
868
+ message: `Route Not Found: ${route}`,
869
+ type: "ROUTE_NOT_FOUND",
870
+ route,
871
+ hint: "No route is registered for this path. Check the API discovery endpoint for available routes."
872
+ }
873
+ }
874
+ };
875
+ }
859
876
  ensureBroker() {
860
877
  if (!this.kernel.broker) {
861
878
  throw { statusCode: 500, message: "Kernel Broker not available" };
@@ -937,12 +954,14 @@ var HttpDispatcher = class {
937
954
  const svcAvailable = (route, provider) => ({
938
955
  enabled: true,
939
956
  status: "available",
957
+ handlerReady: true,
940
958
  route,
941
959
  provider
942
960
  });
943
961
  const svcUnavailable = (name) => ({
944
962
  enabled: false,
945
963
  status: "unavailable",
964
+ handlerReady: false,
946
965
  message: `Install a ${name} plugin to enable`
947
966
  });
948
967
  let locale = { default: "en", supported: ["en"], timezone: "UTC" };
@@ -975,7 +994,7 @@ var HttpDispatcher = class {
975
994
  },
976
995
  services: {
977
996
  // 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" },
997
+ metadata: { enabled: true, status: "degraded", handlerReady: true, route: routes.metadata, provider: "kernel", message: "In-memory registry; DB persistence pending" },
979
998
  data: svcAvailable(routes.data, "kernel"),
980
999
  // Plugin-provided — only available when a plugin registers the service
981
1000
  auth: hasAuth ? svcAvailable(routes.auth) : svcUnavailable("auth"),
@@ -1127,6 +1146,7 @@ var HttpDispatcher = class {
1127
1146
  }
1128
1147
  if (parts.length === 2) {
1129
1148
  const [type, name] = parts;
1149
+ const packageId = query?.package || void 0;
1130
1150
  if (method === "PUT" && body) {
1131
1151
  const protocol = await this.resolveService("protocol");
1132
1152
  if (protocol && typeof protocol.saveMetaItem === "function") {
@@ -1164,7 +1184,7 @@ var HttpDispatcher = class {
1164
1184
  const protocol = await this.resolveService("protocol");
1165
1185
  if (protocol && typeof protocol.getMetaItem === "function") {
1166
1186
  try {
1167
- const data = await protocol.getMetaItem({ type: singularType, name });
1187
+ const data = await protocol.getMetaItem({ type: singularType, name, packageId });
1168
1188
  return { handled: true, response: this.success(data) };
1169
1189
  } catch (e) {
1170
1190
  }
@@ -1752,19 +1772,108 @@ var HttpDispatcher = class {
1752
1772
  capitalize(s) {
1753
1773
  return s.charAt(0).toUpperCase() + s.slice(1);
1754
1774
  }
1775
+ /**
1776
+ * Handle AI service routes (/ai/chat, /ai/models, /ai/conversations, etc.)
1777
+ * Resolves the AI service and its built-in route handlers, then dispatches.
1778
+ */
1779
+ async handleAI(subPath, method, body, query, _context) {
1780
+ let aiService;
1781
+ try {
1782
+ aiService = await this.resolveService("ai");
1783
+ } catch {
1784
+ }
1785
+ if (!aiService) {
1786
+ return {
1787
+ handled: true,
1788
+ response: {
1789
+ status: 404,
1790
+ body: { success: false, error: { message: "AI service is not configured", code: 404 } }
1791
+ }
1792
+ };
1793
+ }
1794
+ const fullPath = `/api/v1${subPath}`;
1795
+ const matchRoute = (pattern, path) => {
1796
+ const patternParts = pattern.split("/");
1797
+ const pathParts = path.split("/");
1798
+ if (patternParts.length !== pathParts.length) return null;
1799
+ const params = {};
1800
+ for (let i = 0; i < patternParts.length; i++) {
1801
+ if (patternParts[i].startsWith(":")) {
1802
+ params[patternParts[i].substring(1)] = pathParts[i];
1803
+ } else if (patternParts[i] !== pathParts[i]) {
1804
+ return null;
1805
+ }
1806
+ }
1807
+ return params;
1808
+ };
1809
+ const routes = this.kernel.__aiRoutes;
1810
+ if (!routes) {
1811
+ return {
1812
+ handled: true,
1813
+ response: {
1814
+ status: 503,
1815
+ body: { success: false, error: { message: "AI service routes not yet initialized", code: 503 } }
1816
+ }
1817
+ };
1818
+ }
1819
+ for (const route of routes) {
1820
+ if (route.method !== method) continue;
1821
+ const params = matchRoute(route.path, fullPath);
1822
+ if (params === null) continue;
1823
+ const result = await route.handler({ body, params, query });
1824
+ if (result.stream && result.events) {
1825
+ return {
1826
+ handled: true,
1827
+ result: {
1828
+ type: "stream",
1829
+ contentType: result.vercelDataStream ? "text/plain; charset=utf-8" : "text/event-stream",
1830
+ events: result.events,
1831
+ vercelDataStream: result.vercelDataStream,
1832
+ headers: {
1833
+ "Content-Type": result.vercelDataStream ? "text/plain; charset=utf-8" : "text/event-stream",
1834
+ "Cache-Control": "no-cache",
1835
+ "Connection": "keep-alive"
1836
+ }
1837
+ }
1838
+ };
1839
+ }
1840
+ return {
1841
+ handled: true,
1842
+ response: {
1843
+ status: result.status,
1844
+ body: result.body
1845
+ }
1846
+ };
1847
+ }
1848
+ return {
1849
+ handled: true,
1850
+ response: this.routeNotFound(subPath)
1851
+ };
1852
+ }
1755
1853
  /**
1756
1854
  * Main Dispatcher Entry Point
1757
1855
  * Routes the request to the appropriate handler based on path and precedence
1758
1856
  */
1759
- async dispatch(method, path, body, query, context) {
1857
+ async dispatch(method, path, body, query, context, prefix) {
1760
1858
  const cleanPath = path.replace(/\/$/, "");
1761
1859
  if ((cleanPath === "/discovery" || cleanPath === "") && method === "GET") {
1762
- const info = await this.getDiscoveryInfo("");
1860
+ const info = await this.getDiscoveryInfo(prefix ?? "");
1763
1861
  return {
1764
1862
  handled: true,
1765
1863
  response: this.success(info)
1766
1864
  };
1767
1865
  }
1866
+ if (cleanPath === "/health" && method === "GET") {
1867
+ return {
1868
+ handled: true,
1869
+ response: this.success({
1870
+ status: "ok",
1871
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1872
+ version: "1.0.0",
1873
+ uptime: typeof process !== "undefined" ? process.uptime() : void 0
1874
+ })
1875
+ };
1876
+ }
1768
1877
  if (cleanPath.startsWith("/auth")) {
1769
1878
  return this.handleAuth(cleanPath.substring(5), method, body, context);
1770
1879
  }
@@ -1795,6 +1904,9 @@ var HttpDispatcher = class {
1795
1904
  if (cleanPath.startsWith("/i18n")) {
1796
1905
  return this.handleI18n(cleanPath.substring(5), method, query, context);
1797
1906
  }
1907
+ if (cleanPath.startsWith("/ai")) {
1908
+ return this.handleAI(cleanPath, method, body, query, context);
1909
+ }
1798
1910
  if (cleanPath === "/openapi.json" && method === "GET") {
1799
1911
  const broker = this.ensureBroker();
1800
1912
  try {
@@ -1805,7 +1917,10 @@ var HttpDispatcher = class {
1805
1917
  }
1806
1918
  const result = await this.handleApiEndpoint(cleanPath, method, body, query, context);
1807
1919
  if (result.handled) return result;
1808
- return { handled: false };
1920
+ return {
1921
+ handled: true,
1922
+ response: this.routeNotFound(cleanPath)
1923
+ };
1809
1924
  }
1810
1925
  /**
1811
1926
  * Handles Custom API Endpoints defined in metadata
@@ -1880,7 +1995,15 @@ function sendResult(result, res) {
1880
1995
  return;
1881
1996
  }
1882
1997
  }
1883
- res.status(404).json({ success: false, error: { message: "Not Found", code: 404 } });
1998
+ res.status(404).json({
1999
+ success: false,
2000
+ error: {
2001
+ message: "Not Found",
2002
+ code: 404,
2003
+ type: "ROUTE_NOT_FOUND",
2004
+ hint: "No handler matched this request. Check the API discovery endpoint for available routes."
2005
+ }
2006
+ });
1884
2007
  }
1885
2008
  function errorResponse(err, res) {
1886
2009
  const code = err.statusCode || 500;
@@ -1912,6 +2035,14 @@ function createDispatcherPlugin(config = {}) {
1912
2035
  server.get(`${prefix}/discovery`, async (_req, res) => {
1913
2036
  res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
1914
2037
  });
2038
+ server.get(`${prefix}/health`, async (_req, res) => {
2039
+ try {
2040
+ const result = await dispatcher.dispatch("GET", "/health", void 0, {}, { request: _req });
2041
+ sendResult(result, res);
2042
+ } catch (err) {
2043
+ errorResponse(err, res);
2044
+ }
2045
+ });
1915
2046
  server.post(`${prefix}/auth/login`, async (req, res) => {
1916
2047
  try {
1917
2048
  const result = await dispatcher.handleAuth("login", "POST", req.body, { request: req });
@@ -2137,6 +2268,65 @@ function createDispatcherPlugin(config = {}) {
2137
2268
  }
2138
2269
  });
2139
2270
  ctx.logger.info("Dispatcher bridge routes registered", { prefix });
2271
+ ctx.hook("ai:routes", async (routes) => {
2272
+ if (!server) return;
2273
+ for (const route of routes) {
2274
+ const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
2275
+ const handler = async (req, res) => {
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
+ }
2327
+ }
2328
+ ctx.logger.info(`[Dispatcher] Registered ${routes.length} AI routes`);
2329
+ });
2140
2330
  }
2141
2331
  };
2142
2332
  }