UNPKG

rxdb

Version:

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

150 lines (135 loc) 4.49 kB
import { BehaviorSubject } from 'rxjs'; import { INTERNAL_CONTEXT_COLLECTION, getPrimaryKeyOfInternalDocument } from '../../rx-database-internal-store.ts'; import { getPreviousVersions } from '../../rx-schema.ts'; import type { InternalStoreCollectionDocType, RxCollection, RxDatabase, RxDocumentData } from '../../types/index.d.ts'; import { PROMISE_RESOLVE_FALSE, PROMISE_RESOLVE_NULL, clone, flatClone, getFromMapOrCreate, toPromise } from '../utils/index.ts'; import { RxMigrationState } from './rx-migration-state.ts'; export async function getOldCollectionMeta( migrationState: RxMigrationState ): Promise<RxDocumentData<InternalStoreCollectionDocType>> { const collectionDocKeys = getPreviousVersions(migrationState.collection.schema.jsonSchema) .map(version => migrationState.collection.name + '-' + version); const found = await migrationState.database.internalStore.findDocumentsById( collectionDocKeys.map(key => getPrimaryKeyOfInternalDocument( key, INTERNAL_CONTEXT_COLLECTION )), false ); if (found.length > 1) { throw new Error('more than one old collection meta found'); } return found[0]; } /** * runs the doc-data through all following migrationStrategies * so it will match the newest schema. * @throws Error if final doc does not match final schema or migrationStrategy crashes * @return final object or null if migrationStrategy deleted it */ export function migrateDocumentData( collection: RxCollection, docSchemaVersion: number, docData: any ): Promise<any | null> { /** * We cannot deep-clone Blob or Buffer * so we just flat clone it here * and attach it to the deep cloned document data. */ const attachmentsBefore = flatClone(docData._attachments); const mutateableDocData = clone(docData); const meta = mutateableDocData._meta; delete mutateableDocData._meta; mutateableDocData._attachments = attachmentsBefore; let nextVersion = docSchemaVersion + 1; // run the document through migrationStrategies let currentPromise = Promise.resolve(mutateableDocData); while (nextVersion <= collection.schema.version) { const version = nextVersion; currentPromise = currentPromise.then(docOrNull => runStrategyIfNotNull( collection, version, docOrNull )); nextVersion++; } return currentPromise.then(doc => { if (doc === null) { return PROMISE_RESOLVE_NULL; } doc._meta = meta; return doc; }); } export function runStrategyIfNotNull( collection: RxCollection, version: number, docOrNull: any | null ): Promise<any | null> { if (docOrNull === null) { return PROMISE_RESOLVE_NULL; } else { const ret = collection.migrationStrategies[version](docOrNull, collection); const retPromise = toPromise(ret); return retPromise; } } /** * returns true if a migration is needed */ export async function mustMigrate( migrationState: RxMigrationState ): Promise<boolean> { if (migrationState.collection.schema.version === 0) { return PROMISE_RESOLVE_FALSE; } const oldColDoc = await getOldCollectionMeta(migrationState); return !!oldColDoc; } export const MIGRATION_DEFAULT_BATCH_SIZE = 200; export type MigrationStateWithCollection = { collection: RxCollection; migrationState: RxMigrationState; }; export const DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE = new WeakMap<RxDatabase, BehaviorSubject<RxMigrationState[]>>(); export function addMigrationStateToDatabase( migrationState: RxMigrationState ) { const allSubject = getMigrationStateByDatabase(migrationState.database); const allList = allSubject.getValue().slice(0); allList.push(migrationState); allSubject.next(allList); } export function getMigrationStateByDatabase(database: RxDatabase): BehaviorSubject<RxMigrationState[]> { return getFromMapOrCreate( DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE, database, () => new BehaviorSubject<RxMigrationState[]>([]) ); } /** * Complete on database destroy * so people do not have to unsubscribe */ export function onDatabaseDestroy(database: RxDatabase) { const subject = DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE.get(database); if (subject) { subject.complete(); } }