rokot-notification
Version:
Rokot - [Rocketmakers](http://www.rocketmakers.com/) TypeScript NodeJs Platform
161 lines (159 loc) • 7.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const _ = require("underscore");
const fs = require("fs");
const path = require("path");
class ApiReflector {
constructor(logger, sourcePaths) {
this.logger = logger;
this.sourcePaths = sourcePaths;
this.collected = {};
const options = {};
const host = ts.createCompilerHost(options);
this.program = ts.createProgram(sourcePaths, options, host);
this.checker = this.program.getTypeChecker();
}
getNotificationInheritance(type) {
const types = [];
if (this.getNotificationInheritanceRecursive(type, types)) {
return types.reverse();
}
}
getNotificationInheritanceRecursive(type, types) {
if (type.symbol.name === "INotification") {
types.push(type);
return true;
}
const bts = type.getBaseTypes();
for (let bt of bts) {
if (this.getNotificationInheritanceRecursive(bt, types)) {
types.push(type);
return true;
}
}
return false;
}
processClass(cls, code, relativePath, options) {
const notificationType = this.getNotificationType(cls);
if (!notificationType || !cls.heritageClauses) {
return;
}
cls.heritageClauses.forEach(hc => {
hc.types.forEach(t => {
if (t.typeArguments && t.typeArguments.length === 1) {
const tr = t.typeArguments[0];
const type = this.checker.getTypeAtLocation(tr);
const nt = this.getNotificationInheritance(type);
if (nt && nt.length) {
const msg = nt[0];
const id = msg["id"];
if (this.collected[id]) {
this.logger.trace("Already collected", msg.symbol.name);
return;
}
this.logger.trace("Collecting", msg.symbol.name);
this.collected[id] = true;
const customProps = this.checker.getPropertiesOfType(msg).filter(p => p.name !== "type" && p.name !== "recipient");
let interfaceProps = _.flatten(customProps.map(cp => {
return cp.declarations.map(d => {
const ddd = d;
return {
name: ddd.name.getText(),
optional: !!ddd.questionToken,
type: ddd.type.getText()
};
});
}));
const propNames = interfaceProps.length ? ", ...payload" : "";
const sf = type.symbol.declarations[0].getSourceFile().fileName;
const relativePath1 = "./" + path.relative(path.resolve("."), sf);
code.imports.push({ name: type.symbol.name, path: relativePath1 });
const methodName = options && options.functionName ? options.functionName(type.symbol.name, notificationType) : `dispatch${type.symbol.name}`;
const interfaceName = `${type.symbol.name}Payload`;
code.interfaces.push(`export type ${interfaceName} = Omit<${type.symbol.name}, OmitProps>\n`);
const methodParams = interfaceProps.length ? `, payload: ${interfaceName}` : "";
const sig = `${methodName}(recipient: NotificationRecipients${methodParams}): Promise<INotificationDispatchResult[]>`;
code.functions.push(`
${sig} {
const notify: ${type.symbol.name} = { type: ${notificationType}, recipient${propNames} }
return this.dispatcher.dispatch(notify)
}
`);
code.interfaceSignatures.push(`
${sig}`);
}
}
});
});
}
getNotificationType(cls) {
if (!cls.decorators) {
return;
}
for (let d of cls.decorators) {
if (d.expression.kind !== ts.SyntaxKind.CallExpression) {
continue;
}
let de = d.expression;
if (de.expression.getText().trim() !== "notifications.handler" || de.arguments.length < 1) {
continue;
}
let dea = de.arguments[0];
if (dea.kind == ts.SyntaxKind.Identifier) {
let ident = dea;
const ttt = this.checker.getSymbolAtLocation(ident);
const vd = ttt.valueDeclaration;
return vd.initializer.getText();
}
return dea.getText();
}
}
writeNotifications(writeTo, options) {
const code = { imports: [], functions: [], interfaceSignatures: [], interfaces: [] };
this.program.getSourceFiles().forEach(sourceFile => {
const relativePath = "./" + path.relative(path.resolve("."), sourceFile.fileName);
if (this.sourcePaths.indexOf(relativePath) > -1) {
this.logger.trace("PROCESSING: ", sourceFile.fileName);
ts.forEachChild(sourceFile, c => {
if (c.kind === ts.SyntaxKind.ClassDeclaration) {
this.processClass(c, code, relativePath, options);
}
});
}
});
if (code.functions.length) {
let src = "";
const groups = _.groupBy(code.imports, i => i.path);
for (let groupKey of _.keys(groups)) {
let ext = path.extname(groupKey);
let relPath = path.relative(path.dirname(writeTo), path.dirname(groupKey)) + "/" + path.basename(groupKey, ext);
let importPath = relPath.indexOf(".") === 0 ? relPath : (relPath.indexOf("/") === 0 ? "." + relPath : "./" + relPath);
src += `import { ${groups[groupKey].map(g => g.name).join(", ")} } from "${importPath}"\n`;
}
src += `import { NotificationRecipients, INotificationDispatcher, INotificationDispatchResult } from "${options && options.notificationImport || "rokot-notification"}"\n`;
src += `${options.additionalImports ? options.additionalImports.join("\n") + "\n" : ""}\n`;
src += `export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;;
type OmitProps = "type" | "recipient"
`;
for (let func of code.interfaces) {
src += func;
}
const className = options && options.className || "Notifications";
src += `\nexport interface I${className} {\n`;
for (let func of code.interfaceSignatures) {
src += func + "\n";
}
src += `}\n\n`;
src += `export class ${className} implements I${className}{\n`;
src += ` constructor(private dispatcher: INotificationDispatcher) {}\n`;
for (let func of code.functions) {
src += func;
}
src += `}\n`;
fs.writeFileSync(writeTo, src);
}
return code;
}
}
exports.ApiReflector = ApiReflector;