diff --git a/apps/server/.gitignore b/apps/server/.gitignore index 7b9ccd07e..b79cbf941 100644 --- a/apps/server/.gitignore +++ b/apps/server/.gitignore @@ -1,10 +1,12 @@ # Build artifacts dist/ .turbo/ +public/ # Bundled API handler (generated during Vercel build) api/_handler.js api/_handler.js.map +api/node_modules/ # Node modules node_modules/ diff --git a/apps/server/package.json b/apps/server/package.json index 1d5e8900c..a6dfd0710 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -35,7 +35,9 @@ "@objectstack/service-automation": "workspace:*", "@objectstack/service-feed": "workspace:*", "@objectstack/spec": "workspace:*", - "hono": "^4.12.12" + "hono": "^4.12.12", + "pino": "^10.3.1", + "pino-pretty": "^13.1.3" }, "devDependencies": { "@objectstack/cli": "workspace:*", diff --git a/apps/server/scripts/build-vercel.sh b/apps/server/scripts/build-vercel.sh index 20b624f59..64754b48d 100755 --- a/apps/server/scripts/build-vercel.sh +++ b/apps/server/scripts/build-vercel.sh @@ -8,11 +8,13 @@ set -euo pipefail # - esbuild bundles server/index.ts → api/_handler.js (self-contained bundle) # - The committed .js wrapper re-exports from _handler.js at runtime # - Studio SPA is built and copied to public/ for serving the UI +# - External dependencies installed in api/node_modules/ (no symlinks) # # Steps: # 1. Build the project with turbo (includes studio) # 2. Bundle the API serverless function (→ api/_handler.js) # 3. Copy studio dist files to public/ for UI serving +# 4. Install external deps in api/node_modules/ (resolve pnpm symlinks) echo "[build-vercel] Starting server build..." @@ -36,4 +38,26 @@ else echo "[build-vercel] ⚠ Studio dist not found (skipped)" fi +# 4. Install external dependencies in api/node_modules/ (no symlinks) +# pnpm uses symlinks in node_modules/, which Vercel's serverless function +# packaging cannot handle ("invalid deployment package" error). +# We use npm to install external packages as real files next to the handler. +echo "[build-vercel] Installing external dependencies for serverless function..." +cat > api/_package.json << 'DEPS' +{ + "private": true, + "dependencies": { + "@libsql/client": "0.14.0", + "pino": "10.3.1", + "pino-pretty": "13.1.3" + } +} +DEPS +cd api +mv _package.json package.json +npm install --production --no-package-lock --ignore-scripts --loglevel error +rm package.json +cd .. +echo "[build-vercel] ✓ External dependencies installed in api/node_modules/" + echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js → api/_handler.js" diff --git a/apps/server/scripts/bundle-api.mjs b/apps/server/scripts/bundle-api.mjs index 57700c855..9103a5d71 100644 --- a/apps/server/scripts/bundle-api.mjs +++ b/apps/server/scripts/bundle-api.mjs @@ -27,6 +27,11 @@ const EXTERNAL = [ 'tedious', // macOS-only native file watcher 'fsevents', + // LibSQL client — has native bindings, must remain external for Vercel + '@libsql/client', + // Logging libraries - use dynamic require, must be external + 'pino', + 'pino-pretty', ]; await build({ diff --git a/apps/server/server/index.ts b/apps/server/server/index.ts index 9d080cfa9..f3bbc577c 100644 --- a/apps/server/server/index.ts +++ b/apps/server/server/index.ts @@ -11,8 +11,9 @@ */ import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime'; -import { ObjectQLPlugin } from '@objectstack/objectql'; +import { ObjectQLPlugin, ObjectQL } from '@objectstack/objectql'; import { TursoDriver } from '@objectstack/driver-turso'; +import { InMemoryDriver } from '@objectstack/driver-memory'; import { createHonoApp } from '@objectstack/hono'; import { AuthPlugin } from '@objectstack/plugin-auth'; import { SecurityPlugin } from '@objectstack/plugin-security'; @@ -62,7 +63,11 @@ async function ensureKernel(): Promise { // Register ObjectQL engine await kernel.use(new ObjectQLPlugin()); - // Database driver - Turso (remote mode for Vercel) + // Register Memory driver (default for CRM, Todo, BI apps) + const memoryDriver = new InMemoryDriver(); + await kernel.use(new DriverPlugin(memoryDriver, 'memory')); + + // Register Turso driver (for sys namespace) const tursoUrl = process.env.TURSO_DATABASE_URL; const tursoToken = process.env.TURSO_AUTH_TOKEN; @@ -76,7 +81,7 @@ async function ensureKernel(): Promise { // Remote mode - no local sync needed for Vercel }); - await kernel.use(new DriverPlugin(tursoDriver)); + await kernel.use(new DriverPlugin(tursoDriver, 'turso')); // Load app manifests (BEFORE plugins that need object schemas) await kernel.use(new AppPlugin(CrmApp)); @@ -112,6 +117,18 @@ async function ensureKernel(): Promise { await kernel.bootstrap(); + // Configure datasource mapping AFTER bootstrap (ObjectQL service is registered during init) + const ql = await kernel.getServiceAsync('objectql'); + if (ql && typeof ql.setDatasourceMapping === 'function') { + ql.setDatasourceMapping([ + // System objects (sys namespace) use Turso for persistent storage + { namespace: 'sys', datasource: 'turso' }, + // All other objects use Memory driver as default + { default: true, datasource: 'memory' }, + ]); + console.log('[Vercel] Datasource mapping configured: sys → turso, default → memory'); + } + _kernel = kernel; console.log('[Vercel] Kernel ready.'); return kernel; @@ -249,6 +266,5 @@ export default getRequestListener(async (request, env) => { * Vercel per-function configuration. */ export const config = { - memory: 1024, maxDuration: 60, }; diff --git a/apps/server/vercel.json b/apps/server/vercel.json index fdaa07ceb..84811d0b0 100644 --- a/apps/server/vercel.json +++ b/apps/server/vercel.json @@ -11,8 +11,8 @@ }, "functions": { "api/**/*.js": { - "memory": 1024, - "maxDuration": 60 + "maxDuration": 60, + "includeFiles": "api/node_modules/**" } }, "headers": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 813ca2db3..0ebff6dcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,6 +193,12 @@ importers: hono: specifier: ^4.12.12 version: 4.12.12 + pino: + specifier: ^10.3.1 + version: 10.3.1 + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 devDependencies: '@objectstack/cli': specifier: workspace:*