npm package: @objectstack/driver-turso
Description: Turso/libSQL Driver for ObjectStack — Edge-first SQLite with embedded replicas and multi-tenancy
Latest version: 4.0.2

# @objectstack/driver-turso

Turso/libSQL driver for ObjectStack — edge-first SQLite with embedded replicas, cloud-only remote mode, and database-per-tenant multi-tenancy.

## Architecture

`TursoDriver` implements a **dual-transport architecture**:

- **Local/Replica modes:** Extends `SqlDriver` from `@objectstack/driver-sql`. All CRUD, schema, filtering, aggregation, window functions, introspection, and transactions are **inherited** via Knex + better-sqlite3.
- **Remote mode:** Delegates all operations to `RemoteTransport` which uses `@libsql/client` SDK directly (HTTP/WebSocket). No local SQLite or Knex dependency needed.

```
TursoDriver extends SqlDriver (dual transport)
├── Transport: local/replica (via Knex + better-sqlite3)
│   ├── Inherited: find, findOne, create, update, delete, count, upsert
│   ├── Inherited: bulkCreate, bulkUpdate, bulkDelete, updateMany, deleteMany
│   ├── Inherited: syncSchema, dropTable, introspectSchema
│   ├── Inherited: aggregate, distinct, findWithWindowFunctions
│   ├── Inherited: beginTransaction, commit, rollback
│   └── Inherited: applyFilters (MongoDB-style + array-style)
├── Transport: remote (via @libsql/client)
│   ├── RemoteTransport: find, findOne, create, update, delete, count, upsert
│   ├── RemoteTransport: bulkCreate, bulkUpdate, bulkDelete, updateMany, deleteMany
│   ├── RemoteTransport: syncSchema, dropTable
│   ├── RemoteTransport: beginTransaction, commit, rollback
│   └── RemoteTransport: execute (raw SQL)
├── Override:  name, version, supports (Turso-specific capabilities)
├── Override:  connect / disconnect (transport-aware lifecycle)
├── Added:     transportMode ('local' | 'replica' | 'remote')
├── Added:     sync() — Embedded replica sync via @libsql/client
├── Added:     Multi-tenant router with TTL cache
└── Added:     TursoDriverConfig (url, authToken, syncUrl, mode, client)
```

## Installation

```bash
pnpm add @objectstack/driver-turso
```

## Connection Modes

### Local File (Embedded SQLite)

```typescript
import { TursoDriver } from '@objectstack/driver-turso';

const driver = new TursoDriver({
  url: 'file:./data/app.db',
});
await driver.connect();
```

### In-Memory (Testing)

```typescript
const driver = new TursoDriver({
  url: ':memory:',
});
await driver.connect();
```

### Embedded Replica (Hybrid)

Local SQLite file + automatic sync from Turso cloud:

```typescript
const driver = new TursoDriver({
  url: 'file:./data/replica.db',
  syncUrl: 'libsql://my-db-orgname.turso.io',
  authToken: process.env.TURSO_AUTH_TOKEN,
  sync: {
    intervalSeconds: 60, // sync every 60 seconds
    onConnect: true,     // sync on initial connect
  },
});
await driver.connect();

// Manual sync
await driver.sync();
```

### Remote (Cloud-Only)

Pure remote queries via `@libsql/client` — no local SQLite needed.
Ideal for Vercel, Cloudflare Workers, and other serverless/edge runtimes:

```typescript
const driver = new TursoDriver({
  url: 'libsql://my-db-orgname.turso.io',
  authToken: process.env.TURSO_AUTH_TOKEN,
});
await driver.connect();

// All CRUD operations work the same as local mode
const users = await driver.find('users', { where: { active: true } });
```

### Auto-Detection

Transport mode is automatically detected from the URL:

| URL Pattern | Mode | Engine |
|:---|:---|:---|
| `file:./data/app.db` | `local` | Knex + better-sqlite3 |
| `:memory:` | `local` | Knex + better-sqlite3 |
| `file:...` + `syncUrl` | `replica` | Knex + @libsql/client sync |
| `libsql://...` | `remote` | @libsql/client only |
| `https://...` | `remote` | @libsql/client only |

You can also force a specific mode:

```typescript
const driver = new TursoDriver({
  url: 'libsql://my-db.turso.io',
  authToken: process.env.TURSO_AUTH_TOKEN,
  mode: 'remote', // Force remote mode
});
```

### Custom Client

Pass a pre-configured `@libsql/client` instance for advanced use cases
(custom caching, connection pooling, testing):

```typescript
import { createClient } from '@libsql/client';

const client = createClient({
  url: 'libsql://my-db.turso.io',
  authToken: process.env.TURSO_AUTH_TOKEN,
});

const driver = new TursoDriver({
  url: 'libsql://my-db.turso.io',
  client, // Inject pre-configured client
});
await driver.connect();
```

## Multi-Tenant Routing

Database-per-tenant architecture with automatic driver caching:

```typescript
import { createMultiTenantRouter } from '@objectstack/driver-turso';

const router = createMultiTenantRouter({
  urlTemplate: 'file:./data/{tenant}.db',
  clientCacheTTL: 300_000, // 5 minutes
  onTenantCreate: async (tenantId) => {
    console.log(`Provisioned database for tenant: ${tenantId}`);
  },
});

// In a request handler:
const driver = await router.getDriverForTenant('acme');
const users = await driver.find('users', { where: { active: true } });

// Cleanup on shutdown
await router.destroyAll();
```

### Multi-Tenant with Embedded Replicas

Both `urlTemplate` and `driverConfigOverrides.syncUrl` support `{tenant}` placeholder interpolation:

```typescript
const router = createMultiTenantRouter({
  urlTemplate: 'file:./data/{tenant}-replica.db',
  groupAuthToken: process.env.TURSO_GROUP_TOKEN,
  driverConfigOverrides: {
    syncUrl: 'libsql://{tenant}-myorg.turso.io',
    sync: { intervalSeconds: 30 },
  },
});
```

### Concurrency Safety

Concurrent `getDriverForTenant()` calls for the same tenant are deduplicated — only one driver is created, and all callers share the same instance.

## Configuration

```typescript
interface TursoDriverConfig {
  /**
   * Database URL.
   * - file:./data/local.db → local mode
   * - :memory: → local mode (ephemeral)
   * - libsql://my-db.turso.io → remote mode
   * - https://my-db.turso.io → remote mode
   */
  url: string;

  /** JWT auth token for the remote Turso database */
  authToken?: string;

  /**
   * AES-256 encryption key for local database file.
   * Only effective in local/replica modes.
   */
  encryptionKey?: string;

  /**
   * Maximum concurrent requests to the remote database.
   * Effective in replica and remote modes.
   * Default: 20
   */
  concurrency?: number;

  /** Remote sync URL for embedded replica mode (libsql:// or https://) */
  syncUrl?: string;

  /** Sync configuration (requires syncUrl) */
  sync?: {
    intervalSeconds?: number; // Default: 60
    onConnect?: boolean;      // Default: true
  };

  /**
   * Operation timeout in milliseconds for remote operations.
   * Effective in replica and remote modes.
   */
  timeout?: number;

  /**
   * Force a specific transport mode.
   * If not set, mode is auto-detected from the URL.
   */
  mode?: 'local' | 'replica' | 'remote';

  /**
   * Pre-configured @libsql/client instance.
   * Useful for custom caching, connection pooling, or testing.
   */
  client?: Client;
}
```

## Capabilities

TursoDriver declares enhanced capabilities beyond the base SqlDriver:

| Capability | SqlDriver | TursoDriver (local) | TursoDriver (remote) |
|:---|:---:|:---:|:---:|
| FTS5 Full-Text Search | ❌ | ✅ | ✅ |
| JSON1 Query | ❌ | ✅ | ✅ |
| Common Table Expressions | ❌ | ✅ | ✅ |
| Savepoints | ❌ | ✅ | ✅ |
| Indexes | ❌ | ✅ | ✅ |
| Connection Pooling | ✅ | ❌ (concurrency limits) | ❌ |
| Embedded Replica Sync | — | ✅ | — |
| Multi-Tenant Routing | — | ✅ | ✅ |
| Serverless/Edge | — | — | ✅ |

## Plugin Registration

```typescript
import tursoPlugin from '@objectstack/driver-turso';

// Via plugin system
await kernel.enablePlugin(tursoPlugin, {
  url: 'file:./data/app.db',
});
```

## Testing

```bash
pnpm test        # Run all tests
```

Tests run against in-memory SQLite (`:memory:`) — no external services required.

## License

Apache-2.0 — Copyright (c) 2025 ObjectStack