feat: add Turso (libSQL) storage support for EdgeOne deployment

- Add TursoAdapter implementing DatabaseAdapter interface using @libsql/client
- Add 'turso' storage type to db.ts storage selector (reuses D1Storage)
- Add init-turso.js script for database migration and admin initialization
- Add @libsql/client dependency to package.json
- Update next.config.js to exclude @libsql/client from client-side bundle
- Update edgeone-deploy.yml workflow with TURSO_URL and TURSO_TOKEN secrets
- Update edgeone-build.mjs runtime env keys to include Turso variables
- Update .env.edgeone.example with Turso deployment instructions
- Update README.md with Turso as recommended storage for EdgeOne

Turso provides free SQLite-compatible cloud database (libSQL) with:
- 500 databases, 9GB storage, 1B row reads/month on free tier
- HTTP API compatible with edge/serverless environments
- Full SQLite syntax compatibility, reuses existing migrations
This commit is contained in:
mtvpls-turso
2026-07-11 17:04:27 +00:00
parent f55a1f50de
commit 8e464c463b
10 changed files with 625 additions and 27 deletions
+34 -1
View File
@@ -17,7 +17,7 @@ import {
SkipConfig,
} from './types';
// storage type 常量: 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | 'd1' | 'postgres',默认 'localstorage'
// storage type 常量: 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | 'd1' | 'postgres' | 'turso',默认 'localstorage'
const IS_CLOUDFLARE_BUILD =
process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare';
const STORAGE_TYPE =
@@ -28,6 +28,7 @@ const STORAGE_TYPE =
| 'kvrocks'
| 'd1'
| 'postgres'
| 'turso'
| undefined) || 'localstorage';
// 创建存储实例
@@ -70,6 +71,15 @@ function createStorage(): IStorage {
// 动态导入 PostgresStorage 以避免客户端打包
const { PostgresStorage } = require('./postgres.db');
return new PostgresStorage(postgresAdapter);
case 'turso':
// TursoStorage 只能在服务端使用,客户端会报错
if (typeof window !== 'undefined') {
throw new Error('TursoStorage can only be used on the server side');
}
const tursoAdapter = getTursoAdapter();
// 复用 D1StorageTurso 基于 libSQL/SQLiteSQL 语法完全兼容)
const { D1Storage: TursoD1Storage } = require('./d1.db');
return new TursoD1Storage(tursoAdapter);
case 'localstorage':
default:
return null as unknown as IStorage;
@@ -89,6 +99,29 @@ function getPostgresAdapter(): any {
return new PostgresAdapter();
}
/**
* 获取 Turso 适配器
* 使用 @libsql/client 连接 Turso (libSQL) 远程数据库
* 适用于 EdgeOne Pages 等无内置数据库的边缘平台
*/
function getTursoAdapter(): any {
// 动态导入适配器以避免客户端打包
const { TursoAdapter } = require('./turso-adapter');
const tursoUrl = process.env.TURSO_URL;
const tursoToken = process.env.TURSO_TOKEN;
if (!tursoUrl || !tursoToken) {
throw new Error(
'TURSO_URL and TURSO_TOKEN env variables must be set for Turso storage'
);
}
console.log('Using Turso (libSQL) database');
return new TursoAdapter(tursoUrl, tursoToken);
}
/**
* 获取 D1 适配器
* 开发环境:使用 better-sqlite3
+159
View File
@@ -0,0 +1,159 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Turso (libSQL) 适配器
*
* 将 @libsql/client API 转换为与 D1 兼容的接口
* Turso 基于 libSQLSQLite 开源分支),SQL 语法与 D1/SQLite 完全兼容
*
* 适用于 EdgeOne Pages 等无内置数据库的边缘平台
*
* 注意:此模块仅在服务端使用,通过 webpack 配置排除客户端打包
*/
import { createClient, type Client } from '@libsql/client';
import { DatabaseAdapter, D1PreparedStatement, D1Result } from './d1-adapter';
/**
* Turso 适配器
*
* 使用 @libsql/client 包装为 D1 兼容接口
*/
export class TursoAdapter implements DatabaseAdapter {
private client: Client;
constructor(url: string, authToken: string) {
this.client = createClient({
url,
authToken,
});
}
prepare(query: string): D1PreparedStatement {
return new TursoPreparedStatement(this.client, query);
}
async batch(statements: D1PreparedStatement[]): Promise<D1Result[]> {
// Turso/libSQL 原生支持 batch
const libsqlStatements = statements.map(
(stmt) => (stmt as TursoPreparedStatement).toLibSQLBatch()
);
const results = await this.client.batch(libsqlStatements, 'write');
return results.map((result) => ({
success: true,
results: result.rows || [],
meta: {
changes: result.rowsAffected,
last_row_id:
result.lastInsertRowid !== undefined
? Number(result.lastInsertRowid)
: null,
},
}));
}
async exec(query: string): Promise<void> {
await this.client.executeMultiple(query);
}
}
/**
* Turso PreparedStatement 包装器
* 将 @libsql/client API 转换为 D1 兼容 API
*/
class TursoPreparedStatement implements D1PreparedStatement {
private params: any[] = [];
constructor(
private client: Client,
private query: string
) {}
bind(...values: any[]): D1PreparedStatement {
this.params = values;
return this;
}
/**
* 执行查询并返回第一行
*/
async first<T = any>(colName?: string): Promise<T | null> {
try {
const result = await this.client.execute({
sql: this.query,
args: this.params,
});
if (!result.rows || result.rows.length === 0) return null;
const row = result.rows[0];
if (colName) return (row as any)[colName] ?? null;
return row as T;
} catch (err) {
console.error('Turso first() error:', err);
return null;
}
}
/**
* 执行查询并返回结果
*/
async run<T = any>(): Promise<D1Result<T>> {
try {
const result = await this.client.execute({
sql: this.query,
args: this.params,
});
return {
success: true,
meta: {
changes: result.rowsAffected,
last_row_id:
result.lastInsertRowid !== undefined
? Number(result.lastInsertRowid)
: null,
},
results: result.rows as T[],
};
} catch (err: any) {
console.error('Turso run() error:', err);
return {
success: false,
error: err.message,
};
}
}
/**
* 执行查询并返回所有行
*/
async all<T = any>(): Promise<D1Result<T>> {
try {
const result = await this.client.execute({
sql: this.query,
args: this.params,
});
return {
success: true,
results: (result.rows || []) as T[],
};
} catch (err: any) {
console.error('Turso all() error:', err);
return {
success: false,
error: err.message,
results: [],
};
}
}
/**
* 转换为 libSQL batch 格式
*/
toLibSQLBatch(): { sql: string; args: any[] } {
return { sql: this.query, args: this.params };
}
}