persist method

Future<void> persist(
  1. PersistPayload payload
)

Implementation

Future<void> persist(PersistPayload payload) {
  return _syncLock.run(() async {
    final localResolver = payload.resolver;
    final docs = payload.persistenceDocs;

    Set<DualDataStore> dataStores = {};
    Map<String, List<DualDataStore>> pathDataStores = {};

    // Pre-calculate and hydrate all resolved file data stores relevant to the updated documents.
    for (final doc in docs) {
      final docPath = doc.path;
      final docData = doc.data;

      if (docData == null) {
        final stores = pathDataStores[docPath] = _resolveDataStores(docPath);
        dataStores.addAll(stores);
      } else {
        final prevDataStoreName = resolver.getNearest(docPath)!.$2;
        final nextDataStoreName = localResolver.getNearest(docPath)!.$2;
        final prevDataStore = index[prevDataStoreName] ??= DualDataStore(
          prevDataStoreName,
          factory: factory,
          encrypter: encrypter,
        );
        final nextDataStore = index[nextDataStoreName] ??= DualDataStore(
          nextDataStoreName,
          factory: factory,
          encrypter: encrypter,
        );

        dataStores.add(prevDataStore);
        dataStores.add(nextDataStore);
      }
    }

    await Future.wait(dataStores.map((dataStore) => dataStore.hydrate()));

    for (final doc in docs) {
      final docPath = doc.path;
      final docData = doc.data;
      final encrypted = doc.encrypted;

      // If the document has been deleted, then clear its data recursively from each of its
      // resolved data stores.
      if (docData == null) {
        resolver.deletePath(docPath);
        for (final dataStore in pathDataStores[docPath]!) {
          dataStore.recursiveDelete(docPath);
        }
        // Otherwise, write its associated data store with the updated document data.
      } else {
        final prevResult = resolver.getNearest(docPath)!;
        final prevResolverPath = prevResult.$1;
        final prevResolverValue = prevResult.$2;

        final nextResult = localResolver.getNearest(docPath)!;
        final nextResolverPath = nextResult.$1;
        final nextResolverValue = nextResult.$2;

        final prevDataStoreName = prevResolverValue;
        final prevDataStore = index[prevDataStoreName]!;

        final dataStoreName = nextResolverValue;
        final dataStore = index[dataStoreName]!;

        resolver.writePath(nextResolverPath, nextResolverValue);

        // Scenario 1: A document's resolver value changes and its path stays the same.
        //
        // When a document is written with the same resolver path a__b__c and updated resolver value 2 from 1,
        // then store 1 grafts *all* its data under resolver path a__b__c into store 2 at resolver path a__b__c.
        if (nextResolverValue != prevResolverValue &&
            nextResolverPath == prevResolverPath) {
          dataStore.graft(
            nextResolverPath,
            prevResolverPath,
            null,
            prevDataStore,
          );
        }
        // Scenario 2: A document's resolver path changes from a shorter path to a longer path.
        //
        // When a document is updated from previous resolver path a__b__c to descendant resolver path a__b__c__d,
        // then data under path a__b__c__d in resolver path a__b__c of the previous store should be grafted into resolver path a__b__c__d of the next store.
        //
        if (nextResolverPath.length > prevResolverPath.length) {
          dataStore.graft(
            nextResolverPath,
            prevResolverPath,
            nextResolverPath,
            prevDataStore,
          );
        }
        // Scenario 3: A document's resolver path changes from a longer path to a shorter path.
        //
        // When a document is updated from previous resolver path a__b__c__d to resolver path a__b__c,
        // then *all* data in resolver path a__b__c__d of the previous store should be grafted into the next store with resolver path a__b__c.
        if (nextResolverPath.length < prevResolverPath.length) {
          resolver.deletePath(prevResolverPath, recursive: false);
          dataStore.graft(
            nextResolverPath,
            prevResolverPath,
            null,
            prevDataStore,
          );
        }

        dataStore.writePath(
          nextResolverPath,
          docPath,
          docData,
          encrypted,
        );
      }
    }

    _scheduleSync();
  });
}