Skip to content

Commit c02579b

Browse files
Copilothotlong
andcommitted
feat(studio): switch Vercel deployment from MSW to real server mode with Hono serverless functions
- Add api/[...path].ts Vercel serverless catch-all using Hono - Add api/_kernel.ts server-side kernel singleton - Extract broker shim to src/lib/create-broker-shim.ts (DRY) - Update vercel.json to VITE_RUNTIME_MODE=server - Add hono and @objectstack/hono dependencies - Update deployment docs Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent b9061e7 commit c02579b

9 files changed

Lines changed: 508 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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
const RESERVED_NS = new Set(['base', 'system']);
62+
const toFQN = (name: string, namespace?: string) => {
63+
if (name.includes('__') || !namespace || RESERVED_NS.has(namespace)) return name;
64+
return `${namespace}__${name}`;
65+
};
66+
67+
for (const appConfig of configs) {
68+
const namespace = (appConfig.manifest || appConfig)?.namespace as string | undefined;
69+
70+
const seedDatasets: any[] = [];
71+
if (Array.isArray(appConfig.data)) {
72+
seedDatasets.push(...appConfig.data);
73+
}
74+
if (appConfig.manifest && Array.isArray(appConfig.manifest.data)) {
75+
seedDatasets.push(...appConfig.manifest.data);
76+
}
77+
78+
for (const dataset of seedDatasets) {
79+
if (!dataset.records || !dataset.object) continue;
80+
81+
const objectFQN = toFQN(dataset.object, namespace);
82+
83+
let existing = await ql.find(objectFQN);
84+
if (existing && (existing as any).value) existing = (existing as any).value;
85+
86+
if (!existing || existing.length === 0) {
87+
console.log(`[Vercel] Seeding ${dataset.records.length} records for ${objectFQN}`);
88+
for (const record of dataset.records) {
89+
await ql.insert(objectFQN, record);
90+
}
91+
}
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Get (or create) the Hono application backed by the ObjectStack kernel.
98+
* The prefix `/api/v1` matches the client SDK's default API path.
99+
*/
100+
export async function getApp(): Promise<Hono> {
101+
if (_app) return _app;
102+
103+
const kernel = await bootKernel();
104+
_app = createHonoApp({ kernel, prefix: '/api/v1' });
105+
return _app;
106+
}

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)