Skip to content

Commit 878d0ef

Browse files
authored
Merge pull request #929 from objectstack-ai/copilot/update-vercel-deployment-mode
2 parents b2ef0af + 85f3180 commit 878d0ef

9 files changed

Lines changed: 511 additions & 337 deletions

File tree

apps/studio/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# @objectstack/studio
22

3+
## 3.2.8
4+
5+
### Minor Changes
6+
7+
- Switch Vercel deployment from MSW (browser mock) to real server mode
8+
- Add `api/[...path].ts` Vercel serverless catch-all using Hono + `@objectstack/hono`
9+
- Add `api/_kernel.ts` server-side kernel singleton with broker shim
10+
- Extract broker shim to `src/lib/create-broker-shim.ts` (shared by MSW and server modes)
11+
- Update `vercel.json` to set `VITE_RUNTIME_MODE=server` and `VITE_SERVER_URL=""`
12+
- Add `hono` and `@objectstack/hono` dependencies
13+
- Update deployment documentation
14+
315
## 3.2.7
416

517
### Patch Changes

apps/studio/api/[...path].ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* Vercel Serverless Catch-All Route
5+
*
6+
* Handles all /api/* requests using the ObjectStack Hono application.
7+
* The Hono app provides discovery, data CRUD, metadata, auth, and more
8+
* endpoints under the /api/v1 prefix.
9+
*
10+
* File name convention: Vercel maps `[...path].ts` to a catch-all route
11+
* that matches /api and any sub-paths (/api/v1/data/task, etc.).
12+
*/
13+
14+
import { Hono } from 'hono';
15+
import { handle } from 'hono/vercel';
16+
import { getApp } from './_kernel';
17+
18+
/**
19+
* Outer Hono app — synchronously created so it can be wrapped by handle().
20+
* Delegates all requests to the lazy-initialized inner app (which boots
21+
* the ObjectStack kernel on first invocation).
22+
*/
23+
const app = new Hono();
24+
25+
app.all('/*', async (c) => {
26+
const inner = await getApp();
27+
return inner.fetch(c.req.raw);
28+
});
29+
30+
export default handle(app);

apps/studio/api/_kernel.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* Server-side Kernel Singleton for Vercel Serverless Functions
5+
*
6+
* Boots an ObjectKernel with ObjectQL + InMemoryDriver, seeds data from
7+
* the Studio configuration, and exposes a Hono app via createHonoApp.
8+
*
9+
* The kernel instance survives across warm invocations of the same
10+
* serverless function container, so boot cost is paid only on cold start.
11+
*
12+
* @module
13+
*/
14+
15+
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
16+
import { ObjectQLPlugin } from '@objectstack/objectql';
17+
import { InMemoryDriver } from '@objectstack/driver-memory';
18+
import { createHonoApp } from '@objectstack/hono';
19+
import { Hono } from 'hono';
20+
import { createBrokerShim } from '../src/lib/create-broker-shim';
21+
import studioConfig from '../objectstack.config';
22+
23+
// --- Singleton state (persists across warm invocations) ---
24+
let _kernel: ObjectKernel | null = null;
25+
let _app: Hono | null = null;
26+
27+
/**
28+
* Boot the ObjectStack kernel (one-time cold-start cost).
29+
*/
30+
async function bootKernel(): Promise<ObjectKernel> {
31+
if (_kernel) return _kernel;
32+
33+
console.log('[Vercel] Booting ObjectStack Kernel (server mode)...');
34+
35+
const kernel = new ObjectKernel();
36+
37+
await kernel.use(new ObjectQLPlugin());
38+
await kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory'));
39+
await kernel.use(new AppPlugin(studioConfig));
40+
41+
// Broker shim — bridges HttpDispatcher → ObjectQL engine
42+
(kernel as any).broker = createBrokerShim(kernel);
43+
44+
await kernel.bootstrap();
45+
46+
// Seed data from config
47+
await seedData(kernel, [studioConfig]);
48+
49+
_kernel = kernel;
50+
console.log('[Vercel] Kernel ready.');
51+
return kernel;
52+
}
53+
54+
/**
55+
* Seed records defined in app configs into the ObjectQL engine.
56+
*/
57+
async function seedData(kernel: ObjectKernel, configs: any[]) {
58+
const ql = (kernel as any).context?.getService('objectql');
59+
if (!ql) return;
60+
61+
// Reserved namespaces ('base', 'system') bypass FQN transformation —
62+
// objects in these namespaces keep their short name as-is.
63+
const RESERVED_NS = new Set(['base', 'system']);
64+
const toFQN = (name: string, namespace?: string) => {
65+
if (name.includes('__') || !namespace || RESERVED_NS.has(namespace)) return name;
66+
return `${namespace}__${name}`;
67+
};
68+
69+
for (const appConfig of configs) {
70+
const namespace = (appConfig.manifest || appConfig)?.namespace as string | undefined;
71+
72+
const seedDatasets: any[] = [];
73+
if (Array.isArray(appConfig.data)) {
74+
seedDatasets.push(...appConfig.data);
75+
}
76+
if (appConfig.manifest && Array.isArray(appConfig.manifest.data)) {
77+
seedDatasets.push(...appConfig.manifest.data);
78+
}
79+
80+
for (const dataset of seedDatasets) {
81+
if (!dataset.records || !dataset.object) continue;
82+
83+
const objectFQN = toFQN(dataset.object, namespace);
84+
85+
// Handle PaginatedResult wrapper — InMemoryDriver may return { value: [...] }
86+
let existing = await ql.find(objectFQN);
87+
if (existing && (existing as any).value) existing = (existing as any).value;
88+
89+
if (!existing || existing.length === 0) {
90+
console.log(`[Vercel] Seeding ${dataset.records.length} records for ${objectFQN}`);
91+
for (const record of dataset.records) {
92+
await ql.insert(objectFQN, record);
93+
}
94+
}
95+
}
96+
}
97+
}
98+
99+
/**
100+
* Get (or create) the Hono application backed by the ObjectStack kernel.
101+
* The prefix `/api/v1` matches the client SDK's default API path.
102+
*/
103+
export async function getApp(): Promise<Hono> {
104+
if (_app) return _app;
105+
106+
const kernel = await bootKernel();
107+
_app = createHonoApp({ kernel, prefix: '/api/v1' });
108+
return _app;
109+
}

apps/studio/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@objectstack/client": "workspace:*",
2121
"@objectstack/client-react": "workspace:*",
2222
"@objectstack/driver-memory": "workspace:*",
23+
"@objectstack/hono": "workspace:*",
2324
"@objectstack/metadata": "workspace:*",
2425
"@objectstack/objectql": "workspace:*",
2526
"@objectstack/plugin-msw": "workspace:*",
@@ -56,6 +57,7 @@
5657
"@vitejs/plugin-react": "^6.0.1",
5758
"autoprefixer": "^10.4.27",
5859
"happy-dom": "^20.8.4",
60+
"hono": "^4.12.8",
5961
"msw": "^2.12.13",
6062
"postcss": "^8.5.8",
6163
"tailwindcss": "^4.2.1",

0 commit comments

Comments
 (0)