176 lines
4.4 KiB
JavaScript
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,
|
|
};
|