delta-sync
Version:
A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.
283 lines (282 loc) • 10.7 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
// 文件操作 API - 依次读取文件
async readFiles(fileIds) {
const result = new Map();
// 依次处理每个文件ID,而不是并行处理
for (const fileId of fileIds) {
try {
const response = await fetch(`${this.baseUrl}/files/${fileId}`, {
method: 'GET',
headers: {
...this.headers,
'Accept': '*/*'
}
});
if (response.ok) {
result.set(fileId, await response.blob());
}
else {
console.warn(`读取文件失败: ${fileId}, 状态码: ${response.status}`);
result.set(fileId, null);
}
}
catch (error) {
console.error(`读取文件 ${fileId} 失败:`, error);
result.set(fileId, null);
}
}
return result;
}
// 依次上传文件
// 依次上传文件
async saveFiles(files) {
const attachments = [];
// 依次处理每个文件,而不是并行处理
for (let i = 0; i < files.length; i++) {
const { content, fileId } = files[i];
try {
let blob;
if (typeof content === 'string') {
if (content.includes(';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 j = 0; j < byteCharacters.length; j++) {
byteNumbers[j] = byteCharacters.charCodeAt(j);
}
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 = content;
}
// 处理文件名和扩展名
let fileName = fileId;
let extension = '';
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex !== -1) {
extension = fileName.substring(lastDotIndex);
fileName = fileName.substring(0, lastDotIndex);
}
const formData = new FormData();
formData.append('file', blob);
formData.append('fileId', fileId);
formData.append('fileName', fileName + extension);
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 result = await response.json();
attachments.push({
...result,
id: fileId,
filename: fileName + extension // 确保返回的附件包含文件名
});
}
else {
throw new Error(`上传失败: ${response.status}`);
}
}
catch (error) {
console.error(`上传文件 ${fileId} 失败:`, error);
// 创建带错误信息的附件
attachments.push({
id: fileId,
filename: fileId, // 使用fileId作为文件名
mimeType: '',
size: 0,
createdAt: Date.now(),
updatedAt: Date.now(),
metadata: { error: error instanceof Error ? error.message : String(error) }
});
}
}
return attachments;
}
async count(storeName) {
try {
const response = await fetch(`${this.baseUrl}/stores/${storeName}/count`, {
method: 'GET',
headers: this.headers
});
if (!response.ok) {
throw new Error(`获取记录数量失败: ${response.status} ${response.statusText}`);
}
const result = await response.json();
return result.count || 0;
}
catch (error) {
console.error(`获取存储 ${storeName} 的记录数量失败:`, error);
throw error;
}
}
// 依次删除文件
async deleteFiles(fileIds) {
const result = {
deleted: [],
failed: []
};
// 依次处理每个文件ID,而不是并行处理
for (const fileId of fileIds) {
try {
const response = await fetch(`${this.baseUrl}/files/${fileId}`, {
method: 'DELETE',
headers: this.headers
});
if (response.ok) {
result.deleted.push(fileId);
}
else {
console.warn(`删除文件失败: ${fileId}, 状态码: ${response.status}`);
result.failed.push(fileId);
}
}
catch (error) {
console.error(`删除文件 ${fileId} 失败:`, error);
result.failed.push(fileId);
}
// 添加小延迟,避免服务器过载
await new Promise(resolve => setTimeout(resolve, 50));
}
return result;
}
}