///
///
///
if (process.argv.indexOf('--source-map'))
require('source-map-support').install()
if (process.argv.indexOf('--monitor-promises'))
require('when/monitor/console')
if (process.argv.indexOf('--profile') > -1) {
var agent = require('webkit-devtools-agent')
agent.start()
}
interface Bulb_Configuration {
path?:string
class?:string
parent?:string
}
class Vineyard {
bulbs:any = {}
config
config_folder
ground:Ground.Core
root_path:string
private validator
private json_schemas = {}
private module_schema_files = []
constructor(config_file:string) {
if (!config_file)
throw new Error("Vineyard constructor is missing config file path.")
this.validator = require('tv4')
this.config = this.load_config(config_file)
var path = require('path')
this.config_folder = path.dirname(config_file)
if (typeof global.SERVER_ROOT_PATH === 'string')
this.root_path = global.SERVER_ROOT_PATH
else {
var path = require('path')
this.root_path = path.dirname(require['main'].filename)
}
console.log('Vineyard root path: ' + this.root_path)
}
finalize() {
var ground_config = this.config.ground
ground_config.trellis_files = this.module_schema_files.concat(ground_config.trellis_files)
delete this.module_schema_files
console.log('schema-files:', ground_config.trellis_files)
// It's important to load ground before the bulbs since the bulbs usually hook into ground.
this.ground = Vineyard.create_ground("local",
ground_config.databases,
ground_config.trellis_files
)
for (var i in this.bulbs) {
var bulb = this.bulbs[i]
bulb.ground = this.ground
bulb.grow()
}
}
static create_ground(db_name:string, databases, trellis_files, metahub_files = null):Ground.Core {
var path = require('path')
var ground = new Ground.Core(databases, db_name);
for (var i in trellis_files) {
ground.load_schema_from_file(trellis_files[i])
}
return ground;
}
get_bulb(name:string):Promise {
return when.resolve(this.bulbs[name])
}
load_bulb(name:string, info:Bulb_Configuration = null):Vineyard.Bulb {
if (!info)
info = this.config.bulbs[name]
if (!info)
throw new Error("Could not find configuration for bulb: " + name)
var file
if (info.path) {
var path = require('path')
file = path.resolve(info.path)
}
else {
file = info.parent || name
// The "vineyard-" prefix for Vineyard bulbs can be implicit in the configuration JSON.
try {
require.resolve(file)
} catch (e) {
file = 'vineyard-' + file
}
}
var bulb_class = require(file)
if (info.class)
bulb_class = bulb_class[info.class]
if (!bulb_class)
throw new Error('Could not load bulb module: "' + name + '" (path=' + file + ').')
if (typeof bulb_class !== 'function')
throw new Error('bulb is not a class: "' + name + '" (path=' + file + ').')
var bulb = new bulb_class(this, info)
this.bulbs[name] = bulb
// Offload and re-add schema files added in the configuration so that they always
// loaded after the module schema files.
bulb.till_ground(this.config.ground)
return bulb
}
load_all_bulbs() {
var bulbs = this.config.bulbs
for (var name in bulbs) {
this.load_bulb(name)
}
this.finalize()
}
add_schema(path:string) {
this.module_schema_files.push(path)
}
load_config(config_file:string) {
var fs = require('fs')
var json = fs.readFileSync(config_file, 'ascii')
var local = JSON.parse(json)
var includes = local.includes
if (typeof includes == 'object') {
for (var i = 0; i < includes.length; ++i) {
var include = this.load_config(includes[i])
// Make sure the merging is done after including
// or there will be an infinite loop
local = Vineyard.deep_merge(local, include)
}
}
return local
}
static deep_merge(source, target) {
if (typeof source != 'object')
throw new Error('Configuration source is not an object.')
if (typeof target != 'object')
throw new Error('Configuration target is not an object.')
for (var key in source) {
var value = source[key]
if (typeof value == 'object') {
if (target[key] === undefined) {
target[key] = source[key]
}
else {
if (MetaHub.is_array(source)) {
for (var i = 0; i < source.length; ++i) {
target.push(source[i])
}
}
else {
Vineyard.deep_merge(value, target[key])
}
}
}
else {
target[key] = value
}
}
return target
}
start():Promise {
this.ground.harden_schema()
var promises = []
for (var i in this.bulbs) {
var bulb = this.bulbs[i]
promises.push(bulb.start())
}
return when.all(promises)
}
stop():Promise {
var promises = []
for (var i in this.bulbs) {
var bulb = this.bulbs[i]
promises.push(bulb.stop())
}
return when.all(promises)
.then(()=> {
this.ground.stop()
})
}
load_json_schema(name, path) {
var fs = require('fs')
this.json_schemas[name] = JSON.parse(fs.readFileSync(path, 'ascii'))
}
add_json_schema(name, schema) {
this.json_schemas[name] = schema
}
find_schema_errors(data, schema_name) {
var schema = this.json_schemas[schema_name]
if (!schema)
throw new Error('Could not validate data. No schema named ' + schema_name + ' was loaded.')
if (this.validator.validate(data, schema))
return null
return this.validator.error
}
}
module Vineyard {
export interface IUser {
id?
name?:string
roles?:IRole[]
}
export interface IRole {
id?
name?:string
}
export interface Ground_Configuration {
databases?:any[]
trellis_files:string[]
metahub_files:string[]
}
export class Bulb extends MetaHub.Meta_Object {
vineyard:Vineyard
config
ground:Ground.Core
constructor(vineyard:Vineyard, config) {
super()
this.vineyard = vineyard
this.config = config
}
// Called before Ground is loaded to allow injecting any additional Ground configuration
till_ground(ground_config:Ground_Configuration) {
}
// Called after Ground is loaded
grow() {
}
// Starts any long-running bulb processes
start() {
}
// Stops any long-running bulb processes
stop() {
}
}
export class Version {
major:number
minor:number = Infinity
revision:number = Infinity
platform:string
text:string
value:number
constructor(text:string) {
this.text = text
var tokens = text.trim().split('.')
var numbers:number[] = []
for (var i = 0; i < tokens.length; ++i) {
var token = tokens[i]
if (token == 'x') {
numbers.push(Infinity)
}
if (token.match(/^\d+$/)) {
numbers.push(parseInt(token))
}
else {
this.platform = token
}
}
this.major = numbers[0]
if (numbers.length > 1) {
this.minor = numbers[1]
if (numbers.length > 2) {
this.revision = numbers[2]
}
}
this.value
= Version.get_finite_value(this.major) * 10000
+ Version.get_finite_value(this.minor) * 100
+ Version.get_finite_value(this.revision)
}
static is_valid_format(text:string) {
return text.match(/^\d+(\.(\d+|x))?(\.(\d+|x))?(\.\w+)?$/)
}
static get_finite_value(integer:number):number {
return isFinite(integer)
? integer
: 0
}
}
}