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:
+34
-1
@@ -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();
|
||||
// 复用 D1Storage(Turso 基于 libSQL/SQLite,SQL 语法完全兼容)
|
||||
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
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
* Turso (libSQL) 适配器
|
||||
*
|
||||
* 将 @libsql/client API 转换为与 D1 兼容的接口
|
||||
* Turso 基于 libSQL(SQLite 开源分支),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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user