UNPKG

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
// 修改后的 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; } }