Source: util/hashes-from-id.ts

/**
 * @author Michael Hasenstein <hasenstein@yahoo.com>
 * @copyright REFINIO GmbH 2022
 * @license CC-BY-NC-SA-2.5; portions MIT License
 * @version 0.0.1
 */

/**
 * Helper function(s) to use the version-maps to get the hashes for ID hashes.
 * @module
 */

import type {HashAndIdHashAndTimestamp} from '../object-graph-bottom-up-iterator';
import {getAllVersionMapEntries, getNthVersionMapEntry} from '../version-map-query';
import {flat} from './function';
import type {SHA256IdHash} from './type-checks';

/**
 * An ID hash stands for 1...n concrete objects. When iterating over the graph we need to
 * transform ID object links to their individual members. This function finds the member objects
 * behind the given ID object hashes by querying the version map for each ID hash.
 *
 * An ID object also disrupts a fundamental property of our hash-link based directed graphs:
 *
 * Normally it is not possible to write a new linked object after the linking parent object has
 * been written. While you can calculate hashes manually and not write an object to storage this
 * is highly discouraged. Since you need to have all the data at that point in any case it makes
 * no sense not to write the object right away, before you store the parent linking to it, since
 * you could end up with an inconsistent or broken object graph for no gain at all (again, you
 * already have all the data, or you could not calculate the hash of the microdata string).
 *
 * ID object links however make it possible to link to *future objects*, new versions belonging
 * to the ID object.
 *
 * Normally we can therefore assume the creation timestamp of the root object is the latest
 * date-time of any object in the graph. However, when we encounter an ID object link this
 * assumption may break since it is only true for versions of the linked ID object written prior
 * to the linking parent object. That is why when we encounter an ID link we start a new
 * iteration with a new `createdAt` timestamp of what now is the new root of the graph hanging
 * on the concrete version. We do this for all versions of the ID object.
 *
 * @private
 * @param {SHA256IdHash[]} idHashes
 * @returns {Promise<HashAndIdHashAndTimestamp[]>} Returns an object with hash, ID hash
 * and object creation timestamp. There is one hash for one ID hash if `idObjectsLatestOnly ===
 * true`, otherwise there may be many hashes for each ID hash.
 */
export async function getLatestHashesForIdHashes(
    idHashes: readonly SHA256IdHash[]
): Promise<HashAndIdHashAndTimestamp[]> {
    // 1:1 mapping per ID Reference (only latest version is used)
    return await Promise.all(
        idHashes.map(async idHash => {
            const entry = await getNthVersionMapEntry(idHash);
            return {
                hash: entry.hash,
                idHash,
                timestamp: entry.timestamp
            };
        })
    );
}

/**
 * See {@link getLatestHashesForIdHashes}
 * @private
 * @param {SHA256IdHash[]} idHashes
 * @returns {Promise<HashAndIdHashAndTimestamp[]>} Returns an object with hash, ID hash
 * and object creation timestamp. There is one hash for one ID hash if `idObjectsLatestOnly ===
 * true`, otherwise there may be many hashes for each ID hash.
 */
export async function getAllHashesForIdHashes(
    idHashes: readonly SHA256IdHash[]
): Promise<HashAndIdHashAndTimestamp[]> {
    // 1:n mapping per ID Reference (all versions are used)
    return flat(
        await Promise.all(
            idHashes.map(async idHash => {
                const entries = await getAllVersionMapEntries(idHash);
                return entries.map(entry => ({
                    hash: entry.hash,
                    idHash,
                    timestamp: entry.timestamp
                }));
            })
        )
    );
}