Skip to content

Commit 91cc129

Browse files
committed
feat: add Turso environment database adapter and update .env.example and .gitignore
1 parent 5b9c025 commit 91cc129

3 files changed

Lines changed: 91 additions & 1 deletion

File tree

apps/server/.env.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# Turso Database Configuration
1+
# Turso Database Configuration (control-plane DB)
22
# Required for Vercel deployment
33
TURSO_DATABASE_URL=libsql://your-database.turso.io
44
TURSO_AUTH_TOKEN=your-auth-token-here
5+
6+
# Turso Platform API (environment provisioning)
7+
# When set, new environments are provisioned as real Turso cloud databases.
8+
# When unset, each environment gets a local SQLite file under .objectstack/data/environments/.
9+
TURSO_ORG_NAME=your-org-slug
10+
TURSO_API_TOKEN=your-platform-api-token

apps/server/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ node_modules/
1919
# OS files
2020
.DS_Store
2121
Thumbs.db
22+
.vercel
23+
.env*.local

packages/services/service-tenant/src/environment-provisioning.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
ProvisionOrganizationResponse,
1313
} from '@objectstack/spec/cloud';
1414
import { ProvisionEnvironmentRequestSchema, ProvisionOrganizationRequestSchema } from '@objectstack/spec/cloud';
15+
import { TursoPlatformClient } from './turso-platform-client.js';
1516

1617
/**
1718
* Backend-agnostic physical DB provisioning adapter.
@@ -63,6 +64,66 @@ export class NoopSecretEncryptor implements SecretEncryptor {
6364
}
6465
}
6566

67+
/**
68+
* Turso Platform adapter — calls the Turso Platform API to provision a new
69+
* cloud database for each environment, then mints a per-database auth token.
70+
*
71+
* Required env vars: `TURSO_ORG_NAME`, `TURSO_API_TOKEN`.
72+
*/
73+
export class TursoEnvironmentDatabaseAdapter implements EnvironmentDatabaseAdapter {
74+
readonly driver: DatabaseDriver = 'turso';
75+
76+
private readonly client: TursoPlatformClient;
77+
78+
constructor(config: { apiToken: string; organization: string; apiBaseUrl?: string }) {
79+
this.client = new TursoPlatformClient(config);
80+
}
81+
82+
async createDatabase(params: {
83+
environmentId: string;
84+
databaseName: string;
85+
region: string;
86+
storageLimitMb: number;
87+
}): Promise<{ databaseUrl: string; plaintextSecret: string }> {
88+
await this.client.createDatabase({ name: params.databaseName });
89+
const { jwt } = await this.client.createDatabaseToken(params.databaseName, {
90+
authorization: 'full-access',
91+
});
92+
const db = await this.client.getDatabase(params.databaseName);
93+
return {
94+
databaseUrl: `libsql://${db.Hostname}`,
95+
plaintextSecret: jwt,
96+
};
97+
}
98+
}
99+
100+
/**
101+
* Local SQLite adapter for development environments. Creates one `.db` file
102+
* per environment under `baseDir`, named after the stable `databaseName`
103+
* (e.g. `env-{uuid}.db`) so the file survives slug renames.
104+
*/
105+
export class LocalSQLiteEnvironmentDatabaseAdapter implements EnvironmentDatabaseAdapter {
106+
readonly driver: DatabaseDriver = 'sqlite';
107+
108+
constructor(private readonly baseDir: string = '.objectstack/data/environments') {}
109+
110+
async createDatabase(params: {
111+
environmentId: string;
112+
databaseName: string;
113+
region: string;
114+
storageLimitMb: number;
115+
}): Promise<{ databaseUrl: string; plaintextSecret: string }> {
116+
const { mkdirSync } = await import('node:fs');
117+
const { resolve } = await import('node:path');
118+
const dbPath = resolve(this.baseDir, `${params.databaseName}.db`);
119+
mkdirSync(this.baseDir, { recursive: true });
120+
return {
121+
databaseUrl: `file:${dbPath}`,
122+
plaintextSecret: '',
123+
};
124+
}
125+
}
126+
66127
/**
67128
* Mock adapter used by dev/test environments when no real provider is
68129
* configured. Returns stable synthetic URLs / tokens.
@@ -421,3 +482,24 @@ export class EnvironmentProvisioningService {
421482
}
422483
}
423484
}
485+
486+
/**
487+
* Build the default adapter list from environment variables.
488+
*
489+
* - `TURSO_ORG_NAME` + `TURSO_API_TOKEN` present → `TursoEnvironmentDatabaseAdapter`
490+
* (provisions real cloud databases via the Turso Platform API).
491+
* - Otherwise → `LocalSQLiteEnvironmentDatabaseAdapter`
492+
* (creates one `.db` file per environment under `.objectstack/data/environments/`).
493+
*/
494+
export function createDefaultEnvironmentAdapters(
495+
env: Record<string, string | undefined> = process.env,
496+
): EnvironmentDatabaseAdapter[] {
497+
const orgName = env.TURSO_ORG_NAME;
498+
const apiToken = env.TURSO_API_TOKEN;
499+
500+
if (orgName && apiToken) {
501+
return [new TursoEnvironmentDatabaseAdapter({ organization: orgName, apiToken })];
502+
}
503+
504+
return [new LocalSQLiteEnvironmentDatabaseAdapter()];
505+
}

0 commit comments

Comments
 (0)