UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

143 lines (135 loc) 6.44 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DEFAULT_COMPRESSIBLE_TYPES = void 0; exports.compressBlob = compressBlob; exports.decompressBlob = decompressBlob; exports.isCompressibleType = isCompressibleType; exports.wrappedAttachmentsCompressionStorage = wrappedAttachmentsCompressionStorage; var _pluginHelpers = require("../../plugin-helpers.js"); var _index = require("../utils/index.js"); /** * Default MIME type patterns that benefit from compression. * Types like images (JPEG, PNG, WebP), videos, and audio * are already compressed and should NOT be re-compressed. */ var DEFAULT_COMPRESSIBLE_TYPES = exports.DEFAULT_COMPRESSIBLE_TYPES = ['text/*', 'application/json', 'application/xml', 'application/xhtml+xml', 'application/javascript', 'application/x-javascript', 'application/ecmascript', 'application/rss+xml', 'application/atom+xml', 'application/soap+xml', 'application/wasm', 'application/x-yaml', 'application/sql', 'application/graphql', 'application/ld+json', 'application/manifest+json', 'application/schema+json', 'application/vnd.api+json', 'image/svg+xml', 'image/bmp', 'font/ttf', 'font/otf', 'application/x-font-ttf', 'application/x-font-otf', 'application/pdf', 'application/rtf', 'application/x-sh', 'application/x-csh', 'application/x-httpd-php']; /** * Checks if a given MIME type should be compressed, * based on a list of type patterns. Supports wildcard suffix matching * (e.g., 'text/*' matches 'text/plain', 'text/html', etc.). * * Deterministic: same type + same pattern list = same answer. * No byte-level inspection needed. */ function isCompressibleType(mimeType, compressibleTypes) { var lower = mimeType.toLowerCase(); for (var pattern of compressibleTypes) { var lowerPattern = pattern.toLowerCase(); if (lowerPattern.endsWith('/*')) { // 'text/*' -> 'text/', so startsWith matches all subtypes var prefix = lowerPattern.slice(0, -1); if (lower.startsWith(prefix)) { return true; } } else if (lower === lowerPattern) { return true; } } return false; } /** * Compress a Blob using streaming CompressionStream API. * @link https://github.com/WICG/compression/blob/main/explainer.md */ async function compressBlob(mode, blob) { var stream = blob.stream().pipeThrough(new CompressionStream(mode)); return new Response(stream).blob(); } /** * Decompress a Blob using streaming DecompressionStream API. */ async function decompressBlob(mode, blob) { var stream = blob.stream().pipeThrough(new DecompressionStream(mode)); return new Response(stream).blob(); } /** * Digest prefix that marks an attachment as having been * stored with compression. Used by getAttachmentData to * determine whether decompression is needed, without * having to fetch the full document to inspect the MIME type. */ var COMPRESSED_DIGEST_PREFIX = 'c-'; /** * A RxStorage wrapper that compresses attachment data on writes * and decompresses the data on reads. * * Only compresses attachments whose MIME type is in the compressible list. * Already-compressed formats (JPEG, PNG, MP4, etc.) are passed through as-is. * * This is using the CompressionStream API, * @link https://caniuse.com/?search=compressionstream */ function wrappedAttachmentsCompressionStorage(args) { return Object.assign({}, args.storage, { async createStorageInstance(params) { if (!params.schema.attachments || !params.schema.attachments.compression) { return args.storage.createStorageInstance(params); } var mode = params.schema.attachments.compression; var compressibleTypes = params.schema.attachments.compressibleTypes || DEFAULT_COMPRESSIBLE_TYPES; async function modifyToStorage(docData) { await Promise.all(Object.values(docData._attachments).map(async attachment => { if (!attachment.data) { return; } var attachmentWriteData = attachment; if (isCompressibleType(attachmentWriteData.type, compressibleTypes)) { attachmentWriteData.data = await compressBlob(mode, attachmentWriteData.data); /** * Prefix the digest to signal that this attachment is compressed. * This is intentional: the stored digest is 'c-' + hash(originalData), * NOT hash(compressedData). RxDB does not use digests for integrity * verification — only for change detection (comparing before vs after). * Change detection remains correct because both sides of any comparison * consistently carry the 'c-' prefix for compressed attachments. * All write paths flow through this modifyToStorage wrapper so the * prefix is always applied, regardless of which API was used to write. */ if (!attachmentWriteData.digest.startsWith(COMPRESSED_DIGEST_PREFIX)) { attachmentWriteData.digest = COMPRESSED_DIGEST_PREFIX + attachmentWriteData.digest; } } })); return docData; } /** * Because this wrapper resolves the attachments.compression, * we have to remove it before sending it to the underlying RxStorage. * which allows underlying storages to detect wrong configurations * like when compression is set to false but no attachment-compression module is used. */ var childSchema = (0, _index.flatClone)(params.schema); childSchema.attachments = (0, _index.flatClone)(childSchema.attachments); delete (0, _index.ensureNotFalsy)(childSchema.attachments).compression; var instance = await args.storage.createStorageInstance(Object.assign({}, params, { schema: childSchema })); var wrappedInstance = (0, _pluginHelpers.wrapRxStorageInstance)(params.schema, instance, modifyToStorage, d => d); /** * Override getAttachmentData to decompress based on the * digest prefix rather than looking up the document's MIME type. */ wrappedInstance.getAttachmentData = async (documentId, attachmentId, digest) => { var data = await instance.getAttachmentData(documentId, attachmentId, digest); if (digest.startsWith(COMPRESSED_DIGEST_PREFIX)) { return decompressBlob(mode, data); } return data; }; return wrappedInstance; } }); } //# sourceMappingURL=index.js.map