UNPKG

6.6 kBPlain TextView Raw
1//import 'reflect-metadata'
2import './Globals'
3
4const Bluebird = require('bluebird')
5Promise = Bluebird
6
7
8import * as Log from './log'
9import {TypeStoreModelKey} from './Constants'
10import {
11 IStorePlugin,
12 ICoordinatorOptions,
13 IModel,
14 IModelType,
15 CoordinatorOptions,
16 ICoordinator,
17 IPlugin, PluginType
18} from './Types'
19import {msg, Strings} from "./Messages"
20import {Repo} from "./Repo";
21import {PluginFilter, assert, PromiseMap, isNil} from "./Util";
22import {IModelOptions} from "./decorations/ModelDecorations";
23import {PluginEventType} from "./PluginTypes";
24
25// Create logger
26const log = Log.create(__filename)
27
28/**
29 * ClassName -> ModelClass Mapping table
30 */
31export type TModelTypeMap = {[clazzName:string]:IModelType}
32
33/**
34 * The overall coordinator
35 */
36export class Coordinator implements ICoordinator {
37
38 private plugins:IPlugin[] = []
39
40 notify(eventType:PluginEventType,...args:any[]) {
41 this.plugins.forEach(plugin => plugin.handle(eventType,...args))
42 }
43
44 /**
45 * Model registration map type
46 */
47
48
49
50 /**
51 * Stores all registrations, enabling
52 * them to be configured against a
53 * changed client, multiple datasources,
54 * utility scripts, etc
55 *
56 * @type {{}}
57 */
58
59 private modelMap:TModelTypeMap = {}
60 private models:IModelType[] = []
61
62
63 /**
64 * Retrieve model registrations
65 *
66 * @returns {TModelTypeMap}
67 */
68 getModels():IModelType[] {
69 return this.models
70 }
71
72 private findModel(predicate) {
73 for (let modelType of this.models) {
74 if (predicate(modelType)) {
75 return modelType
76 }
77 }
78
79 log.debug('unable to find registered model for clazz in',Object.keys(this.modelMap))
80 return null
81 }
82
83 getModel(clazz:any):IModelType {
84 return this.findModel((model) => model.clazz === clazz)
85 }
86
87 getModelByName(name:string):IModelType {
88 return this.findModel((model) => model.name === name)
89 }
90
91 /**
92 * Default options
93 */
94 private options:ICoordinatorOptions = new CoordinatorOptions(null)
95
96 getOptions() {
97 return this.options
98 }
99
100
101 private initialized = false
102
103 // NOTE: settled and settling Promise are overriden properties - check below namespace
104
105 private startPromise:Promise<any> = null
106 private internal = {
107 started:false
108 }
109 get started() {
110 return this.startPromise !== null && this.internal.started
111 }
112
113 set started(newVal:boolean) {
114 this.internal.started = newVal
115 }
116
117
118
119 private checkInitialized(not:boolean = false) {
120 this.checkStarted(true)
121 assert(not ? !this.initialized : this.initialized,
122 msg(not ? Strings.ManagerInitialized : Strings.ManagerNotInitialized))
123 }
124
125
126
127 private checkStarted(not:boolean = false) {
128 const valid = (not) ? !this.started : this.started
129
130 assert(valid, msg(not ? Strings.ManagerSettled : Strings.ManagerNotSettled))
131 }
132
133 stores() {
134 return PluginFilter<IStorePlugin>(this.plugins,PluginType.Store)
135 }
136
137
138
139 /**
140 * Set the coordinator options
141 */
142 init(newOptions:ICoordinatorOptions, ...newPlugins:IPlugin[]):Promise<ICoordinator> {
143 this.checkStarted(true)
144 this.checkInitialized(true)
145 this.initialized = true
146 this.plugins.push(...newPlugins)
147
148 // Update the default options
149 this.options = this.options || newOptions
150 Object.assign(this.options,newOptions)
151
152
153 // Make sure we got a valid store
154 assert(this.stores().length > 0,msg(Strings.ManagerTypeStoreRequired))
155
156 // Coordinator is ready, now initialize the store
157 log.debug(msg(Strings.ManagerInitComplete))
158 return Bluebird.all(
159 this.plugins
160 .filter(plugin => !isNil(plugin))
161 .map(plugin => plugin.init(this,this.options))
162 ).return(this)
163
164 }
165
166
167
168 /**
169 * Start the coordinator and embedded store from options
170 *
171 * @returns {Bluebird<boolean>}
172 */
173 start(...models):Promise<ICoordinator> {
174 this.checkStarted(true)
175 models.forEach(model => this.registerModel(model))
176
177 this.startPromise = PromiseMap(this.plugins, plugin => (plugin) && plugin.start())
178
179 return Bluebird.resolve(this.startPromise)
180 .return(this)
181 .catch(err => {
182 log.error('failed to start coordinator',err)
183 throw err
184 })
185 .finally(() => {
186 this.started = true
187 this.startPromise = null
188 })
189
190
191 }
192
193 async stop():Promise<ICoordinator> {
194 if (!this.started)
195 return this
196
197 try {
198 await (this.startPromise) ?
199 this.startPromise.then(this.stopPlugins.bind(this)) :
200 this.stopPlugins()
201 } catch (err) {
202 log.error(`Coordinator shutdown was not clean`)
203 } finally {
204 this.startPromise = null
205 this.started = false
206 this.initialized = false
207 this.plugins = []
208 this.models = []
209 this.modelMap = {}
210 }
211 return this
212 }
213
214
215 /**
216 * Execute function either immediately if
217 * ready or when the starting Promise
218 * completes
219 *
220 * @param fn
221 */
222 async execute<T>(fn:Function):Promise<T> {
223 return new Promise<T>((resolve,reject) => {
224
225 function executeFn(...args) {
226 const result = fn(...args)
227 resolve(result)
228 }
229
230 function handleError(err) {
231 const fnName = (fn) ? (fn as any).name : null
232 log.error(msg(Strings.ManagerErrorFn,fnName ? fnName : 'UNKNOWN'), err)
233 reject(err)
234 }
235
236 return (this.startPromise) ?
237 this.startPromise.then(executeFn).catch(handleError) :
238 Promise.resolve(executeFn).catch(handleError)
239
240 })
241
242 }
243
244 async stopPlugins() {
245 await PromiseMap(this.plugins, plugin => (plugin) && plugin.stop())
246 }
247
248 /**
249 * Reset the coordinator status
250 *
251 * @returns {Coordinator.reset}
252 */
253 async reset():Promise<ICoordinator> {
254 await this.stop()
255
256
257 return this
258
259 }
260
261 /**
262 * Register a model with the system
263 *
264 * @param constructor
265 */
266 registerModel(constructor:Function) {
267 this.checkStarted(true)
268
269 let model = this.getModel(constructor)
270 if (model) {
271 log.debug(`Trying to register ${model.name} a second time? is autoregister enabled?`)
272 return
273 }
274
275 const modelOpts:IModelOptions = Reflect.getMetadata(TypeStoreModelKey,constructor)
276 if (!modelOpts) {
277 log.info(`Can not register a model without metadata ${(constructor && constructor.name) || 'unknown'}`)
278 return
279 }
280
281 model = {
282 options: modelOpts,
283 name: modelOpts.clazzName,
284 clazz: constructor
285 }
286
287 this.modelMap[modelOpts.clazzName] = model
288 this.models.push(model)
289 this.notify(PluginEventType.ModelRegister,model)
290 return this
291 }
292
293
294 private repoMap = new WeakMap<any,any>()
295
296 /**
297 * Get a repository for the specified model/class
298 *
299 * @param clazz
300 * @returns {T}
301 */
302 getRepo<T extends Repo<M>,M extends IModel>(clazz:{new(): T; }):T {
303 let repo:T = this.repoMap.get(clazz)
304 if (repo)
305 return repo
306
307 repo = new clazz()
308 repo.init(this)
309
310 this.notify(PluginEventType.RepoInit,repo)
311
312 repo.start()
313 this.repoMap.set(clazz,repo)
314 return repo
315 }
316
317}
318
\No newline at end of file