test-rxdb
Version:
A local realtime NoSQL Database for JavaScript applications -
239 lines (214 loc) • 8.34 kB
text/typescript
/**
* 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
);
}