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
2 changes: 2 additions & 0 deletions apps/server/.gitignore
Original file line number Diff line number Diff line change
@@ -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/
Expand Down
4 changes: 3 additions & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
24 changes: 24 additions & 0 deletions apps/server/scripts/build-vercel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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..."

Expand All @@ -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"
5 changes: 5 additions & 0 deletions apps/server/scripts/bundle-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
24 changes: 20 additions & 4 deletions apps/server/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -62,7 +63,11 @@ async function ensureKernel(): Promise<ObjectKernel> {
// 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;

Expand All @@ -76,7 +81,7 @@ async function ensureKernel(): Promise<ObjectKernel> {
// 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));
Expand Down Expand Up @@ -112,6 +117,18 @@ async function ensureKernel(): Promise<ObjectKernel> {

await kernel.bootstrap();

// Configure datasource mapping AFTER bootstrap (ObjectQL service is registered during init)
const ql = await kernel.getServiceAsync<ObjectQL>('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;
Expand Down Expand Up @@ -249,6 +266,5 @@ export default getRequestListener(async (request, env) => {
* Vercel per-function configuration.
*/
export const config = {
memory: 1024,
maxDuration: 60,
};
4 changes: 2 additions & 2 deletions apps/server/vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
},
"functions": {
"api/**/*.js": {
"memory": 1024,
"maxDuration": 60
"maxDuration": 60,
"includeFiles": "api/node_modules/**"
}
},
"headers": [
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.