Files
MoonTVPlus/src/lib/postgres-adapter.ts
2026-02-08 01:18:14 +08:00

152 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Vercel Postgres (Neon/Postgres) 适配器
*
* 将 Vercel Postgres API 转换为与 D1 兼容的接口
*
* 注意:此模块仅在服务端使用,通过 webpack 配置排除客户端打包
*/
import { sql } from '@vercel/postgres';
import { DatabaseAdapter, D1PreparedStatement, D1Result } from './d1-adapter';
/**
* Vercel Postgres 适配器
*
* 使用 @vercel/postgres 包装为 D1 兼容接口
*/
export class PostgresAdapter implements DatabaseAdapter {
private queryParams: { query: string; values: any[] } | null = null;
prepare(query: string): D1PreparedStatement {
return new PostgresPreparedStatement(query);
}
batch(statements: D1PreparedStatement[]): Promise<D1Result[]> {
// Postgres 使用事务模拟 batch
return new Promise((resolve, reject) => {
Promise.all(statements.map((stmt) => (stmt as PostgresPreparedStatement).execute()))
.then((results) => resolve(results))
.catch((err) => reject(err));
});
}
exec(query: string): void {
// Vercel Postgres 不支持直接 exec需要使用 sql 模板
throw new Error('exec() is not supported for Vercel Postgres. Use prepare() instead.');
}
}
/**
* Vercel Postgres PreparedStatement 包装器
* 将 Vercel Postgres API 转换为 D1 兼容 API
*/
class PostgresPreparedStatement implements D1PreparedStatement {
private params: any[] = [];
private paramIndex = 1;
constructor(private query: string) {}
bind(...values: any[]): D1PreparedStatement {
this.params = values;
return this;
}
/**
* 将 SQLite 风格的 ? 占位符替换为 Postgres 风格的 $1, $2, ...
*/
private convertQuery(query: string): string {
let index = 1;
return query.replace(/\?/g, () => `$${index++}`);
}
/**
* 将 SQL 查询中的表名和列名转换为双引号包裹Postgres 要求)
* 注意:需要排除已经有引号的内容
*/
private quoteIdentifiers(query: string): string {
// 这个方法主要用于处理列值,表名在 schema 中已经创建好
return query;
}
/**
* 执行查询并返回第一行
*/
async first<T = any>(colName?: string): Promise<T | null> {
try {
const convertedQuery = this.convertQuery(this.query);
// 使用 Vercel Postgres 的 query 方法执行参数化查询
const result = await sql.query(convertedQuery, this.params);
if (!result || result.rows.length === 0) return null;
const row = result.rows[0];
if (colName) return row[colName] ?? null;
return row as T;
} catch (err) {
console.error('Postgres first() error:', err);
return null;
}
}
/**
* 执行查询并返回结果
*/
async run<T = any>(): Promise<D1Result<T>> {
try {
const convertedQuery = this.convertQuery(this.query);
const result = await sql.query(convertedQuery, this.params);
return {
success: true,
meta: {
changes: result.rowCount || 0,
last_row_id: null, // Postgres 不直接返回 lastInsertId
},
results: result.rows,
};
} catch (err: any) {
console.error('Postgres run() error:', err);
return {
success: false,
error: err.message,
};
}
}
/**
* 执行查询并返回所有行
*/
async all<T = any>(): Promise<D1Result<T>> {
try {
const convertedQuery = this.convertQuery(this.query);
const result = await sql.query(convertedQuery, this.params);
return {
success: true,
results: result.rows || [],
};
} catch (err: any) {
console.error('Postgres all() error:', err);
return {
success: false,
error: err.message,
results: [],
};
}
}
/**
* 内部执行方法(用于 batch 操作)
*/
async execute(): Promise<D1Result> {
return this.run();
}
}