Skip to content

Commit bf91b0d

Browse files
authored
Merge pull request #432 from objectstack-ai/copilot/use-objectql-driver-turso
2 parents 96b4a13 + cd08385 commit bf91b0d

File tree

8 files changed

+162
-19
lines changed

8 files changed

+162
-19
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- **`apps/demo`** — added explicit `@objectstack/spec` and `zod` devDependencies as defense-in-depth for Vercel deployment.
1414
- **`@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.
1515

16+
### Changed
17+
18+
- **`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.
19+
- `objectstack.config.ts` — environment-aware `createDefaultDriver()` selects Turso or MemoryDriver.
20+
- `api/[[...route]].ts` — Vercel serverless handler uses TursoDriver with `TURSO_DATABASE_URL`, `TURSO_AUTH_TOKEN`, `TURSO_SYNC_URL`, and `TURSO_SYNC_INTERVAL` env vars.
21+
- `scripts/build-vercel.sh` — now builds `@objectql/driver-turso` alongside other drivers.
22+
- `README.md` — documents new Turso environment variables and architecture.
23+
1624
### Added
1725

1826
- **`apps/demo`** — standalone Vercel-deployable demo application ([#issue](https://github.com/objectstack-ai/objectql/issues)):

apps/demo/README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ Runs locally with `@objectstack/cli` and deploys to **Vercel** as a serverless f
55

66
## Features
77

8-
- **In-memory driver** — zero external database required; data persists across warm Vercel invocations.
8+
- **Turso/libSQL driver** — persistent, edge-first SQLite via `@objectql/driver-turso` when `TURSO_DATABASE_URL` is set.
9+
- **In-memory fallback** — zero external database required for quick local development.
910
- **Console UI** — full ObjectStack Console available at `/console/`.
1011
- **Studio UI** — ObjectStack Studio available at `/_studio/`.
1112
- **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.
@@ -44,6 +45,10 @@ The development server starts on `http://localhost:3000`.
4445
|---|---|---|
4546
| `AUTH_SECRET` | **Yes** (production) | Secret key for signing auth tokens. Generate with `openssl rand -base64 32`. |
4647
| `AUTH_TRUSTED_ORIGINS` | No | Comma-separated list of additional trusted origins (e.g. `https://myapp.example.com`). |
48+
| `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. |
49+
| `TURSO_AUTH_TOKEN` | When using Turso | JWT auth token for the Turso database. |
50+
| `TURSO_SYNC_URL` | No | Remote sync URL for embedded replica mode. |
51+
| `TURSO_SYNC_INTERVAL` | No | Periodic sync interval in seconds (default: 60). |
4752

4853
4. Deploy:
4954

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

6065
- **`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.
61-
- **`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.
66+
- **`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.
6267
- **`scripts/build-vercel.sh`** — Builds all required workspace packages (foundation, drivers, plugins, protocols, examples) in the correct dependency order.
6368
- **`scripts/patch-symlinks.cjs`** — Replaces pnpm workspace symlinks with real copies so Vercel can bundle the function without symlink errors.
6469

@@ -110,5 +115,5 @@ apps/demo/
110115
111116
ObjectStack Kernel
112117
(ObjectQL + Auth +
113-
InMemoryDriver)
118+
TursoDriver / InMemoryDriver)
114119
```

apps/demo/api/[[...route]].ts

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
* Vercel Serverless Function — ObjectQL Demo Handler
33
*
44
* Bootstraps the ObjectStack kernel with ObjectQL plugins and the
5-
* project-tracker demo metadata, using @objectstack/driver-memory
6-
* for zero-config in-memory data.
5+
* project-tracker demo metadata, using @objectql/driver-turso when
6+
* TURSO_DATABASE_URL is set, or @objectstack/driver-memory as a
7+
* zero-config fallback.
78
*
89
* Uses `getRequestListener()` from `@hono/node-server` together with
910
* an `extractBody()` helper to handle Vercel's pre-buffered request
@@ -14,8 +15,10 @@
1415
* a fresh `Request` object prevents POST/PUT/PATCH requests (e.g.
1516
* login) from hanging indefinitely.
1617
*
17-
* Data lives in the function instance's memory and persists across
18-
* warm invocations (Vercel Fluid Compute) but resets on cold start.
18+
* When using Turso, data is persisted in the Turso cloud database.
19+
* When using InMemoryDriver, data lives in the function instance's
20+
* memory and persists across warm invocations (Vercel Fluid Compute)
21+
* but resets on cold start.
1922
*
2023
* Both Console (/) and Studio (/_studio/) UIs are served as static SPAs.
2124
*
@@ -30,6 +33,7 @@ import { ObjectKernel, DriverPlugin, AppPlugin, createDispatcherPlugin, createRe
3033
import { HonoHttpServer } from '@objectstack/plugin-hono-server';
3134
import { AuthPlugin } from '@objectstack/plugin-auth';
3235
import { InMemoryDriver } from '@objectstack/driver-memory';
36+
import { createTursoDriver, type TursoDriver } from '@objectql/driver-turso';
3337
import { ObjectQLPlugin } from '@objectstack/objectql';
3438
import { getRequestListener } from '@hono/node-server';
3539
import type { Hono } from 'hono';
@@ -278,10 +282,38 @@ async function bootstrap(): Promise<Hono> {
278282
await withTimeout(kernel.use(new ObjectQLPlugin()), PLUGIN_TIMEOUT_MS, 'ObjectQLPlugin');
279283
log('ObjectQLPlugin registered.');
280284

281-
// 2. In-memory data driver (no external DB required)
282-
log('Registering DriverPlugin (InMemoryDriver)…');
283-
await withTimeout(kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory')), PLUGIN_TIMEOUT_MS, 'DriverPlugin');
284-
log('DriverPlugin registered.');
285+
// 2. Data driver — Turso when TURSO_DATABASE_URL is set, InMemoryDriver otherwise
286+
const tursoUrl = process.env.TURSO_DATABASE_URL;
287+
let tursoDriver: TursoDriver | null = null;
288+
if (tursoUrl) {
289+
log(`Registering TursoDriver (${tursoUrl})…`);
290+
const syncUrl = process.env.TURSO_SYNC_URL;
291+
const rawSyncInterval = process.env.TURSO_SYNC_INTERVAL;
292+
const parsedSyncInterval =
293+
rawSyncInterval !== undefined ? Number(rawSyncInterval) : NaN;
294+
const syncIntervalSeconds = Number.isFinite(parsedSyncInterval)
295+
? parsedSyncInterval
296+
: 60;
297+
tursoDriver = createTursoDriver({
298+
url: tursoUrl,
299+
authToken: process.env.TURSO_AUTH_TOKEN,
300+
syncUrl,
301+
sync: syncUrl
302+
? {
303+
intervalSeconds: syncIntervalSeconds,
304+
onConnect: true,
305+
}
306+
: undefined,
307+
});
308+
// DriverPlugin from @objectstack/runtime accepts any driver; TursoDriver
309+
// implements @objectql/types Driver which is structurally compatible.
310+
await withTimeout(kernel.use(new DriverPlugin(tursoDriver, 'turso')), PLUGIN_TIMEOUT_MS, 'DriverPlugin-turso');
311+
log('TursoDriver registered.');
312+
} else {
313+
log('Registering DriverPlugin (InMemoryDriver)…');
314+
await withTimeout(kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory')), PLUGIN_TIMEOUT_MS, 'DriverPlugin-memory');
315+
log('InMemoryDriver registered.');
316+
}
285317

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

448-
// 12. Bootstrap kernel (init + start all plugins, fire kernel:ready)
480+
// 12. Connect Turso driver (if applicable) before kernel bootstrap
481+
if (tursoDriver) {
482+
log('Connecting TursoDriver…');
483+
await withTimeout(tursoDriver.connect(), PLUGIN_TIMEOUT_MS, 'TursoDriver.connect()');
484+
log('TursoDriver connected.');
485+
}
486+
487+
// 13. Bootstrap kernel (init + start all plugins, fire kernel:ready)
449488
log('Running kernel.bootstrap()…');
450489
await withTimeout(kernel.bootstrap(), KERNEL_BOOTSTRAP_TIMEOUT_MS, 'kernel.bootstrap()');
451490
log(`Bootstrap complete in ${elapsed()}.`);

apps/demo/objectstack.config.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/**
22
* ObjectQL Demo — Application Configuration
33
*
4-
* Minimal ObjectStack configuration for the demo application.
5-
* Uses in-memory driver with the project-tracker showcase example.
4+
* ObjectStack configuration for the demo application.
5+
* Uses @objectql/driver-turso when TURSO_DATABASE_URL is set,
6+
* falls back to MemoryDriver for zero-config local development.
67
*
78
* For local development: `pnpm dev` (uses @objectstack/cli)
89
* For Vercel deployment: configured via api/[[...route]].ts
@@ -29,10 +30,39 @@ import { FormulaPlugin } from '@objectql/plugin-formula';
2930
import { ObjectQLSecurityPlugin } from '@objectql/plugin-security';
3031
import { createApiRegistryPlugin } from '@objectstack/core';
3132
import { MemoryDriver } from '@objectql/driver-memory';
33+
import { createTursoDriver } from '@objectql/driver-turso';
3234
import { createAppPlugin } from '@objectql/platform-node';
3335

34-
// In-memory driver — zero-config, no external DB required.
35-
const defaultDriver = new MemoryDriver();
36+
// Choose driver based on environment — Turso when TURSO_DATABASE_URL is set,
37+
// MemoryDriver otherwise (zero-config fallback for quick starts).
38+
function createDefaultDriver() {
39+
const tursoUrl = process.env.TURSO_DATABASE_URL;
40+
if (tursoUrl) {
41+
console.log(`🗄️ Driver: Turso (${tursoUrl})`);
42+
const syncUrl = process.env.TURSO_SYNC_URL;
43+
const rawSyncInterval = process.env.TURSO_SYNC_INTERVAL;
44+
const parsedSyncInterval =
45+
rawSyncInterval !== undefined ? Number(rawSyncInterval) : NaN;
46+
const syncIntervalSeconds = Number.isFinite(parsedSyncInterval)
47+
? parsedSyncInterval
48+
: 60;
49+
return createTursoDriver({
50+
url: tursoUrl,
51+
authToken: process.env.TURSO_AUTH_TOKEN,
52+
syncUrl,
53+
sync: syncUrl
54+
? {
55+
intervalSeconds: syncIntervalSeconds,
56+
onConnect: true,
57+
}
58+
: undefined,
59+
});
60+
}
61+
console.log('🗄️ Driver: Memory (in-memory, non-persistent)');
62+
return new MemoryDriver();
63+
}
64+
65+
const defaultDriver = createDefaultDriver();
3666

3767
// Load the project-tracker showcase metadata.
3868
const projectTrackerPlugin = createAppPlugin({
@@ -51,13 +81,17 @@ export default {
5181
createApiRegistryPlugin(),
5282
new HonoServerPlugin({}),
5383
new ConsolePlugin(),
54-
// Register the driver as 'driver.default' service.
84+
// Register the active driver as 'driver.default' service so upstream
85+
// ObjectQLPlugin can discover it during start() phase.
5586
{
5687
name: 'driver-default',
5788
init: async (ctx: any) => {
5889
ctx.registerService('driver.default', defaultDriver);
5990
},
60-
start: async () => {},
91+
start: async () => {
92+
// Driver.connect() is optional in the interface; call if present
93+
await defaultDriver.connect?.();
94+
},
6195
},
6296
projectTrackerPlugin,
6397
new ObjectQLPlugin(),

apps/demo/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@
1212
},
1313
"devDependencies": {
1414
"@hono/node-server": "^1.19.11",
15+
"@libsql/client": "^0.17.2",
16+
"@libsql/core": "^0.17.2",
17+
"@libsql/hrana-client": "^0.9.0",
18+
"@libsql/isomorphic-ws": "^0.1.5",
19+
"@neon-rs/load": "^0.0.4",
1520
"@object-ui/console": "^3.1.3",
1621
"@objectql/core": "workspace:*",
1722
"@objectql/driver-memory": "workspace:*",
23+
"@objectql/driver-turso": "workspace:*",
1824
"@objectql/example-project-tracker": "workspace:*",
1925
"@objectql/platform-node": "workspace:*",
2026
"@objectql/plugin-formula": "workspace:*",
@@ -35,8 +41,16 @@
3541
"@objectstack/runtime": "^3.2.8",
3642
"@objectstack/studio": "^3.2.8",
3743
"@types/node": "^20.19.37",
44+
"detect-libc": "^2.0.2",
3845
"hono": "^4.12.8",
46+
"js-base64": "^3.7.8",
47+
"libsql": "^0.5.28",
48+
"nanoid": "^3.3.11",
49+
"promise-limit": "^2.7.0",
3950
"typescript": "^5.9.3",
51+
"ws": "^8.19.0",
52+
"cross-fetch": "^4.1.0",
53+
"node-fetch": "^2.7.0",
4054
"zod": "^4.3.6"
4155
},
4256
"engines": {

apps/demo/scripts/build-vercel.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pnpm --filter @objectql/platform-node build
3535

3636
echo "▸ Building drivers…"
3737
pnpm --filter @objectql/driver-memory \
38+
--filter @objectql/driver-turso \
3839
--filter @objectql/driver-sql \
3940
build
4041

apps/demo/vercel.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"api/**/*.ts": {
88
"memory": 1024,
99
"maxDuration": 60,
10-
"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}/**"
10+
"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}/**"
1111
}
1212
},
1313
"rewrites": [

pnpm-lock.yaml

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)