/
Starolat
/
DeepDive
Обзор
Документация
Войти
/
Starolat
/
DeepDive
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/services/ZstdCompressionService.js
191 строка
6 KB
Starolat Sergei
Перевод сохранения .dddb на JSZip DEFLATE (.dddb.zip) и legacy-чтение .zst
29 июн 2026, 13:20
29 июн 2026, 13:20
6efc64d
Код
Авторство
О чём код?
// @ts-check /** * @fileoverview ZstdCompressionService — распаковка legacy zstd через нативные API. * @module ZstdCompressionService * @version 1.1.0 * * Legacy сервис: используется только для чтения старых `.dddb.zst` файлов. * Сжатие больше не используется в UI; новые файлы сохраняются как `.dddb.zip` через ZipCompressionService. * Целевая среда: Chromium с поддержкой DecompressionStream('zstd'). * Fallback для Node.js-тестов: zlib.zstdDecompressSync (Node.js 24+). * Fallback для браузера: fzstd для декомпрессии (pure-JS). */ /** * @typedef {Object} ZstdCapabilities * @property {boolean} compress - поддерживается ли сжатие * @property {boolean} decompress - поддерживается ли распаковка */ const ZSTD_MAGIC_MASK = 0xfffffff0; const ZSTD_MAGIC_VALUE = 0x28b52ff0; class ZstdCompressionService { constructor() { /** @type {ZstdCapabilities|null} */ this._capabilities = null; } /** * Возвращает возможности текущей среды относительно zstd. * @returns {ZstdCapabilities} */ getCapabilities() { if (this._capabilities) return this._capabilities; const caps = { compress: false, decompress: false }; if (typeof window === "undefined") { // Node.js (тесты запускаются на Node.js 24+ с zlib zstd) caps.compress = true; caps.decompress = true; } else { // Browser try { // eslint-disable-next-line no-new new CompressionStream("zstd"); caps.compress = true; } catch { caps.compress = false; } try { // eslint-disable-next-line no-new new DecompressionStream("zstd"); caps.decompress = true; } catch { caps.decompress = false; } if (!caps.decompress) { const fzstd = (typeof window !== "undefined" && window.fzstd) || (typeof globalThis !== "undefined" && globalThis.fzstd); caps.decompress = typeof fzstd?.decompress === "function"; } } this._capabilities = caps; return caps; } /** * Проверяет, поддерживается ли zstd в текущей среде. * @returns {boolean} */ isSupported() { const caps = this.getCapabilities(); return caps.compress && caps.decompress; } /** * Сжимает Uint8Array через zstd. * @param {Uint8Array} data * @param {number} [level=3] - игнорируется в браузере (CompressionStream не expose level) * @returns {Promise<Uint8Array>} */ async compress(data, level = 3) { if (typeof window === "undefined") { return this._compressNode(data, level); } return this._compressBrowser(data); } /** * Распаковывает zstd-сжатые данные. * @param {Uint8Array} data * @returns {Promise<Uint8Array>} */ async decompress(data) { if (typeof window === "undefined") { return this._decompressNode(data); } return this._decompressBrowser(data); } /** * @private * @param {Uint8Array} data * @param {number} level * @returns {Promise<Uint8Array>} */ async _compressNode(data, level) { const zlib = await import("zlib"); const compressed = zlib.zstdCompressSync( Buffer.from(data.buffer, data.byteOffset, data.byteLength), level, ); return new Uint8Array( compressed.buffer, compressed.byteOffset, compressed.byteLength, ); } /** * @private * @param {Uint8Array} data * @returns {Promise<Uint8Array>} */ async _decompressNode(data) { const zlib = await import("zlib"); const decompressed = zlib.zstdDecompressSync( Buffer.from(data.buffer, data.byteOffset, data.byteLength), ); return new Uint8Array( decompressed.buffer, decompressed.byteOffset, decompressed.byteLength, ); } /** * @private * @param {Uint8Array} data * @returns {Promise<Uint8Array>} */ async _compressBrowser(data) { const cs = new CompressionStream("zstd"); const source = new ReadableStream({ start(controller) { controller.enqueue(data); controller.close(); }, }); const response = new Response(source.pipeThrough(cs)); const blob = await response.blob(); const arrayBuffer = await blob.arrayBuffer(); return new Uint8Array(arrayBuffer); } /** * @private * @param {Uint8Array} data * @returns {Promise<Uint8Array>} */ async _decompressBrowser(data) { if (this.getCapabilities().decompress) { const ds = new DecompressionStream("zstd"); const source = new ReadableStream({ start(controller) { controller.enqueue(data); controller.close(); }, }); const response = new Response(source.pipeThrough(ds)); const blob = await response.blob(); const arrayBuffer = await blob.arrayBuffer(); return new Uint8Array(arrayBuffer); } // Fallback to pure-JS fzstd const fzstd = (typeof window !== "undefined" && window.fzstd) || (typeof globalThis !== "undefined" && globalThis.fzstd); if (typeof fzstd?.decompress !== "function") { throw new Error("zstd decompression is not supported in this environment"); } return fzstd.decompress(data); } } const zstdCompressionService = new ZstdCompressionService(); export default zstdCompressionService;