sgammon/GUST

View on GitHub
js/backend/init.ts

Summary

Maintainability
A
0 mins
Test Coverage
/*
 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
 *
 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
 * this code in object or source form requires and implies consent and agreement to that license in principle and
 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
 * is strictly forbidden except in adherence with assigned license requirements.
 */
import {Express} from 'express';
import express from '@feathersjs/express';
import feathers from '@feathersjs/feathers';

import {Logging} from 'gust/js/backend/logging';


/**
 * Main Feathers-powered application, which is used to initialize ExpressJS with middleware, controllers, services, and
 * so on. This app is eventually served from `app.ts`.
 */
const app: feathers.Application = feathers();

/** Express-style application, provided pre-wrapped around the main FeathersJS app. */
const expressApp: Express = express(app);


/**
 * Command type for a given buffered command.
 */
enum CommandType {
    /** Generic prep callback type. */
    GENERIC
}


/**
 * Describes the signature expected for an application init callback, which accepts the FeathersJS application being
 * initialized, and is not expected to return anything.
 */
type AppInitCallback = (app: feathers.Application, express: Express) => Promise<any> | undefined | void;

/**
 * Holds properties and a callback method that, together, specify a step that must be taken to initialize a backend app
 * running atop FeatherJS/Express.
 */
class QueuedAppInit {
    /** Type of command enqueued in this entry. */
    private readonly type: CommandType;

    /** Callback to execute the enqueued command. */
    private readonly callback: AppInitCallback;

    /**
     * Construct a new app init step, with the provided command type and callback, which should execute the requisite
     * code against the provided app parameters.
     *
     * @param {!CommandType} type Type of command being executed.
     * @param {!AppInitCallback} callback Callback function to dispatch when ready.
     */
    constructor(type: CommandType,
                callback: AppInitCallback) {
        this.type = type;
        this.callback = callback;
    }

    /**
     * Execute the attached callback, against the provided app objects.
     *
     * @param {!feathers.Application} app FeathersJS application.
     * @param {!Express} express ExpressJS-style application.
     */
    async execute(app: feathers.Application, express: Express) {
        try {
            const promise = this.callback(app, express);
            if (!!promise) {
                await promise;
                return;
            }
        } catch (err) {
            if (err instanceof Error) {
                throw new AppInitError(err.message, err);
            } else {
                throw new AppInitError("Unknown.", err);
            }
        }
    }
}

/**
 * Local command queue, used when buffering deferred calls that initialize or otherwise configure a backend application
 * running on NodeJS.
 */
const _queue: QueuedAppInit[] = [];


/**
 * Specialized exception class, thrown when a provided init callback fails or throws an error in some way, preventing
 * app startup and halting the process.
 */
export class AppInitError extends Error {
    private readonly context: any;

    constructor(message: string, context?: any) {
        super(`A fatal app init error occurred: "${message}'.`);
        this.context = context;
    }

    /**
     * Report the error to the console.
     */
    public report() {
        Logging.error(
            `An error occurred during app init: "${this.message}".`,
            this.context);
    }
}


/**
 * Enqueue a function to prepare an application, built atop FeathersJS or ExpressJS. The provided callback should accept
 * two parameters: `app` (the FeathersJS app), and `express` (the same app, but with an Express-compliant interface).
 *
 * @param {!AppInitCallback} config Function to dispatch.
 */
export function prepare(config: AppInitCallback) {
    _queue.push(new QueuedAppInit(CommandType.GENERIC, config));
}


/**
 * Boot the application, by executing any queued installation closures which mount stuff to `app`. Once these closures
 * are executed, the app can "boot," and it is returned to the calling function so it has a chance to do so.
 *
 * @return {!Express} Express-wrapped application.
 * @throws {AppInitError} If a deferred app init function fails or otherwise errors.
 */
export async function boot(): Promise<Express> {
    if (_queue.length < 1)
        throw new AppInitError("No tasks for bootstrap.");

    // perform init steps
    while (_queue.length) {
        let command = _queue.pop();
        if (!!command)
            await command.execute(app, expressApp);
        else
            break;
    }
    return expressApp;
}