From b3b1886c7e03268df266e7a2cdb0766e753f845a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Feb 2026 05:04:09 +0000
Subject: [PATCH 1/3] Initial plan
From 1e1cabf79b3562e730a77718c82145704c3ac300 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Feb 2026 05:08:14 +0000
Subject: [PATCH 2/3] Add Vercel deployment guide covering MSW and Server modes
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
content/docs/guides/deployment-vercel.mdx | 374 ++++++++++++++++++++++
content/docs/guides/index.mdx | 1 +
content/docs/guides/meta.json | 1 +
3 files changed, 376 insertions(+)
create mode 100644 content/docs/guides/deployment-vercel.mdx
diff --git a/content/docs/guides/deployment-vercel.mdx b/content/docs/guides/deployment-vercel.mdx
new file mode 100644
index 000000000..c4940bb3f
--- /dev/null
+++ b/content/docs/guides/deployment-vercel.mdx
@@ -0,0 +1,374 @@
+---
+title: Deploy to Vercel
+description: Deploy ObjectStack applications to Vercel — MSW mode for static demos and Server mode for full-stack production
+---
+
+# Deploy to Vercel
+
+ObjectStack supports two deployment modes on Vercel. Choose the mode that fits your use case:
+
+| Mode | Runtime | Vercel Feature | Use Case |
+| :--- | :--- | :--- | :--- |
+| **MSW** | Browser (Service Worker) | Static Site | Demos, prototypes, offline-capable apps |
+| **Server** | Node.js / Edge | Serverless Functions | Production apps with real database |
+
+---
+
+## MSW Mode (Static SPA)
+
+In MSW mode the entire ObjectStack kernel runs **in the browser**. Mock Service Worker intercepts `fetch` calls and routes them to an in-memory ObjectQL engine — no backend required.
+
+### How It Works
+
+```
+┌─────────────────────────── Browser ───────────────────────────┐
+│ React App → fetch('/api/v1/data/task') │
+│ ↓ │
+│ MSW Service Worker (intercepts request) │
+│ ↓ │
+│ ObjectKernel → ObjectQL → InMemoryDriver │
+│ ↓ │
+│ JSON Response → React App │
+└───────────────────────────────────────────────────────────────┘
+```
+
+### Project Setup
+
+```typescript
+// objectstack.config.ts
+import { defineStack } from '@objectstack/spec';
+import * as objects from './src/objects';
+
+export default defineStack({
+ manifest: {
+ id: 'com.example.myapp',
+ name: 'My App',
+ version: '1.0.0',
+ type: 'app',
+ },
+ objects: Object.values(objects),
+ data: [
+ {
+ object: 'task',
+ mode: 'upsert',
+ externalId: 'subject',
+ records: [
+ { subject: 'Learn ObjectStack', status: 'in_progress', priority: 'high' },
+ ],
+ },
+ ],
+});
+```
+
+### Kernel Bootstrap (Browser)
+
+Create a kernel factory that boots the entire stack in the browser:
+
+```typescript
+// src/mocks/createKernel.ts
+import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
+import { ObjectQLPlugin } from '@objectstack/objectql';
+import { InMemoryDriver } from '@objectstack/driver-memory';
+import { MSWPlugin } from '@objectstack/plugin-msw';
+
+export async function createKernel(appConfigs: any[]) {
+ const kernel = new ObjectKernel();
+
+ await kernel.use(new ObjectQLPlugin());
+ await kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory'));
+
+ for (const config of appConfigs) {
+ await kernel.use(new AppPlugin(config));
+ }
+
+ await kernel.use(new MSWPlugin({
+ enableBrowser: true,
+ baseUrl: '/api/v1',
+ logRequests: true,
+ }));
+
+ await kernel.bootstrap();
+ return kernel;
+}
+```
+
+### Entry Point
+
+```typescript
+// src/main.tsx
+import appConfig from '../objectstack.config';
+import { createKernel } from './mocks/createKernel';
+
+async function bootstrap() {
+ // Boot the in-browser kernel before rendering
+ await createKernel([appConfig]);
+
+ // Now render — all fetch('/api/v1/...') calls are intercepted by MSW
+ ReactDOM.createRoot(document.getElementById('root')!).render();
+}
+
+bootstrap();
+```
+
+### Vite Configuration
+
+MSW requires the Service Worker file in your `public/` directory. Add the init script and configure Vite:
+
+```bash
+# Generate the MSW Service Worker file
+npx msw init public --save
+```
+
+```typescript
+// vite.config.ts
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ optimizeDeps: {
+ include: ['msw', 'msw/browser', '@objectstack/spec'],
+ },
+});
+```
+
+### `vercel.json`
+
+```json
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "framework": "vite",
+ "buildCommand": "vite build",
+ "outputDirectory": "dist",
+ "build": {
+ "env": {
+ "VITE_RUNTIME_MODE": "msw"
+ }
+ },
+ "rewrites": [
+ { "source": "/(.*)", "destination": "/index.html" }
+ ]
+}
+```
+
+
+The `rewrites` rule is essential for SPA routing — it ensures all paths serve `index.html` so the client-side router can handle navigation.
+
+
+### Monorepo Configuration
+
+If your project lives in a monorepo (e.g. pnpm workspaces + Turborepo), update the install and build commands:
+
+```json
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "framework": "vite",
+ "installCommand": "cd ../.. && pnpm install",
+ "buildCommand": "cd ../.. && pnpm turbo run build --filter=@myorg/my-app",
+ "outputDirectory": "dist",
+ "build": {
+ "env": {
+ "VITE_RUNTIME_MODE": "msw"
+ }
+ },
+ "rewrites": [
+ { "source": "/(.*)", "destination": "/index.html" }
+ ]
+}
+```
+
+Set the **Root Directory** in Vercel project settings to the app's folder (e.g. `apps/my-app`).
+
+---
+
+## Server Mode (Serverless Functions)
+
+In Server mode, ObjectStack runs inside Vercel Serverless Functions. API requests are handled by real backend logic with a real database.
+
+### Architecture
+
+```
+┌─── Vercel ────────────────────────────────────────────────────┐
+│ │
+│ Static Assets (React SPA) │
+│ ┌───────────────────────────────────┐ │
+│ │ /index.html, /assets/* │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ Serverless Functions │
+│ ┌───────────────────────────────────┐ │
+│ │ /api/[...objectstack] │ │
+│ │ → ObjectKernel │ │
+│ │ → ObjectQL │ │
+│ │ → PostgreSQL / MongoDB / ... │──── External DB │
+│ └───────────────────────────────────┘ │
+│ │
+└───────────────────────────────────────────────────────────────┘
+```
+
+### Option A: Next.js + `@objectstack/nextjs`
+
+This is the recommended approach for Vercel. The `@objectstack/nextjs` adapter maps all ObjectStack protocol endpoints to a single Next.js catch-all route.
+
+**1. Create the kernel singleton:**
+
+```typescript
+// lib/kernel.ts
+import { ObjectKernel, DriverPlugin } from '@objectstack/runtime';
+import { ObjectQLPlugin } from '@objectstack/objectql';
+import appConfig from '../objectstack.config';
+
+let kernel: ObjectKernel | null = null;
+
+export async function getKernel() {
+ if (kernel) return kernel;
+
+ kernel = new ObjectKernel();
+ await kernel.use(new ObjectQLPlugin());
+
+ // Use your production driver (Postgres, MongoDB, etc.)
+ // await kernel.use(new DriverPlugin(new PostgresDriver({
+ // url: process.env.DATABASE_URL,
+ // })));
+
+ await kernel.bootstrap();
+ return kernel;
+}
+```
+
+**2. Create the API route handler:**
+
+```typescript
+// app/api/[...objectstack]/route.ts
+import { createRouteHandler } from '@objectstack/nextjs';
+import { getKernel } from '@/lib/kernel';
+
+async function handler(...args: any[]) {
+ const kernel = await getKernel();
+ const routeHandler = createRouteHandler({ kernel, prefix: '/api' });
+ return routeHandler(...args);
+}
+
+export { handler as GET, handler as POST, handler as PATCH, handler as DELETE };
+```
+
+**3. `vercel.json` (optional — Next.js works out of the box):**
+
+```json
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "framework": "nextjs"
+}
+```
+
+### Option B: Hono on Vercel Edge
+
+Use the `@objectstack/hono` adapter for lightweight Edge Function deployments:
+
+```typescript
+// api/[[...route]].ts (Vercel Edge Function)
+import { Hono } from 'hono';
+import { handle } from 'hono/vercel';
+import { ObjectKernel, DriverPlugin } from '@objectstack/runtime';
+import { ObjectQLPlugin } from '@objectstack/objectql';
+
+const app = new Hono().basePath('/api');
+
+// Initialize kernel (singleton)
+let kernel: ObjectKernel | null = null;
+async function getKernel() {
+ if (kernel) return kernel;
+ kernel = new ObjectKernel();
+ await kernel.use(new ObjectQLPlugin());
+ // await kernel.use(new DriverPlugin(yourDriver));
+ await kernel.bootstrap();
+ return kernel;
+}
+
+// Catch-all route
+app.all('/*', async (c) => {
+ const k = await getKernel();
+ // Dispatch through HttpDispatcher
+ // ...
+ return c.json({ status: 'ok' });
+});
+
+export const GET = handle(app);
+export const POST = handle(app);
+export const PATCH = handle(app);
+export const DELETE = handle(app);
+```
+
+---
+
+## Environment Variables
+
+Configure these in Vercel Project Settings → Environment Variables:
+
+| Variable | Mode | Description |
+| :--- | :--- | :--- |
+| `VITE_RUNTIME_MODE` | MSW | Set to `msw` (build-time Vite env) |
+| `VITE_SERVER_URL` | Server | Backend API URL (empty for same-origin) |
+| `DATABASE_URL` | Server | Database connection string |
+
+
+**Never commit secrets.** Use Vercel's environment variable UI or the Vercel CLI (`vercel env add`) to configure `DATABASE_URL` and other credentials.
+
+
+---
+
+## Runtime Mode Switching
+
+ObjectStack Studio supports switching between MSW and Server mode at runtime using a URL parameter:
+
+```
+https://myapp.vercel.app?mode=msw → In-browser kernel
+https://myapp.vercel.app?mode=server → Real backend
+```
+
+This is controlled by the config module which checks (in priority order):
+1. `?mode=` URL parameter
+2. `VITE_RUNTIME_MODE` environment variable
+3. Embedded detection (`/_studio/` path)
+4. Default: `msw`
+
+---
+
+## Deployment Checklist
+
+### MSW Mode
+
+- [ ] `msw init public --save` has been run (Service Worker in `public/`)
+- [ ] `vercel.json` specifies `"framework": "vite"` and SPA rewrites
+- [ ] `VITE_RUNTIME_MODE=msw` is set in build environment
+- [ ] Seed data is defined in `objectstack.config.ts` (`data` array)
+
+### Server Mode
+
+- [ ] API route handler is configured (`app/api/[...objectstack]/route.ts`)
+- [ ] Kernel singleton initializes the correct database driver
+- [ ] `DATABASE_URL` is configured in Vercel environment variables
+- [ ] CORS is configured if frontend and API are on different origins
+
+---
+
+## Comparison
+
+| Feature | MSW Mode | Server Mode |
+| :--- | :--- | :--- |
+| **Database** | In-memory (browser) | PostgreSQL, MongoDB, etc. |
+| **Data Persistence** | Per session (lost on refresh) | Persistent |
+| **Cold Start** | None (client-side) | ~200ms (Serverless) |
+| **Offline Support** | ✅ Full | ❌ Requires network |
+| **Multi-user** | ❌ Single user | ✅ Full |
+| **Cost** | Free (static hosting) | Pay per invocation |
+| **Best For** | Demos, prototypes, docs | Production applications |
+
+---
+
+## Related
+
+- [Plugin System](/docs/guides/plugins) — MSW and Hono server plugins
+- [Client SDK](/docs/guides/client-sdk) — Frontend data fetching
+- [Driver Configuration](/docs/guides/driver-configuration) — Database setup
+- [Architecture](/docs/getting-started/architecture) — Kernel and runtime overview
diff --git a/content/docs/guides/index.mdx b/content/docs/guides/index.mdx
index 3e7cf57a5..8c0f5d4aa 100644
--- a/content/docs/guides/index.mdx
+++ b/content/docs/guides/index.mdx
@@ -61,6 +61,7 @@ Practical, task-oriented guides covering the full development workflow. Each gui
| Guide | What You'll Learn |
| :--- | :--- |
+| [Deploy to Vercel](/docs/guides/deployment-vercel) | MSW mode (static SPA) and Server mode (Serverless) deployment |
| [Error Handling (Client)](/docs/guides/error-handling-client) | Frontend error handling patterns |
| [Error Handling (Server)](/docs/guides/error-handling-server) | Backend error handling and recovery |
| [Development Standards](/docs/guides/standards) | Naming conventions, project structure, testing |
diff --git a/content/docs/guides/meta.json b/content/docs/guides/meta.json
index 957f9423d..396b3f70e 100644
--- a/content/docs/guides/meta.json
+++ b/content/docs/guides/meta.json
@@ -19,6 +19,7 @@
"kernel-services",
"data-flow",
"---Operations---",
+ "deployment-vercel",
"error-handling-client",
"error-handling-server",
"standards",
From b5319847b0e2a638dd786d4e1ddcccbdf310a9d5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Feb 2026 05:09:20 +0000
Subject: [PATCH 3/3] Address code review: add AppPlugin, fix Hono Edge example
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
content/docs/guides/deployment-vercel.mdx | 54 +++++++++++++++++++----
1 file changed, 46 insertions(+), 8 deletions(-)
diff --git a/content/docs/guides/deployment-vercel.mdx b/content/docs/guides/deployment-vercel.mdx
index c4940bb3f..36c5ccabd 100644
--- a/content/docs/guides/deployment-vercel.mdx
+++ b/content/docs/guides/deployment-vercel.mdx
@@ -214,7 +214,7 @@ This is the recommended approach for Vercel. The `@objectstack/nextjs` adapter m
```typescript
// lib/kernel.ts
-import { ObjectKernel, DriverPlugin } from '@objectstack/runtime';
+import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import appConfig from '../objectstack.config';
@@ -231,6 +231,9 @@ export async function getKernel() {
// url: process.env.DATABASE_URL,
// })));
+ // Load the application configuration (objects, data, etc.)
+ await kernel.use(new AppPlugin(appConfig));
+
await kernel.bootstrap();
return kernel;
}
@@ -266,31 +269,62 @@ export { handler as GET, handler as POST, handler as PATCH, handler as DELETE };
Use the `@objectstack/hono` adapter for lightweight Edge Function deployments:
```typescript
-// api/[[...route]].ts (Vercel Edge Function)
+// api/[...route].ts (Vercel Edge Function)
import { Hono } from 'hono';
import { handle } from 'hono/vercel';
-import { ObjectKernel, DriverPlugin } from '@objectstack/runtime';
+import { ObjectKernel, DriverPlugin, AppPlugin, HttpDispatcher } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
+import appConfig from '../objectstack.config';
+
+export const config = { runtime: 'edge' };
const app = new Hono().basePath('/api');
-// Initialize kernel (singleton)
+// Initialize kernel (singleton — survives across warm invocations)
let kernel: ObjectKernel | null = null;
async function getKernel() {
if (kernel) return kernel;
kernel = new ObjectKernel();
await kernel.use(new ObjectQLPlugin());
// await kernel.use(new DriverPlugin(yourDriver));
+ await kernel.use(new AppPlugin(appConfig));
await kernel.bootstrap();
return kernel;
}
-// Catch-all route
+// Catch-all route — delegates to HttpDispatcher
app.all('/*', async (c) => {
const k = await getKernel();
- // Dispatch through HttpDispatcher
- // ...
- return c.json({ status: 'ok' });
+ const dispatcher = new HttpDispatcher(k);
+ const method = c.req.method;
+ const url = new URL(c.req.url);
+ const segments = url.pathname.replace(/^\/api\//, '').split('/').filter(Boolean);
+
+ // Dispatch based on first segment (data, meta, auth, etc.)
+ const body = ['POST', 'PATCH', 'PUT'].includes(method)
+ ? await c.req.json().catch(() => ({}))
+ : {};
+
+ const subPath = segments.slice(1).join('/');
+ let result;
+
+ switch (segments[0]) {
+ case 'data':
+ const queryParams: Record = {};
+ url.searchParams.forEach((v, k) => (queryParams[k] = v));
+ result = await dispatcher.handleData(subPath, method, body, queryParams, {});
+ break;
+ case 'meta':
+ result = await dispatcher.handleMetadata(subPath, {}, method, body);
+ break;
+ default:
+ return c.json({ error: 'Not Found' }, 404);
+ }
+
+ if (result.handled && result.response) {
+ return c.json(result.response.body, result.response.status as any);
+ }
+ return c.json({ error: 'Not Found' }, 404);
});
export const GET = handle(app);
@@ -299,6 +333,10 @@ export const PATCH = handle(app);
export const DELETE = handle(app);
```
+
+The Next.js adapter (Option A) is recommended for most Vercel deployments because it handles all protocol endpoints automatically. Use the Hono approach when you need fine-grained control over routing or Edge runtime.
+
+
---
## Environment Variables