Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/app-host/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Turso Database Configuration
# Required for Vercel deployment
TURSO_DATABASE_URL=libsql://your-database.turso.io
TURSO_AUTH_TOKEN=your-auth-token-here
19 changes: 19 additions & 0 deletions examples/app-host/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Build artifacts
dist/
.turbo/

# Bundled API handler (generated during Vercel build)
api/_handler.js
api/_handler.js.map

# Node modules
node_modules/

# Environment files
.env
.env.local
.env.*.local

# OS files
.DS_Store
Thumbs.db
16 changes: 16 additions & 0 deletions examples/app-host/.vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Ignore build artifacts
dist/
.turbo/

# Ignore test files
test/
*.test.ts
*.spec.ts

# Ignore development-only files that are not required by the Vercel build
debug-registry.ts

# Keep only the bundled API handler
!api/_handler.js
!api/_handler.js.map
!api/[[...route]].js
148 changes: 148 additions & 0 deletions examples/app-host/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Deploying App Host to Vercel

This example demonstrates how to deploy the ObjectStack app-host to Vercel using Hono.

## Prerequisites

1. A Vercel account
2. Vercel CLI installed (optional): `npm i -g vercel`

## Environment Variables

Set the following environment variables in your Vercel project:

- `AUTH_SECRET`: A secure random string (minimum 32 characters) for authentication
- `TURSO_DATABASE_URL`: Your Turso database URL (e.g., `libsql://your-database.turso.io`)
- `TURSO_AUTH_TOKEN`: Your Turso authentication token

You can get these credentials from [Turso Dashboard](https://turso.tech/).

## Deployment Steps

### Option 1: Using Vercel CLI

1. Install Vercel CLI:
```bash
npm i -g vercel
```

2. Navigate to the app-host directory:
```bash
cd examples/app-host
```

3. Deploy to Vercel:
```bash
vercel
```

4. Set environment variables:
```bash
vercel env add AUTH_SECRET
vercel env add TURSO_DATABASE_URL
vercel env add TURSO_AUTH_TOKEN
```

### Option 2: Using Vercel Dashboard

1. Import the repository to Vercel
2. Set the root directory to `examples/app-host`
3. Add environment variables in the project settings
4. Deploy

## Build Configuration

The build is configured in `vercel.json`:

- **Install Command**: `cd ../.. && pnpm install` (installs monorepo dependencies)
- **Build Command**: `bash scripts/build-vercel.sh` (builds and bundles the application)
- **Framework**: `hono` (uses Vercel's Hono framework preset)

## How It Works

1. **Build Process** (`scripts/build-vercel.sh`):
- Builds the TypeScript project using Turbo
- Bundles the server code using esbuild (`scripts/bundle-api.mjs`)

2. **API Handler** (`api/[[...route]].js`):
- Committed catch-all route handler that Vercel detects pre-build
- Delegates to the bundled handler (`api/_handler.js`)

3. **Server Entrypoint** (`server/index.ts`):
- Boots ObjectStack kernel with Hono adapter
- Uses `@hono/node-server`'s `getRequestListener()` for Vercel compatibility
- Connects to Turso database in remote mode (HTTP-only, no local SQLite)
- Handles Vercel's pre-buffered request body properly

4. **Hono Integration**:
- Uses `@objectstack/hono` adapter to create the HTTP application
- Provides REST API at `/api/v1` prefix
- Includes authentication via AuthPlugin

## Architecture

The deployment follows Vercel's serverless function pattern:

```
examples/app-host/
├── api/
│ ├── [[...route]].js # Committed entry point
│ └── _handler.js # Generated bundle (not committed)
├── server/
│ └── index.ts # Server implementation
├── scripts/
│ ├── build-vercel.sh # Build script
│ └── bundle-api.mjs # Bundler configuration
├── .npmrc # pnpm configuration (node-linker=hoisted)
└── vercel.json # Vercel configuration
```

## Testing Locally

Before deploying, you can test locally:

```bash
# Build the application
pnpm build

# Run in development mode
pnpm dev

# Test the API
curl http://localhost:3000/api/v1/discovery
```

## Accessing the API

After deployment, your API will be available at:

- Discovery: `https://your-app.vercel.app/api/v1/discovery`
- Data API: `https://your-app.vercel.app/api/v1/data/:object`
- Meta API: `https://your-app.vercel.app/api/v1/meta/:type`

## Troubleshooting

### Build Fails

- Ensure all dependencies are installed: `pnpm install`
- Check build logs in Vercel dashboard
- Verify `build-vercel.sh` is executable: `chmod +x scripts/build-vercel.sh`

### Runtime Errors

- Check function logs in Vercel dashboard
- Verify environment variables are set correctly (`TURSO_DATABASE_URL`, `TURSO_AUTH_TOKEN`, `AUTH_SECRET`)
- Ensure `AUTH_SECRET` is at least 32 characters
- Test Turso connection using the provided credentials

### Database Connection Issues

- Verify your Turso database URL and auth token are correct
- Check that your Turso database is accessible (not paused)
- The deployment uses TursoDriver in **remote mode** (HTTP-only), which doesn't require native modules like better-sqlite3

## References

- [Vercel Hono Documentation](https://vercel.com/docs/frameworks/backend/hono)
- [ObjectStack Documentation](../../README.md)
- [Hono Documentation](https://hono.dev/)
7 changes: 7 additions & 0 deletions examples/app-host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
This is a reference implementation of the ObjectStack Server Protocol (Kernel).
It demonstrates how to build a metadata-driven backend that dynamically loads object definitions from plugins and automatically generates REST APIs.

## Deployment

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/objectstack-ai/framework/tree/main/examples/app-host&project-name=objectstack-app-host&repository-name=objectstack-app-host)

See [DEPLOYMENT.md](./DEPLOYMENT.md) for detailed deployment instructions.

## Features

- **Dynamic Schema Loading**: Loads `crm` and `todo` apps as plugins.
- **Unified Metadata API**: `/api/v1/meta/objects`
- **Unified Data API**: `/api/v1/data/:object` (CRUD)
- **Zero-Code Backend**: No creating routes or controllers per object.
- **Preview Mode**: Run in demo mode — bypass login, auto-simulate admin identity.
- **Vercel Deployment**: Ready-to-deploy to Vercel with Hono adapter.

## Setup

Expand Down
16 changes: 16 additions & 0 deletions examples/app-host/api/[[...route]].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Vercel Serverless Function — Catch-all API route.
//
// This file MUST be committed to the repository so Vercel can detect it
// as a serverless function during the pre-build phase.
//
// It delegates to the esbuild bundle (`_handler.js`) generated by
// `scripts/bundle-api.mjs` during the Vercel build step. A separate
// bundle file is used (rather than overwriting this file) so that:
// 1. Vercel always finds this committed entry point (no "File not found").
// 2. Vercel does not TypeScript-compile a .ts stub that references
// source files absent at runtime (no ERR_MODULE_NOT_FOUND).
//
// @see ../server/index.ts — the actual server entrypoint
// @see ../scripts/bundle-api.mjs — the esbuild bundler

export { default, config } from './_handler.js';
9 changes: 8 additions & 1 deletion examples/app-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@example/app-host",
"version": "4.0.3",
"license": "Apache-2.0",
"type": "module",
"private": true,
"scripts": {
"dev": "objectstack serve --dev",
Expand All @@ -16,16 +17,22 @@
"@example/app-crm": "workspace:*",
"@example/app-todo": "workspace:*",
"@example/plugin-bi": "workspace:*",
"@hono/node-server": "^1.19.14",
"@libsql/client": "^0.14.0",
"@objectstack/driver-memory": "workspace:*",
"@objectstack/driver-turso": "workspace:*",
"@objectstack/hono": "workspace:*",
"@objectstack/metadata": "workspace:*",
"@objectstack/objectql": "workspace:*",
"@objectstack/plugin-auth": "workspace:*",
"@objectstack/plugin-hono-server": "workspace:*",
"@objectstack/runtime": "workspace:*",
"@objectstack/spec": "workspace:*"
"@objectstack/spec": "workspace:*",
"hono": "^4.12.12"
},
"devDependencies": {
"@objectstack/cli": "workspace:*",
"esbuild": "^0.24.2",
Comment on lines 17 to +35
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Vercel function entrypoint (api/[[...route]].js) and the bundled handler are authored/emitted as ESM (export ...). @example/app-host/package.json currently has no "type": "module", so Node/Vercel will treat .js files as CommonJS and throw a syntax error when loading the handler. Add "type": "module" (matching apps/studio) or switch the Vercel handler/bundle output to CommonJS consistently.

Copilot uses AI. Check for mistakes.
"ts-node": "^10.9.2",
"tsx": "^4.21.0",
"typescript": "^6.0.2"
Expand Down
26 changes: 26 additions & 0 deletions examples/app-host/scripts/build-vercel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

# Build script for Vercel deployment of @example/app-host.
#
# Follows the Vercel deployment pattern:
# - api/[[...route]].js is committed to the repo (Vercel detects it pre-build)
# - esbuild bundles server/index.ts → api/_handler.js (self-contained bundle)
# - The committed .js wrapper re-exports from _handler.js at runtime
#
# Steps:
# 1. Build the project with turbo
# 2. Bundle the API serverless function (→ api/_handler.js)
# 3. Copy native/external modules into local node_modules for Vercel packaging

echo "[build-vercel] Starting app-host build..."

# 1. Build the project with turbo (from monorepo root)
cd ../..
pnpm turbo run build --filter=@example/app-host
cd examples/app-host

# 2. Bundle API serverless function
node scripts/bundle-api.mjs

echo "[build-vercel] Done. Serverless function in api/[[...route]].js → api/_handler.js"
61 changes: 61 additions & 0 deletions examples/app-host/scripts/bundle-api.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Pre-bundles the Vercel serverless API function.
*
* Vercel's @vercel/node builder resolves pnpm workspace packages via symlinks,
* which can cause esbuild to resolve to TypeScript source files rather than
* compiled dist output — producing ERR_MODULE_NOT_FOUND at runtime.
*
* This script bundles server/index.ts with ALL dependencies inlined (including
* npm packages), so the deployed function is self-contained. Only packages
* with native bindings are kept external.
*
* Run from the examples/app-host directory during the Vercel build step.
*/

import { build } from 'esbuild';

// Packages that cannot be bundled (native bindings / optional drivers)
const EXTERNAL = [
// Optional knex database drivers — never used at runtime, but knex requires() them
'pg',
'pg-native',
'pg-query-stream',
'mysql',
'mysql2',
'sqlite3',
'oracledb',
'tedious',
// macOS-only native file watcher
'fsevents',
];

await build({
entryPoints: ['server/index.ts'],
bundle: true,
platform: 'node',
format: 'esm',
target: 'es2020',
outfile: 'api/_handler.js',
sourcemap: true,
external: EXTERNAL,
// Silence warnings about optional/unused require() calls in knex drivers
logOverride: { 'require-resolve-not-external': 'silent' },
// Vercel resolves ESM .js files correctly when "type": "module" is set.
// CJS format would conflict with the project's "type": "module" setting,
// causing Node.js to fail parsing require()/module.exports as ESM syntax.
//
// The createRequire banner provides a real `require` function in the ESM
// scope. esbuild's __require shim (generated for CJS→ESM conversion)
// checks `typeof require !== "undefined"` and uses it when available,
// which fixes "Dynamic require of <builtin> is not supported" errors
// from CJS dependencies like knex/tarn that require() Node.js built-ins.
banner: {
js: [
'// Bundled by esbuild — see scripts/bundle-api.mjs',
'import { createRequire } from "module";',
'const require = createRequire(import.meta.url);',
].join('\n'),
},
});

console.log('[bundle-api] Bundled server/index.ts → api/_handler.js');
Loading
Loading