Skip to content

Commit 5cd6c83

Browse files
feat(server): Configure Vercel deployment with dual-driver setup (Turso + InMemory)
- Add InMemoryDriver alongside TursoDriver for example apps - Configure datasource mapping: example apps (crm/todo/bi) → memory, system → turso - Mark @libsql/client as external in bundle-api.mjs for native binaries - Add .npmrc with node-linker=hoisted for Vercel compatibility - Update build-vercel.sh to copy @libsql/client and better-sqlite3 native modules - Update vercel.json to include native modules in function deployment package - Make Turso credentials optional (fallback to file-based SQLite for local dev) Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/a39e186a-367a-4f31-b27e-0dcf46dfcb96 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent 3c8f7c8 commit 5cd6c83

File tree

5 files changed

+75
-8
lines changed

5 files changed

+75
-8
lines changed

apps/server/.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# pnpm configuration for Vercel deployment
2+
# Use hoisted node_modules instead of symlinks to avoid Vercel packaging issues
3+
node-linker=hoisted

apps/server/scripts/build-vercel.sh

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ set -euo pipefail
1212
# Steps:
1313
# 1. Build the project with turbo (includes studio)
1414
# 2. Bundle the API serverless function (→ api/_handler.js)
15-
# 3. Copy studio dist files to public/ for UI serving
15+
# 3. Copy native/external modules into local node_modules for Vercel packaging
16+
# 4. Copy studio dist files to public/ for UI serving
1617

1718
echo "[build-vercel] Starting server build..."
1819

@@ -25,7 +26,43 @@ cd apps/server
2526
# 2. Bundle API serverless function
2627
node scripts/bundle-api.mjs
2728

28-
# 3. Copy studio dist files to public/ for UI serving
29+
# 3. Copy native/external modules into local node_modules for Vercel packaging.
30+
#
31+
# This monorepo uses pnpm's default strict node_modules structure. External
32+
# dependencies marked in bundle-api.mjs (@libsql/client, better-sqlite3) only
33+
# exist in the monorepo root's node_modules/.pnpm/ virtual store.
34+
#
35+
# The vercel.json includeFiles pattern references node_modules/ relative to
36+
# apps/server/, so we must copy the actual module files here for Vercel to
37+
# include them in the serverless function's deployment package.
38+
echo "[build-vercel] Copying external native modules to local node_modules..."
39+
for mod in "@libsql/client" better-sqlite3; do
40+
src="../../node_modules/$mod"
41+
if [ -e "$src" ]; then
42+
dest="node_modules/$mod"
43+
mkdir -p "$(dirname "$dest")"
44+
cp -rL "$src" "$dest"
45+
echo "[build-vercel] ✓ Copied $mod"
46+
else
47+
echo "[build-vercel] ⚠ $mod not found at $src (skipped)"
48+
fi
49+
done
50+
51+
# Copy native binary subdirectories for @libsql/client
52+
if [ -d "../../node_modules/@libsql" ]; then
53+
mkdir -p "node_modules/@libsql"
54+
for pkg in ../../node_modules/@libsql/*/; do
55+
pkgname="$(basename "$pkg")"
56+
if [ "$pkgname" != "client" ]; then # client already copied above
57+
cp -rL "$pkg" "node_modules/@libsql/$pkgname"
58+
echo "[build-vercel] ✓ Copied @libsql/$pkgname"
59+
fi
60+
done
61+
else
62+
echo "[build-vercel] ⚠ @libsql not found (skipped)"
63+
fi
64+
65+
# 4. Copy studio dist files to public/ for UI serving
2966
echo "[build-vercel] Copying studio dist to public/..."
3067
rm -rf public
3168
mkdir -p public

apps/server/scripts/bundle-api.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@
1414

1515
import { build } from 'esbuild';
1616

17-
// Packages that cannot be bundled (native bindings / optional drivers)
17+
// Packages that cannot be bundled (native bindings / platform-specific modules)
1818
const EXTERNAL = [
19+
// @libsql/client has platform-specific native binaries that must be external
20+
'@libsql/client',
1921
// Optional knex database drivers — never used at runtime, but knex requires() them
2022
'pg',
2123
'pg-native',
2224
'pg-query-stream',
2325
'mysql',
2426
'mysql2',
2527
'sqlite3',
28+
'better-sqlite3',
2629
'oracledb',
2730
'tedious',
2831
// macOS-only native file watcher

apps/server/server/index.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
1414
import { ObjectQLPlugin } from '@objectstack/objectql';
1515
import { TursoDriver } from '@objectstack/driver-turso';
16+
import { InMemoryDriver } from '@objectstack/driver-memory';
1617
import { createHonoApp } from '@objectstack/hono';
1718
import { AuthPlugin } from '@objectstack/plugin-auth';
1819
import { SecurityPlugin } from '@objectstack/plugin-security';
@@ -62,21 +63,40 @@ async function ensureKernel(): Promise<ObjectKernel> {
6263
// Register ObjectQL engine
6364
await kernel.use(new ObjectQLPlugin());
6465

65-
// Database driver - Turso (remote mode for Vercel)
66+
// Register Memory Driver for example apps (volatile, fast)
67+
await kernel.use(new DriverPlugin(new InMemoryDriver(), { name: 'memory' }));
68+
69+
// Register Turso Driver for system objects (persistent, production)
6670
const tursoUrl = process.env.TURSO_DATABASE_URL;
6771
const tursoToken = process.env.TURSO_AUTH_TOKEN;
6872

6973
if (!tursoUrl || !tursoToken) {
70-
throw new Error('Missing required environment variables: TURSO_DATABASE_URL and TURSO_AUTH_TOKEN');
74+
console.warn('[Vercel] Turso credentials not found, falling back to file-based SQLite');
7175
}
7276

7377
const tursoDriver = new TursoDriver({
74-
url: tursoUrl,
78+
url: tursoUrl ?? 'file:./data/server.db',
7579
authToken: tursoToken,
7680
// Remote mode - no local sync needed for Vercel
7781
});
7882

79-
await kernel.use(new DriverPlugin(tursoDriver));
83+
await kernel.use(new DriverPlugin(tursoDriver, { name: 'turso' }));
84+
85+
// Configure datasource mapping
86+
// This must be done before loading apps, so ObjectQL can route objects correctly
87+
const ql = kernel.getService('ObjectQL');
88+
if (ql && typeof ql.setDatasourceMapping === 'function') {
89+
ql.setDatasourceMapping([
90+
// Example apps use in-memory driver for fast, ephemeral data
91+
{ namespace: 'crm', datasource: 'memory' },
92+
{ namespace: 'todo', datasource: 'memory' },
93+
{ namespace: 'bi', datasource: 'memory' },
94+
// System objects use Turso for persistent, production-grade storage
95+
{ namespace: 'sys', datasource: 'turso' },
96+
// Default fallback to memory driver
97+
{ default: true, datasource: 'memory' },
98+
]);
99+
}
80100

81101
// Load app manifests (BEFORE plugins that need object schemas)
82102
await kernel.use(new AppPlugin(CrmApp));

apps/server/vercel.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
"functions": {
1313
"api/**/*.js": {
1414
"memory": 1024,
15-
"maxDuration": 60
15+
"maxDuration": 60,
16+
"includeFiles": [
17+
"node_modules/@libsql/**",
18+
"node_modules/better-sqlite3/**"
19+
]
1620
}
1721
},
1822
"headers": [

0 commit comments

Comments
 (0)