moleculer-api
Version:
A dynamic API Gateway for MoleculerJS which updates REST endpoints and aggregated GraphQL schema, access control policy for each action calls from metadata of remote services schema without restart or deployment.
170 lines • 8.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServerApplication = void 0;
const tslib_1 = require("tslib");
const async_lock_1 = tslib_1.__importDefault(require("async-lock"));
const interface_1 = require("../../interface");
const component_1 = require("./component");
const context_1 = require("./context");
class ServerApplication {
constructor(props, opts) {
this.props = props;
this.componentBranchHandlerMap = new Map();
this.componentsAliasedVersions = new Map();
this.staticRoutes = [];
/* handler update */
this.lock = new async_lock_1.default({ maxPending: 1000, timeout: 1000 });
// create application components
this.components = Object.entries(component_1.ServerApplicationComponentConstructors).reduce((components, [compKey, Component]) => {
const key = compKey;
return components.concat(new Component({
logger: this.props.logger.getChild(`${key}`),
}, opts && opts[key]));
}, []);
// create branch handler map for each components
for (const component of this.components) {
this.componentBranchHandlerMap.set(component, new Map());
this.componentsAliasedVersions.set(component, []);
}
}
// modules map of each application components which are passed to protocols
get componentModules() {
return this.components.reduce((obj, component) => {
obj[component.key] = component.module;
return obj;
}, {});
}
/* lifecycle */
start() {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
for (const component of this.components) {
yield component.start();
}
this.props.logger.info(`gateway server application has been started: ${this.components.join(", ")}`);
});
}
stop() {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
for (const component of this.components) {
yield component.stop();
}
this.componentBranchHandlerMap.clear();
this.props.logger.info(`gateway server application has been stopped: ${this.components.join(", ")}`);
});
}
mountBranchHandler(branch) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
yield Promise.all(this.components.map((component) => tslib_1.__awaiter(this, void 0, void 0, function* () {
return this.lock.acquire(component.key, () => {
// get version handler map
const branchHandlerMap = this.componentBranchHandlerMap.get(component);
const versionHandlerMap = branchHandlerMap.get(branch) || new Map();
// get aliases versions
const aliasedVersions = this.componentsAliasedVersions.get(component);
// unmount old versions
const newVersions = branch.versions; // latest -> oldest versions
for (const [version, routeHandlerMap] of versionHandlerMap.entries()) {
// clear forgotten version or prev-latest version
if (!newVersions.includes(version) || aliasedVersions.includes(version)) {
component.unmountRoutes(routeHandlerMap);
versionHandlerMap.delete(version);
}
}
// prepare context creation
const createContext = context_1.APIRequestContext.createConstructor(this.props.contextFactories, {
before(source) {
branch.touch();
// console.log("create context with ", source);
},
});
// mount new versions
for (const version of newVersions) {
if (!versionHandlerMap.has(version)) {
// prepare mount path
const { branchPathPrefix, versionPathPrefix } = component_1.Route;
const pathPrefixes = [`/${branchPathPrefix}${branch.name}${versionPathPrefix}${version.shortHash}`]; // ~dev@abcd1234
if (branch.latestVersion === version) {
pathPrefixes.unshift(`/${branchPathPrefix}${branch.name}`); // ~dev
if (branch.isMaster) {
pathPrefixes.unshift(`/`);
}
// remember aliased versions to unmount it later when alias should be removed
aliasedVersions.push(version);
}
// create route handlers and mount
const routes = [
...version.routes,
...this.staticRoutes,
].filter(component.canHandleRoute.bind(component));
const routeHandlerMap = component.mountRoutes(routes, pathPrefixes, createContext);
versionHandlerMap.set(version, routeHandlerMap);
}
}
branchHandlerMap.set(branch, versionHandlerMap);
this.props.logger.info(`${branch} handler mounted for ${component} component`);
})
.catch(error => {
if (error instanceof interface_1.FatalError) {
throw error;
}
this.props.logger.error(`failed to mount ${branch} handler for ${component}: ${error}`);
});
})));
// tslint:disable-next-line:no-shadowed-variable
// this.props.logger.info("\n" + this.routes.map(({branch, version, route}) => `/~${branch.name}@${version.shortHash}${route}`).join("\n"));
});
}
unmountBranchHandler(branch) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
yield Promise.all(this.components.map((component) => tslib_1.__awaiter(this, void 0, void 0, function* () {
return this.lock.acquire(component.key, () => {
// get branch handlers map
const branchHandlerMap = this.componentBranchHandlerMap.get(component);
if (!branchHandlerMap) {
return;
}
const versionHandlerMap = branchHandlerMap.get(branch);
if (!versionHandlerMap) {
return;
}
// remove all version handlers
for (const [version, routeHandlerMap] of versionHandlerMap.entries()) {
component.unmountRoutes(routeHandlerMap);
versionHandlerMap.delete(version);
}
// clear resources
versionHandlerMap.clear();
branchHandlerMap.delete(branch);
this.props.logger.info(`${branch} handler unmounted for ${component}`);
})
.catch(error => {
if (error instanceof interface_1.FatalError) {
throw error;
}
this.props.logger.info(`failed to unmount ${branch} handler for ${component}`, error);
});
})));
});
}
addStaticRoute(route) {
this.staticRoutes.push(route);
return this;
}
get routes() {
const items = [];
for (const branchHandlerMap of this.componentBranchHandlerMap.values()) {
for (const [branch, versionHandlerMap] of branchHandlerMap.entries()) {
for (const [version, routeHandlerMap] of versionHandlerMap.entries()) {
for (const route of routeHandlerMap.keys()) {
items.push({ branch, version, route });
}
// add static routes
items.push(...this.staticRoutes.map(route => ({ branch, version, route })));
}
}
}
return items;
}
}
exports.ServerApplication = ServerApplication;
//# sourceMappingURL=application.js.map