delta-sync
Version:
A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.
225 lines (224 loc) • 8.34 kB
JavaScript
// 修改后的 RestAdapter.ts 文件
export class RestAdapter {
constructor(options) {
this.baseUrl = options.baseUrl.endsWith('/')
? options.baseUrl.slice(0, -1)
: options.baseUrl;
this.headers = {
'Content-Type': 'application/json',
...options.headers
};
if (options.apiKey) {
this.headers['Authorization'] = `Bearer ${options.apiKey}`;
}
}
async initSync() {
try {
const response = await fetch(`${this.baseUrl}/health`, {
method: 'GET',
headers: this.headers
});
if (!response.ok) {
throw new Error(`REST服务不可用: ${response.status} ${response.statusText}`);
}
}
catch (error) {
console.error('初始化REST适配器失败:', error);
throw new Error(`无法连接到REST服务: ${error instanceof Error ? error.message : String(error)}`);
}
}
async isAvailable() {
try {
const response = await fetch(`${this.baseUrl}/health`, {
method: 'GET',
headers: this.headers
});
return response.ok;
}
catch (error) {
return false;
}
}
async read(storeName, options) {
const params = new URLSearchParams();
if (options?.limit)
params.append('limit', options.limit.toString());
if (options?.offset)
params.append('offset', options.offset.toString());
if (options?.since)
params.append('since', options.since.toString());
const url = `${this.baseUrl}/stores/${storeName}?${params.toString()}`;
try {
const response = await fetch(url, {
method: 'GET',
headers: this.headers
});
if (!response.ok) {
throw new Error(`读取数据失败: ${response.status} ${response.statusText}`);
}
const result = await response.json();
return {
items: result.items || [],
hasMore: result.hasMore || false
};
}
catch (error) {
console.error(`从存储 ${storeName} 读取数据失败:`, error);
throw error;
}
}
async readBulk(storeName, ids) {
if (!ids.length)
return [];
try {
const response = await fetch(`${this.baseUrl}/stores/${storeName}/bulk`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ ids })
});
if (!response.ok) {
throw new Error(`批量读取数据失败: ${response.status} ${response.statusText}`);
}
const result = await response.json();
return result.items || [];
}
catch (error) {
console.error(`从存储 ${storeName} 批量读取数据失败:`, error);
throw error;
}
}
async putBulk(storeName, items) {
if (!items.length)
return [];
try {
const response = await fetch(`${this.baseUrl}/stores/${storeName}`, {
method: 'PUT',
headers: this.headers,
body: JSON.stringify({ items })
});
if (!response.ok) {
throw new Error(`批量写入数据失败: ${response.status} ${response.statusText}`);
}
const result = await response.json();
return result.items || [];
}
catch (error) {
console.error(`向存储 ${storeName} 批量写入数据失败:`, error);
throw error;
}
}
async deleteBulk(storeName, ids) {
if (!ids.length)
return;
try {
const response = await fetch(`${this.baseUrl}/stores/${storeName}/delete`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ ids })
});
if (!response.ok) {
throw new Error(`批量删除数据失败: ${response.status} ${response.statusText}`);
}
}
catch (error) {
console.error(`从存储 ${storeName} 批量删除数据失败:`, error);
throw error;
}
}
// 文件操作 API - 修复后的版本
async readFile(fileId) {
try {
console.log(`正在读取文件: ${fileId}`);
const response = await fetch(`${this.baseUrl}/files/${fileId}`, {
method: 'GET',
headers: {
...this.headers,
'Accept': '*/*',
'Content-Type': 'application/json' // 确保Content-Type正确
}
});
if (!response.ok) {
throw new Error(`读取文件失败: ${response.status} ${response.statusText}`);
}
return await response.blob();
}
catch (error) {
console.error(`读取文件 ${fileId} 失败:`, error);
throw error;
}
}
async saveFile(content, fileId) {
try {
console.log(`正在上传文件: ${fileId}`);
const formData = new FormData();
// 处理不同类型的内容
let blob;
if (typeof content === 'string') {
if (content.includes(';base64,')) {
// 处理 base64 编码的数据
const [metaPart, base64Data] = content.split(';base64,');
const mimeType = metaPart.replace('data:', '') || 'application/octet-stream';
const byteCharacters = atob(base64Data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
blob = new Blob([byteArray], { type: mimeType });
}
else {
// 纯文本
blob = new Blob([content], { type: 'text/plain' });
}
}
else if (content instanceof ArrayBuffer) {
blob = new Blob([content], { type: 'application/octet-stream' });
}
else {
// 已经是 Blob 类型
blob = content;
}
// 重要:添加文件到表单,不指定文件名
formData.append('file', blob);
formData.append('fileId', fileId);
const headers = { ...this.headers };
delete headers['Content-Type']; // 让浏览器自动设置正确的 Content-Type
const response = await fetch(`${this.baseUrl}/files`, {
method: 'POST',
headers: headers,
body: formData
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`上传文件失败: ${response.status} ${response.statusText} - ${errorText}`);
}
const result = await response.json();
// 确保返回的附件信息使用我们指定的 ID
return {
...result,
id: fileId
};
}
catch (error) {
console.error(`上传文件 ${fileId} 失败:`, error);
throw error;
}
}
async deleteFile(fileId) {
try {
console.log(`正在删除文件: ${fileId}`);
const response = await fetch(`${this.baseUrl}/files/${fileId}`, {
method: 'DELETE',
headers: this.headers
});
if (!response.ok) {
throw new Error(`删除文件失败: ${response.status} ${response.statusText}`);
}
console.log(`文件 ${fileId} 删除成功`);
}
catch (error) {
console.error(`删除文件 ${fileId} 失败:`, error);
throw error;
}
}
}