UNPKG

test-rxdb

Version:

A local realtime NoSQL Database for JavaScript applications -

239 lines (214 loc) 8.34 kB
/** * this plugin adds the keycompression-capabilities to rxdb * if you don't use this, ensure that you set disableKeyCompression to false in your schema */ import { createCompressionTable, CompressionTable, JsonSchema as KeyCompressionJsonSchema, compressObject, decompressObject, compressedPath, DEFAULT_COMPRESSION_FLAG, createCompressedJsonSchema, compressQuery } from 'jsonschema-key-compression'; import { overwritable } from '../../overwritable.ts'; import { wrapRxStorageInstance } from '../../plugin-helpers.ts'; import { getPrimaryFieldOfPrimaryKey } from '../../rx-schema-helper.ts'; import { flatCloneDocWithMeta } from '../../rx-storage-helper.ts'; import type { RxJsonSchema, CompositePrimaryKey, RxStorage, RxStorageInstanceCreationParams, RxDocumentData, FilledMangoQuery, PreparedQuery, RxDocumentWriteData } from '../../types/index.d.ts'; import { clone, flatClone, getFromMapOrCreate, isMaybeReadonlyArray } from '../../plugins/utils/index.ts'; import { prepareQuery } from '../../rx-query.ts'; declare type CompressionState = { table: CompressionTable; schema: RxJsonSchema<any>; compressedSchema: RxJsonSchema<any>; }; /** * Cache the compression table and the compressed schema * by the storage instance for better performance. */ const COMPRESSION_STATE_BY_SCHEMA: WeakMap< RxJsonSchema<any>, CompressionState > = new WeakMap(); export function getCompressionStateByRxJsonSchema( schema: RxJsonSchema<any> ): CompressionState { /** * Because we cache the state by the JsonSchema, * it must be assured that the given schema object * is never mutated. */ overwritable.deepFreezeWhenDevMode(schema); return getFromMapOrCreate( COMPRESSION_STATE_BY_SCHEMA, schema, () => { const compressionSchema: KeyCompressionJsonSchema = flatClone(schema) as any; delete (compressionSchema as any).primaryKey; const table = createCompressionTable( compressionSchema, DEFAULT_COMPRESSION_FLAG, [ /** * Do not compress the primary field * for easier debugging. */ getPrimaryFieldOfPrimaryKey(schema.primaryKey), '_rev', '_attachments', '_deleted', '_meta' ] ); delete (compressionSchema as any).primaryKey; const compressedSchema: RxJsonSchema<any> = createCompressedJsonSchema( table, compressionSchema ) as RxJsonSchema<any>; // also compress primary key if (typeof schema.primaryKey !== 'string') { const composedPrimary: CompositePrimaryKey<any> = schema.primaryKey; const newComposedPrimary: CompositePrimaryKey<any> = { key: compressedPath(table, composedPrimary.key as string), fields: composedPrimary.fields.map(field => compressedPath(table, field as string)), separator: composedPrimary.separator }; compressedSchema.primaryKey = newComposedPrimary; } else { compressedSchema.primaryKey = compressedPath(table, schema.primaryKey); } /** * the key compression module does not know about indexes * in the schema, so we have to also compress them here. */ if (schema.indexes) { const newIndexes = schema.indexes.map(idx => { if (isMaybeReadonlyArray(idx)) { return idx.map(subIdx => compressedPath(table, subIdx)); } else { return compressedPath(table, idx); } }); compressedSchema.indexes = newIndexes; } const compressionState = { table, schema, compressedSchema }; return compressionState; } ); } export function wrappedKeyCompressionStorage<Internals, InstanceCreationOptions>( args: { storage: RxStorage<Internals, InstanceCreationOptions>; } ): RxStorage<Internals, InstanceCreationOptions> { return Object.assign( {}, args.storage, { async createStorageInstance<RxDocType>( params: RxStorageInstanceCreationParams<RxDocType, any> ) { if (!params.schema.keyCompression) { return args.storage.createStorageInstance(params); } const compressionState = getCompressionStateByRxJsonSchema(params.schema); function modifyToStorage(docData: RxDocumentWriteData<RxDocType>) { const ret = compressDocumentData(compressionState, docData); return ret; } function modifyFromStorage(docData: RxDocumentData<any>): Promise<RxDocumentData<RxDocType>> { return decompressDocumentData(compressionState, docData); } /** * Because this wrapper resolves the key-compression, * we can set the flag to false * which allows underlying storages to detect wrong configurations * like when keyCompression is set to false but no key-compression module is used. */ const childSchema = flatClone(compressionState.compressedSchema); childSchema.keyCompression = false; const instance = await args.storage.createStorageInstance( Object.assign( {}, params, { schema: childSchema } ) ); const wrappedInstance = wrapRxStorageInstance( params.schema, instance, modifyToStorage, modifyFromStorage ); const overwriteMethods = ['query', 'count'] as const; overwriteMethods.forEach(methodName => { const methodBefore = wrappedInstance[methodName].bind(wrappedInstance); (wrappedInstance as any)[methodName] = async (preparedQuery: PreparedQuery<RxDocType>) => { const compressedQuery: FilledMangoQuery<RxDocType> = compressQuery( compressionState.table, preparedQuery.query as any ) as any; const compressedPreparedQuery = prepareQuery( compressionState.compressedSchema, compressedQuery ); return methodBefore(compressedPreparedQuery); } }); return wrappedInstance; } } ); } export function compressDocumentData( compressionState: CompressionState, docData: RxDocumentData<any> ): RxDocumentData<any> { /** * Do not send attachments to compressObject() * because it will deep clone which does not work on Blob or Buffer. */ docData = flatCloneDocWithMeta(docData); const attachments = docData._attachments; delete docData._attachments; docData = compressObject( compressionState.table, docData ); docData._attachments = attachments; return docData; } export function decompressDocumentData( compressionState: CompressionState, docData: RxDocumentData<any> ): RxDocumentData<any> { return decompressObject( compressionState.table, docData ); }