namespace Leaf { export class ServiceStateModel extends DataSource{ constructor(service: Service, fields: (keyof TState)[] = []) { super(service.name, fields) } } export type AnyService = Service export abstract class Service { isInitialized: boolean = false readonly dependencies: string[] = [] abstract name: string initialize(...args) { this.bindAPINames() } settings: TSettings constructor(defaultSettings: TSettings = {} as any, settings: TSettings = {} as any) { this.settings = Leaf.Util.buildOption(defaultSettings, settings) } public events = new EventEmitter() public services: { [name: string]: AnyService } private apiNames: string[] = [] public connect(delegator: { invoke: (uri: string, data: any, callback: Function) => void }) { this.bindAPINames() for (let name of this.apiNames) { this[name] = (o, callback) => { delegator.invoke(this.name + "/" + name, o, callback) } } } private bindAPINames() { this.apiNames.length = 0 for (let prop in this) { if (typeof this[prop] !== "function") continue let v = this[prop] as any as TaggedFunction if (v.tag !== "api") continue v.api = prop this.apiNames.push(prop) } } private api: Function[] = [] createAPI(handler: (option: TOption, callback: (err?: Error, result?: TResult) => void) => void = (() => { })): { (option?: TOption, callback?: (err: Error, result: TResult) => void): void } { let withDefaultCallback = ((o, c = () => { }) => { return handler(o, c) }) as any as TaggedFunction withDefaultCallback.tag = "api" return withDefaultCallback as any } } export interface TaggedFunction { tag?: "api" api?: string } export type DependencyMatcher = string | RegExp export class DependencyManager { private context: { [index: string]: DependencyMatcher[] } = {} add(name: string, deps: DependencyMatcher[]): void { if (this.context[name]) throw new Error(`Duplicate dependency entry: ${name}`) if (deps && deps.length == 0) deps = null this.context[name] = deps } onAddDymanicDependency: (name: string) => void = (name: string) => { } resolveDependency(name: string, chain: string[] = []): string[] { if (typeof this.context[name] === "undefined") { this.onAddDymanicDependency(name) if (typeof this.context[name] === "undefined") { throw new Error(`${name} required by ${chain[chain.length - 1]} couldn't be fullfilled.`) } } if (this.context[name] === null) return [] chain = chain.slice() if (chain.indexOf(name) >= 0) { chain.push(name) throw new Error(`Recursive dependency detected: ${chain.join("\n=>")}`) } chain.push(name) let result: string[] = [] let all: string[] = [] for (let matcher of this.context[name] || []) { if (typeof matcher === "string") { if (all.indexOf(matcher) < 0) all.push(matcher) } else if (matcher instanceof RegExp) { for (let name in this.context) { if (matcher.test(name) && all.indexOf(name) < 0) { all.push(name) } } } } for (let dep of all) { let deps = this.resolveDependency(dep, chain) for (let subDep of deps) { if (result.indexOf(subDep) < 0) result.push(subDep) } result.push(dep) } return result } resolve(): string[] { let result: string[] = [] for (let name in this.context) { let deps = this.resolveDependency(name) for (let dep of deps) { if (result.indexOf(dep) < 0) result.push(dep) } if (result.indexOf(name) < 0) result.push(name) } return result } } export class ServiceSetupProcedure extends Leaf.States { constructor(private services: { [key: string]: AnyService }, private setupSequence: string[]) { super() } protected data: { dones?: Function[] todo?: string[] } = {} setup(callback: (err: Error) => void) { if (!this.data.dones) this.data.dones = [] if (this.state === "finish") { callback(null) return } this.data.dones.push(callback) if (this.state === "void") { this.setState("prepare") } } atPrepare() { this.data.todo = this.setupSequence.slice() this.setState("setup") } atSetup() { let name = this.data.todo.shift() if (!name) { this.setState("finish") return } let service = this.services[name] if (!service) { this.error(new Error("Fail to find service name " + name)) return } if (service.isInitialized) { this.setState("setup") return } service.services = this.services console.log(`Service ${service.name} Initializing`) if (service.initialize) { if (service.initialize.length == 1) { service.initialize((err?: Error) => { if (err) { this.error(err) return } service.isInitialized = true this.logInitialized(name) this.setState("setup") }) } else { service.initialize() service.isInitialized = true this.logInitialized(name) this.setState("setup") } } else { // this service don't need initialization service.isInitialized = true this.logInitialized(name) this.setState("setup") } } atFinish() { for (let cb of this.data.dones) { cb() } } atPanic() { for (let cb of this.data.dones) { cb(this.panicError) } } private logInitialized(name: string) { console.log(`Service ${name} initialized`) } } export class ServiceContext { private BuiltInServiceInstances = {} constructor(private settings: any = {}) { // When dependency not met but we have that dependency in our built-in service this.dependencyManager.onAddDymanicDependency = (name) => { let s = this.BuiltInServiceInstances[name] if (s) { if (s["asBuiltIn"]) { s["asBuiltIn"] } this.register(s) } } } services: { [index: string]: AnyService } = {} dependencyManager: DependencyManager = new DependencyManager; register(s: T): T { this.dependencyManager.add(s.name, s.dependencies || null) this.services[s.name] = s return s } private procedure: ServiceSetupProcedure setup(callback: (err: Error) => void) { if (!this.procedure) { this.procedure = new ServiceSetupProcedure(this.services, this.dependencyManager.resolve()) } this.procedure.setup(callback) } } }