Source: instance-creator.ts

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

/**
 * This module creates the initial {@link Instance} and {@link Person} (for the instance owner)
 * objects.
 * @module
 */

/**
 * @typedef {object} InstanceUpdateOptions
 * @property {string} name - The name given to the instance is part of the instance ID
 * @property {string} email - The uniquely identifying email address of the instance owner
 * @property {string} [ownerName] - The name of the owner (optional)
 * @property {KeyPair} [personEncryptionKeyPair] - Encryption keypair used for instance owner.
 * If provided, **all four keys** must be provided together. In that case no new keys will be
 * created upon instance creation, instead these keys will be used. The keys are only used for a
 * new instance. If they are provided but an instance already exists nothing will happen.
 * @property {SignKeyPair} [personSignKeyPair] - Sign keypair used for instance owner.
 * Also see the description for `personEncryptionKeyPair`.
 * @property {KeyPair} [instanceEncryptionKeyPair] - Encryption keypair used for instance.
 * Also see the description for `personEncryptionKeyPair`.
 * @property {SignKeyPair} [instanceSignKeyPair] - Sign keypair used for instance.
 * Also see the description for `personEncryptionKeyPair`.
 * @property {string} secret - A secret known to the owner. It is used to derive a key to
 * encrypt the private keys created (only!) during initial instance creation. The string will be
 * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize|normalized}.
 * @property {Recipe[]} [recipes=[]]
 * @property {Map<OneObjectTypeNames,undefined|string[]>} [enabledReverseMapTypes=new Map()]
 * @property {Map<OneObjectTypeNames,undefined|string[]>}
 * [enabledReverseMapTypesForIdObjects=new Map()]
 */
export interface InstanceCreatorOptions {
    name: InstanceOptions['name'];
    email: string;
    ownerName?: string;
    personEncryptionKeyPair?: KeyPair;
    personSignKeyPair?: SignKeyPair;
    instanceEncryptionKeyPair?: KeyPair;
    instanceSignKeyPair?: SignKeyPair;
    initialRecipes: InstanceOptions['initialRecipes'];
    initiallyEnabledReverseMapTypes: InstanceOptions['initiallyEnabledReverseMapTypes'];
    initiallyEnabledReverseMapTypesForIdObjects: InstanceOptions['initiallyEnabledReverseMapTypesForIdObjects'];
}

import {addRecipesToRuntimeAndStorage} from './crdt-recipes';
import type {KeyPair} from './crypto/encryption';
import type {SignKeyPair} from './crypto/sign';

import {createError} from './errors';
import type {InstanceOptions} from './instance';
import {createDefaultKeysIfNotExist} from './keychain/keychain';
import type {Instance} from './recipes';
import type {VersionedObjectResult} from './storage-versioned-objects';
import {storeVersionedObject} from './storage-versioned-objects';

/**
 * Use this module through {@link instance.module:ts.registerRecipes|instance.registerRecipes} for
 * your convenience. It provides the `name` and `owner` from the active instance
 * and registers any given {@link Recipe|Recipes} with the currently running instance, which
 * this function does not do since you could create or update an inactive instance.
 * Note that recipes are *not* added to the runtime, since you might be updating an inactive
 * Instance object.
 * @static
 * @async
 * @param {InstanceUpdaterOptions} options
 * @returns {Promise<ObjectCreation[]>} Returns the result of creating the Instance object and,
 * if provided Recipe and Module objects. The Instance object creation result always is in the
 * first position and always exists. The {@link Person} object creation result is in second
 * place, but it only exists if the Person object had to be created. {@link Recipe} and
 * {@link Module} creation results only exist if any recipes or modules were provided.
 */
export async function createInstance({
    name,
    email,
    ownerName,
    personEncryptionKeyPair,
    personSignKeyPair,
    instanceEncryptionKeyPair,
    instanceSignKeyPair,
    initialRecipes = [],
    initiallyEnabledReverseMapTypes = new Map(),
    initiallyEnabledReverseMapTypesForIdObjects = new Map()
}: InstanceCreatorOptions): Promise<VersionedObjectResult<Instance>> {
    if (
        // If any key is provided...
        (personEncryptionKeyPair !== undefined || personSignKeyPair !== undefined) &&
        // ... then *all* must be provided.
        (personEncryptionKeyPair === undefined || personSignKeyPair === undefined)
    ) {
        throw createError('INCR-WPK2', {
            iName: name,
            encType: typeof personEncryptionKeyPair,
            sigType: typeof personSignKeyPair
        });
    }

    if (
        // If any key is provided...
        (instanceEncryptionKeyPair !== undefined || instanceSignKeyPair !== undefined) &&
        // ... then *all* must be provided.
        (instanceEncryptionKeyPair === undefined || instanceSignKeyPair === undefined)
    ) {
        throw createError('INCR-WPK3', {
            iName: name,
            encType: typeof instanceEncryptionKeyPair,
            sigType: typeof instanceSignKeyPair
        });
    }

    // ----------------------------------------------------
    // PERSON (instance owner)
    // ----------------------------------------------------

    const owner = await storeVersionedObject({
        $type$: 'Person',
        email,
        name: ownerName
    });

    await createDefaultKeysIfNotExist(owner.idHash, personEncryptionKeyPair, personSignKeyPair);

    // ----------------------------------------------------
    // INSTANCE
    // ----------------------------------------------------
    const recipeResults = await addRecipesToRuntimeAndStorage(initialRecipes);

    const instance = await storeVersionedObject({
        $type$: 'Instance',
        name,
        owner: owner.idHash,
        recipe: recipeResults.map(r => r.hash),
        module: [],
        enabledReverseMapTypes: initiallyEnabledReverseMapTypes,
        enabledReverseMapTypesForIdObjects: initiallyEnabledReverseMapTypesForIdObjects
    } as Instance);

    await createDefaultKeysIfNotExist(
        instance.idHash,
        instanceEncryptionKeyPair,
        instanceSignKeyPair
    );

    return instance;
}