Files
2026-04-20 01:17:00 +07:00

176 lines
4.4 KiB
JavaScript

/**
* Локальное хранилище объектов (SQLite). Сохранены имена экспортов readS3TextObject / writeS3JsonObject и т.д. для совместимости с роутами.
* Справочники EvoBGP не хранятся здесь — см. evobgpClient.js
*/
const { openDatabase } = require('../db/sqliteDb');
const blobStorage = require('./blobStorage');
openDatabase();
const BUCKET_NAME = process.env.SQLITE_BUCKET_LABEL || 'sqlite';
const cache = {
text: new Map(),
head: new Map(),
max: 100,
ttlMs: 30_000,
};
async function streamToString(stream) {
if (!stream) return '';
if (typeof stream.transformToString === 'function') {
return await stream.transformToString();
}
return await new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(String(c))));
stream.once('error', reject);
stream.once('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
});
}
function getCache(map, key) {
const v = map.get(key);
if (!v) return null;
if (Date.now() > v.at + cache.ttlMs) {
map.delete(key);
return null;
}
return v.value;
}
function setCache(map, key, value) {
if (map.size >= cache.max) {
const firstKey = map.keys().next().value;
if (firstKey) map.delete(firstKey);
}
map.set(key, { value, at: Date.now() });
}
function invalidateCacheForKey(key) {
try {
cache.text.delete(key);
} catch (_) {}
try {
cache.head.delete(key);
} catch (_) {}
}
async function readS3TextObject(key) {
const cached = getCache(cache.text, key);
if (cached) return cached;
const row = blobStorage.readBlob(key);
if (!row) {
const err = new Error('NoSuchKey');
err.code = 'NoSuchKey';
throw err;
}
const out = {
body: row.body,
etag: row.etag,
lastModified: row.lastModified,
contentLength: row.contentLength,
};
setCache(cache.text, key, out);
return out;
}
async function headS3ObjectEtag(key) {
const cached = getCache(cache.head, key);
if (cached && cached.etag) return cached.etag;
const etag = blobStorage.headEtag(key);
if (etag) setCache(cache.head, key, { etag });
return etag;
}
async function headMeta(key) {
return blobStorage.headMetaRow(key);
}
async function writeS3TextObject(key, content, contentType = 'text/plain') {
const db = openDatabase();
blobStorage.writeBlobTx(db, key, content, contentType);
invalidateCacheForKey(key);
return headMeta(key);
}
async function writeS3JsonObject(key, data) {
return writeS3TextObject(key, JSON.stringify(data, null, 2), 'application/json');
}
async function deleteS3Object(key) {
blobStorage.deleteBlob(key);
invalidateCacheForKey(key);
}
async function listS3Objects(prefix, { maxKeys = 100 } = {}) {
return blobStorage.listByPrefix(prefix, maxKeys);
}
async function streamPaginatedText({ key, mapLine, q, offset = 0, limit = 0 }) {
let text = '';
try {
const row = await readS3TextObject(key);
text = row.body || '';
} catch (e) {
if (e?.code === 'NoSuchKey') {
return { items: [], total: 0 };
}
throw e;
}
const lines = text.split('\n');
let total = 0;
const items = [];
const qstr = q ? String(q).toLowerCase() : '';
const matchesQuery = (line) => !qstr || line.toLowerCase().includes(qstr);
for (let i = 0; i < lines.length; i++) {
const line = String(lines[i] || '').trim();
if (!line) continue;
if (!matchesQuery(line)) continue;
total++;
const pos = total - 1;
if (limit > 0) {
if (pos >= offset && items.length < limit) items.push(mapLine(line));
} else {
items.push(mapLine(line));
}
}
return { items, total };
}
function listBlobVersionsForKey(key, maxKeys = 50) {
return blobStorage.listVersions(key, maxKeys);
}
function rollbackBlobVersion(key, versionId) {
openDatabase();
return blobStorage.rollbackToVersionId(key, versionId);
}
/** Заглушка: прямой вызов S3 SDK больше не используется. */
const s3 = {
send() {
throw new Error('S3 SDK removed: use readS3TextObject / writeS3TextObject or evobgpClient');
},
};
module.exports = {
s3,
BUCKET_NAME,
streamToString,
readS3TextObject,
headS3ObjectEtag,
headMeta,
writeS3TextObject,
writeS3JsonObject,
deleteS3Object,
streamPaginatedText,
invalidateCacheForKey,
listS3Objects,
listBlobVersionsForKey,
rollbackBlobVersion,
};