ayanami
Version:
A better way to react with state
114 lines (113 loc) • 5.55 kB
JavaScript
import { __awaiter, __generator } from "tslib";
import { from, race, timer, throwError } from 'rxjs';
import { flatMap, skip, take, tap } from 'rxjs/operators';
import { InjectableFactory } from '@asuka/di';
import { combineWithIkari } from '../core/ikari';
import { createOrGetInstanceInScope, ayanamiInstances, createScopeWithRequest, } from '../core/scope/utils';
import { SSRSymbol, CleanupSymbol, DEFAULT_SCOPE_NAME } from './constants';
import { moduleNameKey } from './ssr-module';
import { SKIP_SYMBOL, reqMap } from './express';
var skipFn = function () { return SKIP_SYMBOL; };
/**
* Run all @SSREffect decorated effects of given modules and extract latest states.
* `cleanup` function returned must be called before end of responding
*
* @param req express request object
* @param modules used ayanami modules
* @param timeout seconds to wait before all effects stream out TERMINATE_ACTION
* @returns object contains ayanami state and cleanup function
*/
export var emitSSREffects = function (req, modules, timeout) {
if (timeout === void 0) { timeout = 3; }
var stateToSerialize = {};
var cleanup = function () {
// non-scope ayanami
if (ayanamiInstances.has(req)) {
ayanamiInstances.get(req).forEach(function (instance) {
instance[CleanupSymbol].call();
});
ayanamiInstances.delete(req);
}
// scoped ayanami
if (reqMap.has(req)) {
Array.from(reqMap.get(req).values()).forEach(function (s) {
ayanamiInstances.get(s).forEach(function (instance) {
instance[CleanupSymbol].call();
});
ayanamiInstances.delete(s);
});
reqMap.delete(req);
}
};
return modules.length === 0
? Promise.resolve({ state: stateToSerialize, cleanup: cleanup })
: race(from(modules).pipe(flatMap(function (m) { return __awaiter(void 0, void 0, void 0, function () {
var constructor, scope, metas, ayanamiInstance, moduleName, ikari, skipCount, _i, metas_1, meta, dispatcher, param, state, existedAyanami, existedIkari;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
scope = DEFAULT_SCOPE_NAME;
if ('scope' in m) {
constructor = m.module;
scope = m.scope;
}
else {
constructor = m;
}
metas = Reflect.getMetadata(SSRSymbol, constructor.prototype);
if (!metas) return [3 /*break*/, 7];
ayanamiInstance = InjectableFactory.initialize(constructor);
moduleName = ayanamiInstance[moduleNameKey];
ikari = combineWithIkari(ayanamiInstance);
skipCount = metas.length - 1;
_i = 0, metas_1 = metas;
_b.label = 1;
case 1:
if (!(_i < metas_1.length)) return [3 /*break*/, 5];
meta = metas_1[_i];
dispatcher = ikari.triggerActions[meta.action];
if (!meta.middleware) return [3 /*break*/, 3];
return [4 /*yield*/, meta.middleware(req, skipFn)];
case 2:
param = _b.sent();
if (param !== SKIP_SYMBOL) {
dispatcher(param);
}
else {
skipCount -= 1;
}
return [3 /*break*/, 4];
case 3:
dispatcher(void 0);
_b.label = 4;
case 4:
_i++;
return [3 /*break*/, 1];
case 5:
if (!(skipCount > -1)) return [3 /*break*/, 7];
return [4 /*yield*/, ikari.terminate$
.pipe(skip(skipCount), take(1))
.toPromise()];
case 6:
_b.sent();
ikari.terminate$.next(null);
state = ikari.state.getState();
if (stateToSerialize[moduleName]) {
stateToSerialize[moduleName][scope] = state;
}
else {
stateToSerialize[moduleName] = (_a = {},
_a[scope] = state,
_a);
}
existedAyanami = createOrGetInstanceInScope(constructor, createScopeWithRequest(req, scope === DEFAULT_SCOPE_NAME ? undefined : scope));
existedIkari = combineWithIkari(existedAyanami);
existedIkari.state.setState(state);
ayanamiInstance.destroy();
_b.label = 7;
case 7: return [2 /*return*/, { state: stateToSerialize, cleanup: cleanup }];
}
});
}); })), timer(timeout * 1000).pipe(tap(cleanup), flatMap(function () { return throwError(new Error('Terminate timeout')); }))).toPromise();
};