import { Topology } from "./topology" import { GraphBuilder } from "./graph" import { Pot } from "./pot" import * as asyncModule from "async" import { CriteriaBuilder, Condition, CriteriaTemplateBuilder } from "./condition" import * as Leaf from "leaf-ts" import { link } from "fs"; import * as CoreLogic from "./coreLogic" import { Errors } from "../errors" import * as util from "util" export class ExecutorContext { constructor(public readonly topology: Topology) { } cacheGraph(hash: string, graph: Type.Topology.Resolution.Graph) { this.cache[hash] = graph } getCachedGraph(hash: string) { return this.cache[hash] || null } cache: { [hash: string]: Type.Topology.Resolution.Graph } = {} } export type MatchInfoDetail = { model: Type.Topology.Model.Definition name: Type.Topology.Model.Name key: string query: Type.Topology.Condition.Criteria template: Type.Topology.Condition.CriteriaTemplate } export type MatchInfo = string | number | Type.Topology.Infer | boolean | Type.Topology.Condition.Criteria | CriteriaBuilder export type MatchConfigs = { // key can be: Model.Name | Joint [key: string]: MatchInfo } export class ExecutorBuilder { constructor(public readonly topology: Topology, public context = topology.executorContext) { } config: { from?: MatchConfigs match?: MatchConfigs about?: string via?: (Type.Topology.Model.Name | Type.Topology.Model.Joint)[] option?: Type.Topology.Resolution.QueryOption permission?: string role?: string debug?: boolean name?: string } = { match: {}, via: [], option: {} } gb: GraphBuilder start: string target: string srcPot: Type.Topology.Resolution.Pot srcPipe: Type.Topology.Resolution.Pipe option(option: Type.Topology.Resolution.QueryOption) { this.config.option = option return this } result: Type.Topology.Resolution.Execution = { graph: null, target: null, restriction: { pot: {}, pipe: {}, }, data: { pot: {}, pipe: {}, }, action: null } name(name: string) { this.config.name = name return this } as(role: string) { this.config.role = role return this } permission(permission: string) { this.config.permission = permission return this } about(target: string) { this.config.about = target return this } from(from: MatchConfigs) { this.config.from = from return this } via(...vias: string[]) { this.config.via = vias return this } debug() { this.config.debug = true return this } match(configs: MatchConfigs) { this.config.match = configs return this } create(data: T, callback?: Callback) { this.result.action = { type: "create", data } let builder = this.seal() let exe = new CreateExecutor(this.topology, builder.result) return exe.evaluate(callback) } creates(datas: T[], callback?: Callback) { this.result.action = { type: "creates", datas } let builder = this.seal() let exe = new CreatesExecutor(this.topology, builder.result) return exe.evaluate(callback) } ensure(data: T, callback?: Callback) { this.result.action = { type: "ensure", data } let builder = this.seal() let exe = new EnsureExecutor(this.topology, builder.result) return exe.evaluate(callback) } update(update: Partial, callback?: Callback) { this.result.action = { type: "update", update } let builder = this.seal() let exe = new UpdateExecutor(this.topology, builder.result) return exe.evaluate(callback) } updates(update: Partial, callback?: Callback) { this.result.action = { type: "updates", update } let builder = this.seal() let exe = new UpdatesExecutor(this.topology, builder.result) return exe.evaluate(callback) } deletes(callback?: Callback) { this.result.action = { type: "deletes" } let builder = this.seal() let exe = new DeletesExecutor(this.topology, builder.result) return exe.evaluate(callback) } some(option?: Type.Topology.Resolution.QueryOption, callback?: Callback) { this.result.action = { type: "some" } if (option) { this.config.option = option } let builder = this.seal() let exe = new SomeExecutor(this.topology, builder.result) return exe.evaluate(callback) } one(callback?: Callback) { this.result.action = { type: "one" } let builder = this.seal() let exe = new OneExecutor(this.topology, builder.result) return exe.evaluate(callback) } exists(callback?: Callback) { this.result.action = { type: "exists" } let builder = this.seal() let exe = new ExistsExecutor(this.topology, builder.result) return exe.evaluate(callback) } notExists(callback?: Callback) { this.result.action = { type: "notExists" } let builder = this.seal() let exe = new NotExistsExecutor(this.topology, builder.result) return exe.evaluate(callback) } index(key: string, callback?: Callback<{ [key: string]: TResult }>) { let builder = this.seal() this.result.action = { type: "index", key } let model = this.topology.getModel(this.result.target) if (model.unique.indexOf(key) < 0) { throw new Errors.LogicError("Can't index none unique key") } let exe = new IndexExecutor(this.topology, builder.result) return exe.evaluate(callback) } map(handler: ((item: T) => TResult) | ((item: T, done: Callback) => void), callback?: Callback) { this.result.action = { type: "map", handler } let builder = this.seal() let exe = new MapExecutor(this.topology, builder.result) return exe.evaluate(callback) } validate(handler: (item: T) => any, callback?: Callback) { this.result.action = { type: "validate", validator: handler } let builder = this.seal() let exe = new ValidateExecutor(this.topology, builder.result) return exe.evaluate(callback) } filter(filter: (item: T) => boolean, callback?: Callback) { this.result.action = { type: "filter", filter } let builder = this.seal() let exe = new FilterExecutor(this.topology, builder.result) return exe.evaluate(callback) } private isSealed = false private seal() { if (this.isSealed) { throw new Error("Already sealed") return } this.isSealed = true this.result.target = this.target = this.getTarget() this.config.from = this.config.from || { [this.target]: null } this.createGraph() this.ensureOptions() this.ensureMatch() this.ensureSrcData() if (this.config.debug) { console.log(`Debug graph ${this.config.name || "Anonymous"}`, util.inspect(this.result.graph, { depth: 10 })) this.result.debug = true } return this } private createGraph() { let gb = this.gb = new GraphBuilder(this.topology) let cache: Type.Topology.Resolution.Graph if (this.config.name) { cache = this.context.getCachedGraph(this.config.name) if (cache) { gb.result = cache } } if (!cache) { this.ensureSrc() this.ensurePermission() this.result.graph = this.gb.build() if (this.config.name) { this.context.cacheGraph(this.config.name, this.result.graph) } } // 1. create graph related } private ensureMatch() { let restriction = this.result.restriction for (let key in this.config.match) { let info = this.config.match[key] if (!this.isJoint(key) && !this.isModelName(key)) { if (this.isJoint(this.result.target + "." + key)) { // When key is only path instead of a joint // using [target].[path] as the key of joint key = this.result.target + "." + key } else { console.error(this.result.target, key, "???") throw new Error(`Unknown match key ${key}`) } } let detail = this.matchInfoToDetail(info, key) let pot = this.gb.getModelPot(detail.model.name) if (!pot) { throw new Errors.InvalidParameter(`${detail.key} not in graph`) } let potRes = restriction.pot[pot.name] = restriction.pot[pot.name] || {} if (potRes.query) { potRes.query = Condition.and(potRes.query, detail.query) } else { potRes.query = detail.query } } } private ensureSrcData() { let froms = this.extractFromInfo() let datas = this.extractFromData() let srcDatas = this.result.data for (let from of froms) { let pot = this.gb.getModelPot(from.model.name) let srcName = this.gb.getSrcGraphName(pot.name) let srcPot = this.gb.result.pots[srcName] let potData = srcDatas.pot[srcPot.name] = srcDatas.pot[srcPot.name] || {} let data = datas[from.model.name] if (!data) { if (srcPot) { let to = srcPot.tos[0] let pipe = this.gb.result.pipes[to] if (pipe.criteriaTemplate) { throw new Errors.InvalidParameter(`Missing data for input of ${from.model.name}`) } } } else { if (Array.isArray(data)) { potData.srcs = data } else { potData.srcs = [data] } } } } private ensureSrc() { let froms = this.extractFromInfo() for (let from of froms) { if (from.model.name !== this.result.target) { if (this.config.debug) { console.log(`pick link ${from.model.name} => ${this.result.target} via`, this.config.via) } let link = this.gb.pickLink({ start: from.model.name, end: this.result.target, vias: [this.config.via] }) let pots = this.gb.ensurePotsByLink(link) let first = pots[0] let src = this.gb.sourceOf({ of: first, criteriaTemplate: from.template }) } else { let target = this.gb.model(this.result.target) let src = this.gb.sourceOf({ of: target, criteriaTemplate: from.template }) } } } public defaultPermission = "public" private ensurePermission() { let targetModel = this.topology.getModel(this.result.target) let userRole = this.config.role || "public" let permission = this.config.permission || this.defaultPermission if (userRole === "root") return let access = targetModel.permission && targetModel.permission[permission] || null if (!access) { throw new Errors.LogicError(`No permission specified for ${permission}`) } let role: string = access.role || "guest" if (role !== userRole && role !== "guest") { throw new Errors.Forbidden(`${userRole} don't have access for ${this.result.target} of ${permission}`) } if (access.from) { let modelName = this.extractModel(access.from) let link = this.gb.pickLink({ start: modelName, end: targetModel.name, vias: access.via && [access.via] }) let path = access.from.split(".").slice(1).join(".") let pots = this.gb.ensurePotsByLink(link) let first = pots[0] if (!path) { if (first.froms.length == 0) { throw new Errors.LogicError(`Permission of ${modelName}.${permission} has no explicity joint declaration for ${access.from}, but find no pot in original graph`) } } else { if (first.froms.length == 0) { throw new Errors.LogicError(`Permission of ${modelName}.${permission} from ${access.from} has not explicity from declaration`) } } // TODO add checks for from } } private buildFullAccess() { return { read: true, create: true, update: true, delete: true } } private ensureOptions() { let target = this.gb.getModelPot(this.target) let potData = this.result.restriction.pot[target.name] = this.result.restriction.pot[target.name] || {} potData.option = this.config.option } private matchInfoToDetail(info: MatchInfo, key: string): MatchInfoDetail { let modelName = this.extractModel(key) let model = this.topology.getModel(modelName) if (!model) { throw new Errors.LogicError(`Model ${modelName} not found`) } let path = key.split(".").slice(1).join(".") let template: Type.Topology.Condition.CriteriaTemplate let query: Type.Topology.Condition.Criteria let qt = new CriteriaTemplateBuilder() let qb = new CriteriaBuilder() if (Leaf.Util.isEmptyValue(info)) { return { model, name: modelName, key, query: null, template: null } } else if (typeof info == "boolean" || typeof info == "number" || typeof info == "string") { if (!path) { throw new Errors.LogicError(`Model ${modelName} can be compared with ${typeof info}`) } qt.path(path).eq(path) qb.path(path).eq(info) template = qt.getTemplate() query = qb.build() } else if (Array.isArray(info)) { qt.path(path).in(path) qb.path(path).in(info) template = qt.getTemplate() query = qb.build() } else if (info instanceof CriteriaBuilder) { if (path) { info.sub(path) } query = info.build() template = Condition.criteriaToTemplateByPath(query) } else if (info.tag == "criteria") { query = info if (path) { Condition.subInplace(info, path) } template = Condition.criteriaToTemplateByPath(info) } else if (info.tag == "infer") { throw new Errors.LogicError("Infer not supported") } return { model, name: modelName, key, query, template } } private matchInfoToData(info: MatchInfo) { if (Leaf.Util.isEmptyValue(info)) return null if (info instanceof CriteriaBuilder) { return Condition.criteriaToValue(info.build()) } else if (typeof info == "string" || typeof info == "boolean" || typeof info == "number") { return info } else if (Array.isArray(info)) { return info } else if (info.tag == "criteria") { return Condition.criteriaToValue(info) } else if (info.tag == "infer") { throw new Errors.LogicError("Infer not supported for now") } } private extractFromData() { if (this.cache.formDataCache) return this.cache.formDataCache let result = {} for (let key of Object.keys(this.config.from)) { let info = this.config.from[key] result[key] = this.matchInfoToData(info) } return this.cache.formDataCache = CoreLogic.reconstructObjectMap(result) } cache: { formInfoCache?: any formDataCache?: any } = {} formInfoCache = null private extractFromInfo(): MatchInfoDetail[] { if (this.cache.formInfoCache) { return this.cache.formInfoCache } let infos = Object.keys(this.config.from).map((key) => { return this.matchInfoToDetail(this.config.from[key], key) }) return this.cache.formInfoCache = infos } private extractDetailFromCriteria(key: string, info: string | number | string[] | number[] | Type.Topology.Infer | boolean | Type.Topology.Condition.Criteria | CriteriaBuilder) { if (Leaf.Util.isEmptyValue(info)) { return null } let parts = key.split(".") let modelName = parts.shift() let model = this.topology.getModel(modelName) if (!model) { throw new Errors.LogicError("Invalid model name") } let path = parts.join(".") if (typeof info == "string" || typeof info == "number" || typeof info == "boolean") { let tb = new CriteriaBuilder() tb.path(path) tb.eq(info) return { model, criteria: tb.build() } } else if (Array.isArray(info)) { let tb = new CriteriaBuilder() tb.path(path) tb.in(info) return { model, criteria: tb.build() } } else if (info instanceof CriteriaBuilder) { let criteria = info.build() Condition.subInplace(criteria, path) return { model, criteria } } else if (info.tag == "criteria") { let criteria = Leaf.Util.clone(info) Condition.subInplace(criteria, path) return { model, criteria } } else if (info.tag == "infer") { throw new Errors.LogicError("From 'Infer' not implemented for now") } throw new Errors.LogicError("Unknown from info") } private defaultStartSourceName = "src" private startPotName: string private getStart() { if (this.config.from) return this.config.from let keys = Object.keys(this.config.match) let start = keys[0] if (start) return this.extractModel(start) start = this.getTarget() if (start) { return this.extractModel(start) } throw new Errors.LogicError("Can't get start") } private getTarget(): string { let end = this.config.about if (end) return end let keys = Object.keys(this.config.from || {}) if (keys.length == 1) { return this.extractModel(keys[0]) } throw new Errors.LogicError("Fail to infer executor target, missing 'about' keys") } private capitalLetterReg = /[A-Z]/ private extractModel(joint: string) { let model = joint.split(".")[0] if (this.topology.getModel(model)) { return model } throw new Errors.LogicError(`Can't extract model name from ${joint}`) } private isJoint(joint: string) { let parts = joint.split(".") if (parts.length > 1 && this.isModelName((parts[0]))) return true return false } private isModelName(model: string) { try { return !!this.topology.getModel(this.extractModel(model)) } catch (e) { return false } } } export abstract class Evaluatable { callbacks = Leaf.SharedCallbacks.create() result: T error: Error ready = new Leaf.ReadyBehavior() executing = false evaluate(callback: Callback = () => { }) { if (this.ready.isReady) { callback(this.error, this.result) return } this.callbacks.push(callback) if (!this.executing) { this.executing = true //process.nextTick(() => { this.execute((err, result) => { this.executing = false this.error = err this.result = result this.ready.yes() this.callbacks(err, result) }) //}) } return this } seal() { this.acceptInfo = false } acceptInfo = true setInfo(info: any) { if (!this.acceptInfo) return this.seal() this.info = info return this } info: any debug: boolean eval = this.evaluate abstract execute(callback: Callback) } export abstract class Executor extends Evaluatable{ constructor(public readonly topology: Topology, info: Type.Topology.Resolution.Execution) { super() this.setInfo(info) this.ready.whenReady(() => { if (this.info.debug) { console.log(`Debug data`, util.inspect(this.info.data, { depth: 10 })) console.log(`Debug restriction`, util.inspect(this.info.restriction, { depth: 10 })) } }) } assert() { this.isAsserting = true return this } isAsserting = false } export class CreatesExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.CreatesAction Pot.solveCriteria({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, criteria) => { if (err) { callback(err) return } let backend = this.topology.getBackend(this.info.target) let invalids = action.datas.filter(item => { return !Condition.testCriteria(criteria, item) }) if (invalids.length > 0) { console.error("Fail to pass criteria", criteria) callback(new Errors.Forbidden(`Fail to creates ${this.info.target}. Some fields is invalid`)) return } backend.inserts({ datas: action.datas }, (err, result) => { callback(err, result) }) }) } } export class CreateExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.CreateAction Pot.solveCriteria({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, criteria) => { if (err) { callback(err) return } let backend = this.topology.getBackend(this.info.target) if (!Condition.testCriteria(criteria, action.data)) { console.error("Fail to pass criteria", criteria) callback(new Errors.Forbidden(`Fail to create ${this.info.target}. Some fields is invalid.`)) return } backend.inserts({ datas: [action.data] }, (err, result) => { callback(err, result[0]) }) }) } } export class EnsureExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.EnsureAction Pot.solveCriteria({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, criteria) => { if (err) { callback(err) return } let backend = this.topology.getBackend(this.info.target) backend.one({ criteria }, (err, result) => { if (result) { let unique = this.topology.getModel(pot.modelName).unique[0] let criteria = new CriteriaBuilder().path(unique).eq(result[unique]).build() backend.update({ criteria, data: action.data }, (err) => { callback(null, result) }) } else { if (!Condition.testCriteria(criteria, action.data)) { callback(new Errors.Forbidden(`Fail to ensure ${this.info.target}.`)) return } backend.inserts({ datas: [action.data] }, (err) => { callback(null, action.data) }) } }) }) } } export class UpdatesExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.UpdatesAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result: T[]) => { if (err) { callback(err) return } if (result.length == 0) { callback(null) return } let model = this.topology.getModel(this.info.target) let unique = model.unique[0] let query = new CriteriaBuilder().path(unique).in(result.map(item => item[unique])).build() let backend = this.topology.getBackend(this.info.target) backend.update({ criteria: query, data: action.update }, (err) => { callback(err, result) }) }) } } export class UpdateExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.UpdateAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result: T[]) => { if (err) { callback(err) return } if (result.length == 0) { callback(null) return } let model = this.topology.getModel(this.info.target) let unique = model.unique[0] let query = new CriteriaBuilder().path(unique).eq(result[0][unique]).build() let backend = this.topology.getBackend(this.info.target) backend.update({ criteria: query, data: action.update }, (err) => { callback(err, result[0]) }) }) } } export class DeletesExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.DeletesAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } if (result.length == 0) { callback(null, []) return } let model = this.topology.getModel(this.info.target) let unique = model.unique[0] let query = new CriteriaBuilder().path(unique).in(result.map(item => item[unique])).build() let backend = this.topology.getBackend(this.info.target) backend.remove({ criteria: query, }, (err) => { callback(err, result as any) }) }) } } export class CountExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.SomeAction Pot.solveCriteria({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, criteria) => { if (err) { callback(err) return } let backend = this.topology.getBackend(this.info.target) backend.count({ criteria }, callback }) } } export class SomeExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.SomeAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } callback(null, result) }) } } export class OneExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.OneAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, count: 1, offset: 0, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } if (this.isAsserting && !result[0]) { callback(new Errors.NotFound(`${this.info.target} not found`)) return } callback(null, result[0]) }) } } export class ExistsExecutor extends Executor { execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.SomeAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, count: 1, offset: 0, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } if (!result[0]) { callback(new Errors.NotExists) return } callback(null) }) } } export class NotExistsExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.SomeAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, count: 1, offset: 0, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } if (result[0]) { callback(new Errors.AlreadyExists) return } callback(null) }) } } export class IndexExecutor extends Executor<{ [key: string]: T }>{ execute(callback: Callback<{ [key: string]: T }>) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.IndexAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } let index = {} for (let item of result) { index[item[action.key]] = item } callback(null, index) }) } } export class MapExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.MapAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } if (action.handler.length == 2) { asyncModule.map(result, action.handler, callback) } else { callback(null, result.map(action.handler as any)) } }) } private handleExecutions(items: any[], callback: Callback) { if (items.length == 0) { callback(null, items) return } let needHandle = false for (let item of items) { if (item instanceof Executor && item instanceof ExecutorBuilder) { needHandle = true break } } if (!needHandle) { callback(null, items) return } asyncModule.map(items, (item, done) => { if (item instanceof ExecutorBuilder) { item = item.some() } if (item instanceof Executor) { item.evaluate(done) return } done(null, item) }, (err, result) => { callback(err, result as any) }) } } export class ValidateExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.ValidateAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } try { for (let item of result) { action.validator(item) } } catch (e) { callback(e) return } callback(null, result) }) } } export class FilterExecutor extends Executor{ execute(callback: Callback) { let pot = this.info.graph.pots[GraphBuilder.getModelGraphName(this.info.target)] let action = this.info.action as Type.Topology.Resolution.FilterAction Pot.solve({ topology: this.topology, graph: this.info.graph, pot: pot, data: this.info.data, restriction: this.info.restriction, debug: this.info.debug, }, (err, result) => { if (err) { callback(err) return } callback(null, result.filter(action.filter)) }) } }