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
+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 };
}
}