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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`apps/demo`** — added explicit `@objectstack/spec` and `zod` devDependencies as defense-in-depth for Vercel deployment.
- **`@objectql/types`** — moved `@objectstack/spec` and `zod` from `devDependencies` to `dependencies`. The compiled JS output contains runtime imports of `@objectstack/spec` (via `z.infer<typeof Data.X>` patterns), so they must be declared as production dependencies.

### Changed

- **`apps/demo`** — switched default data driver from `@objectstack/driver-memory` (InMemoryDriver) to `@objectql/driver-turso` (TursoDriver). When `TURSO_DATABASE_URL` is set, the demo uses a persistent Turso/libSQL database; otherwise falls back to InMemoryDriver for zero-config local development.
- `objectstack.config.ts` — environment-aware `createDefaultDriver()` selects Turso or MemoryDriver.
- `api/[[...route]].ts` — Vercel serverless handler uses TursoDriver with `TURSO_DATABASE_URL`, `TURSO_AUTH_TOKEN`, `TURSO_SYNC_URL`, and `TURSO_SYNC_INTERVAL` env vars.
- `scripts/build-vercel.sh` — now builds `@objectql/driver-turso` alongside other drivers.
- `README.md` — documents new Turso environment variables and architecture.

### Added

- **`apps/demo`** — standalone Vercel-deployable demo application ([#issue](https://github.com/objectstack-ai/objectql/issues)):
Expand Down
11 changes: 8 additions & 3 deletions apps/demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Runs locally with `@objectstack/cli` and deploys to **Vercel** as a serverless f

## Features

- **In-memory driver** — zero external database required; data persists across warm Vercel invocations.
- **Turso/libSQL driver** — persistent, edge-first SQLite via `@objectql/driver-turso` when `TURSO_DATABASE_URL` is set.
- **In-memory fallback** — zero external database required for quick local development.
- **Console UI** — full ObjectStack Console available at `/console/`.
- **Studio UI** — ObjectStack Studio available at `/_studio/`.
- **Project-Tracker showcase** — ships with the `examples/showcase/project-tracker` metadata (objects, views, permissions) so the demo has real data structures out of the box.
Expand Down Expand Up @@ -44,6 +45,10 @@ The development server starts on `http://localhost:3000`.
|---|---|---|
| `AUTH_SECRET` | **Yes** (production) | Secret key for signing auth tokens. Generate with `openssl rand -base64 32`. |
| `AUTH_TRUSTED_ORIGINS` | No | Comma-separated list of additional trusted origins (e.g. `https://myapp.example.com`). |
| `TURSO_DATABASE_URL` | No | Turso/libSQL database URL (e.g. `libsql://my-db-org.turso.io`). When set, uses Turso instead of in-memory driver. |
| `TURSO_AUTH_TOKEN` | When using Turso | JWT auth token for the Turso database. |
| `TURSO_SYNC_URL` | No | Remote sync URL for embedded replica mode. |
| `TURSO_SYNC_INTERVAL` | No | Periodic sync interval in seconds (default: 60). |

4. Deploy:

Expand All @@ -58,7 +63,7 @@ vercel --cwd apps/demo --prod
### How It Works

- **`vercel.json`** — Configures Vercel to use a custom build command, allocate 1 GiB memory to the serverless function, and rewrite all requests to the catch-all `api/[[...route]].ts` handler.
- **`api/[[...route]].ts`** — Bootstraps the full ObjectStack kernel with ObjectQL plugins, the in-memory driver, auth, Console, and Studio. Uses `@hono/node-server`'s `getRequestListener()` to bridge the Vercel serverless runtime with the Hono HTTP framework.
- **`api/[[...route]].ts`** — Bootstraps the full ObjectStack kernel with ObjectQL plugins, the Turso driver (or in-memory fallback), auth, Console, and Studio. Uses `@hono/node-server`'s `getRequestListener()` to bridge the Vercel serverless runtime with the Hono HTTP framework.
- **`scripts/build-vercel.sh`** — Builds all required workspace packages (foundation, drivers, plugins, protocols, examples) in the correct dependency order.
- **`scripts/patch-symlinks.cjs`** — Replaces pnpm workspace symlinks with real copies so Vercel can bundle the function without symlink errors.

Expand Down Expand Up @@ -110,5 +115,5 @@ apps/demo/
ObjectStack Kernel
(ObjectQL + Auth +
InMemoryDriver)
TursoDriver / InMemoryDriver)
```
57 changes: 48 additions & 9 deletions apps/demo/api/[[...route]].ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* Vercel Serverless Function — ObjectQL Demo Handler
*
* Bootstraps the ObjectStack kernel with ObjectQL plugins and the
* project-tracker demo metadata, using @objectstack/driver-memory
* for zero-config in-memory data.
* project-tracker demo metadata, using @objectql/driver-turso when
* TURSO_DATABASE_URL is set, or @objectstack/driver-memory as a
* zero-config fallback.
*
* Uses `getRequestListener()` from `@hono/node-server` together with
* an `extractBody()` helper to handle Vercel's pre-buffered request
Expand All @@ -14,8 +15,10 @@
* a fresh `Request` object prevents POST/PUT/PATCH requests (e.g.
* login) from hanging indefinitely.
*
* Data lives in the function instance's memory and persists across
* warm invocations (Vercel Fluid Compute) but resets on cold start.
* When using Turso, data is persisted in the Turso cloud database.
* When using InMemoryDriver, data lives in the function instance's
* memory and persists across warm invocations (Vercel Fluid Compute)
* but resets on cold start.
*
* Both Console (/) and Studio (/_studio/) UIs are served as static SPAs.
*
Expand All @@ -30,6 +33,7 @@ import { ObjectKernel, DriverPlugin, AppPlugin, createDispatcherPlugin, createRe
import { HonoHttpServer } from '@objectstack/plugin-hono-server';
import { AuthPlugin } from '@objectstack/plugin-auth';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { createTursoDriver, type TursoDriver } from '@objectql/driver-turso';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { getRequestListener } from '@hono/node-server';
import type { Hono } from 'hono';
Expand Down Expand Up @@ -278,10 +282,38 @@ async function bootstrap(): Promise<Hono> {
await withTimeout(kernel.use(new ObjectQLPlugin()), PLUGIN_TIMEOUT_MS, 'ObjectQLPlugin');
log('ObjectQLPlugin registered.');

// 2. In-memory data driver (no external DB required)
log('Registering DriverPlugin (InMemoryDriver)…');
await withTimeout(kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory')), PLUGIN_TIMEOUT_MS, 'DriverPlugin');
log('DriverPlugin registered.');
// 2. Data driver — Turso when TURSO_DATABASE_URL is set, InMemoryDriver otherwise
const tursoUrl = process.env.TURSO_DATABASE_URL;
let tursoDriver: TursoDriver | null = null;
if (tursoUrl) {
log(`Registering TursoDriver (${tursoUrl})…`);
const syncUrl = process.env.TURSO_SYNC_URL;
const rawSyncInterval = process.env.TURSO_SYNC_INTERVAL;
const parsedSyncInterval =
rawSyncInterval !== undefined ? Number(rawSyncInterval) : NaN;
const syncIntervalSeconds = Number.isFinite(parsedSyncInterval)
? parsedSyncInterval
: 60;
tursoDriver = createTursoDriver({
url: tursoUrl,
authToken: process.env.TURSO_AUTH_TOKEN,
syncUrl,
sync: syncUrl
? {
intervalSeconds: syncIntervalSeconds,
onConnect: true,
}
: undefined,
});
// DriverPlugin from @objectstack/runtime accepts any driver; TursoDriver
// implements @objectql/types Driver which is structurally compatible.
await withTimeout(kernel.use(new DriverPlugin(tursoDriver, 'turso')), PLUGIN_TIMEOUT_MS, 'DriverPlugin-turso');
log('TursoDriver registered.');
} else {
log('Registering DriverPlugin (InMemoryDriver)…');
await withTimeout(kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory')), PLUGIN_TIMEOUT_MS, 'DriverPlugin-memory');
log('InMemoryDriver registered.');
}

// 3. HTTP server adapter — register the Hono app without TCP listener
const httpServer = new HonoHttpServer();
Expand Down Expand Up @@ -445,7 +477,14 @@ async function bootstrap(): Promise<Hono> {
log('Studio SPA registered.');
}

// 12. Bootstrap kernel (init + start all plugins, fire kernel:ready)
// 12. Connect Turso driver (if applicable) before kernel bootstrap
if (tursoDriver) {
log('Connecting TursoDriver…');
await withTimeout(tursoDriver.connect(), PLUGIN_TIMEOUT_MS, 'TursoDriver.connect()');
log('TursoDriver connected.');
}

// 13. Bootstrap kernel (init + start all plugins, fire kernel:ready)
log('Running kernel.bootstrap()…');
await withTimeout(kernel.bootstrap(), KERNEL_BOOTSTRAP_TIMEOUT_MS, 'kernel.bootstrap()');
log(`Bootstrap complete in ${elapsed()}.`);
Expand Down
46 changes: 40 additions & 6 deletions apps/demo/objectstack.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/**
* ObjectQL Demo — Application Configuration
*
* Minimal ObjectStack configuration for the demo application.
* Uses in-memory driver with the project-tracker showcase example.
* ObjectStack configuration for the demo application.
* Uses @objectql/driver-turso when TURSO_DATABASE_URL is set,
* falls back to MemoryDriver for zero-config local development.
*
* For local development: `pnpm dev` (uses @objectstack/cli)
* For Vercel deployment: configured via api/[[...route]].ts
Expand All @@ -29,10 +30,39 @@ import { FormulaPlugin } from '@objectql/plugin-formula';
import { ObjectQLSecurityPlugin } from '@objectql/plugin-security';
import { createApiRegistryPlugin } from '@objectstack/core';
import { MemoryDriver } from '@objectql/driver-memory';
import { createTursoDriver } from '@objectql/driver-turso';
import { createAppPlugin } from '@objectql/platform-node';

// In-memory driver — zero-config, no external DB required.
const defaultDriver = new MemoryDriver();
// Choose driver based on environment — Turso when TURSO_DATABASE_URL is set,
// MemoryDriver otherwise (zero-config fallback for quick starts).
function createDefaultDriver() {
const tursoUrl = process.env.TURSO_DATABASE_URL;
if (tursoUrl) {
console.log(`🗄️ Driver: Turso (${tursoUrl})`);
const syncUrl = process.env.TURSO_SYNC_URL;
const rawSyncInterval = process.env.TURSO_SYNC_INTERVAL;
const parsedSyncInterval =
rawSyncInterval !== undefined ? Number(rawSyncInterval) : NaN;
const syncIntervalSeconds = Number.isFinite(parsedSyncInterval)
? parsedSyncInterval
: 60;
return createTursoDriver({
url: tursoUrl,
authToken: process.env.TURSO_AUTH_TOKEN,
syncUrl,
sync: syncUrl
? {
intervalSeconds: syncIntervalSeconds,
onConnect: true,
}
: undefined,
});
}
console.log('🗄️ Driver: Memory (in-memory, non-persistent)');
return new MemoryDriver();
}

const defaultDriver = createDefaultDriver();

// Load the project-tracker showcase metadata.
const projectTrackerPlugin = createAppPlugin({
Expand All @@ -51,13 +81,17 @@ export default {
createApiRegistryPlugin(),
new HonoServerPlugin({}),
new ConsolePlugin(),
// Register the driver as 'driver.default' service.
// Register the active driver as 'driver.default' service so upstream
// ObjectQLPlugin can discover it during start() phase.
{
name: 'driver-default',
init: async (ctx: any) => {
ctx.registerService('driver.default', defaultDriver);
},
start: async () => {},
start: async () => {
// Driver.connect() is optional in the interface; call if present
await defaultDriver.connect?.();
},
},
projectTrackerPlugin,
new ObjectQLPlugin(),
Expand Down
14 changes: 14 additions & 0 deletions apps/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@
},
"devDependencies": {
"@hono/node-server": "^1.19.11",
"@libsql/client": "^0.17.2",
"@libsql/core": "^0.17.2",
"@libsql/hrana-client": "^0.9.0",
"@libsql/isomorphic-ws": "^0.1.5",
"@neon-rs/load": "^0.0.4",
"@object-ui/console": "^3.1.3",
"@objectql/core": "workspace:*",
"@objectql/driver-memory": "workspace:*",
"@objectql/driver-turso": "workspace:*",
"@objectql/example-project-tracker": "workspace:*",
"@objectql/platform-node": "workspace:*",
"@objectql/plugin-formula": "workspace:*",
Expand All @@ -35,8 +41,16 @@
"@objectstack/runtime": "^3.2.8",
"@objectstack/studio": "^3.2.8",
"@types/node": "^20.19.37",
"detect-libc": "^2.0.2",
"hono": "^4.12.8",
"js-base64": "^3.7.8",
"libsql": "^0.5.28",
"nanoid": "^3.3.11",
"promise-limit": "^2.7.0",
"typescript": "^5.9.3",
"ws": "^8.19.0",
"cross-fetch": "^4.1.0",
"node-fetch": "^2.7.0",
"zod": "^4.3.6"
},
"engines": {
Expand Down
1 change: 1 addition & 0 deletions apps/demo/scripts/build-vercel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pnpm --filter @objectql/platform-node build

echo "▸ Building drivers…"
pnpm --filter @objectql/driver-memory \
--filter @objectql/driver-turso \
--filter @objectql/driver-sql \
build

Expand Down
2 changes: 1 addition & 1 deletion apps/demo/vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"api/**/*.ts": {
"memory": 1024,
"maxDuration": 60,
"includeFiles": "{node_modules/@object-ui/console/dist,node_modules/@objectstack/plugin-auth/dist,node_modules/@objectstack/studio/dist,node_modules/@objectql/example-project-tracker/dist}/**"
"includeFiles": "node_modules/{@object-ui/*/dist,@objectstack/*/dist,@objectql/*/dist,@libsql,@neon-rs,libsql,detect-libc,js-base64,promise-limit,nanoid,ws,cross-fetch,node-fetch}/**"
}
},
"rewrites": [
Expand Down
42 changes: 42 additions & 0 deletions pnpm-lock.yaml

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

Loading