UNPKG

@nodeswork/sbase

Version:

Basic REST api foundation from Nodeswork.

526 lines (524 loc) 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); require("reflect-metadata"); const _ = require("underscore"); const debug_1 = require("debug"); const mongoose_1 = require("mongoose"); const model_config_1 = require("./model-config"); const helpers_1 = require("./helpers"); const d = debug_1.default('sbase:model'); /** * Wrapped Model from mongoose.Model. */ class Model { static modelize(o) { const p = { toJSON() { return this; }, }; Object.setPrototypeOf(o, p); Object.setPrototypeOf(p, this.prototype); return o; } static Schema(schema) { helpers_1.extendMetadata(SCHEMA_KEY, this.prototype, schema); return this; } static Config(config) { helpers_1.extendMetadata(CONFIG_KEY, this.prototype, config); return this; } static Pre(pre) { helpers_1.pushMetadata(PRE_KEY, this.prototype, pre); return this; } static Post(post) { helpers_1.pushMetadata(POST_KEY, this.prototype, post); return this; } static Virtual(virtual) { helpers_1.pushMetadata(VIRTUAL_KEY, this.prototype, virtual); return this; } static Plugin(plugin) { helpers_1.pushMetadata(PLUGIN_KEY, this.prototype, plugin); return this; } static Index(index) { helpers_1.pushMetadata(INDEX_KEY, this.prototype, index); return this; } static Mixin(model) { helpers_1.pushMetadata(MIXIN_KEY, this.prototype, model); return this; } static UpdateValidator(validate) { helpers_1.pushMetadata(UPDATE_VALIDATOR_KEY, this.prototype, validate); return this; } static $mongooseOptions(sbaseConfig, tenancy = 'default') { if (this === Model) { return null; } d('generate $mongooseOptions for %O', this.name); let mongooseOptionsMap = Reflect.getOwnMetadata(MONGOOSE_OPTIONS_KEY, this.prototype); if (mongooseOptionsMap && mongooseOptionsMap[tenancy]) { return mongooseOptionsMap[tenancy]; } const mongooseOptions = {}; mongooseOptionsMap = { [tenancy]: mongooseOptions, }; Reflect.defineMetadata(MONGOOSE_OPTIONS_KEY, mongooseOptionsMap, this.prototype); const superClass = this.__proto__; const superOptions = superClass.$mongooseOptions(sbaseConfig, tenancy) || {}; const mixinModels = _.union(Reflect.getOwnMetadata(MIXIN_KEY, this.prototype) || []); const mixinOptions = _.map(mixinModels, (model) => model.$mongooseOptions(sbaseConfig, tenancy)); const schemas = _.flatten([ _.map(mixinOptions, (opt) => opt.schema), superOptions.schema, Reflect.getOwnMetadata(SCHEMA_KEY, this.prototype), ]); mongooseOptions.schema = _.extend({}, ...schemas); const configs = _.flatten([ _.map(mixinOptions, (opt) => opt.config), superOptions.config, Reflect.getOwnMetadata(CONFIG_KEY, this.prototype), ]); mongooseOptions.config = _.extend({}, ...configs); mongooseOptions.pres = _.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.pres), superOptions.pres, Reflect.getOwnMetadata(PRE_KEY, this.prototype), ])), (x) => !!x); mongooseOptions.posts = _.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.posts), superOptions.posts, Reflect.getOwnMetadata(POST_KEY, this.prototype), ])), (x) => !!x); mongooseOptions.virtuals = _.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.virtuals), superOptions.virtuals, Reflect.getOwnMetadata(VIRTUAL_KEY, this.prototype), ])), (x) => !!x); mongooseOptions.updateValidators = _.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.updateValidators), superOptions.updateValidators, Reflect.getOwnMetadata(UPDATE_VALIDATOR_KEY, this.prototype), ])), (x) => !!x); mongooseOptions.plugins = _.sortBy(_.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.plugins), superOptions.plugins, Reflect.getOwnMetadata(PLUGIN_KEY, this.prototype), ])), (x) => !!x), (plugin) => plugin.priority); mongooseOptions.indexes = _.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.indexes), superOptions.indexes, Reflect.getOwnMetadata(INDEX_KEY, this.prototype), ])), (x) => !!x); mongooseOptions.methods = _.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.methods), superOptions.methods, ])), (x) => !!x); mongooseOptions.statics = _.filter(_.union(_.flatten([ _.map(mixinOptions, (opt) => opt.statics), superOptions.statics, ])), (x) => !!x); for (const name of Object.getOwnPropertyNames(this.prototype)) { if (name === 'constructor') { continue; } const descriptor = Object.getOwnPropertyDescriptor(this.prototype, name); if (descriptor.value && _.isFunction(descriptor.value)) { mongooseOptions.methods.push({ name, fn: descriptor.value }); } if (descriptor.get || descriptor.set) { mongooseOptions.virtuals.push({ name, get: descriptor.get, set: descriptor.set, }); } } for (const name of Object.getOwnPropertyNames(this)) { const descriptor = Object.getOwnPropertyDescriptor(this, name); if (STATIC_FILTER_NAMES.indexOf(name) >= 0) { continue; } if (descriptor.value) { mongooseOptions.statics.push({ name, fn: descriptor.value }); } } const collection = sbaseConfig != null && mongooseOptions.config.collection ? _.chain([ tenancy === 'default' ? sbaseConfig.multiTenancy.defaultCollectionNamespace : tenancy, mongooseOptions.config.collection, ]) .filter((x) => !!x) .join('.') .value() : mongooseOptions.config.collection; const mongooseSchema = new mongoose_1.Schema(mongooseOptions.schema, _.extend(_.clone(mongooseOptions.config), { collection })); mongooseSchema.parentSchema = superOptions.mongooseSchema; for (const pre of mongooseOptions.pres) { d('create pre for %O with name %O and options %O', this.name, pre.name, pre); pre.parallel = pre.parallel || false; mongooseSchema.pre(pre.name, pre.parallel, pre.fn, pre.errorCb); } for (const post of mongooseOptions.posts) { d('create post for %O with name %O and options %O', this.name, post.name, post); mongooseSchema.post(post.name, post.fn); } for (const virtual of mongooseOptions.virtuals) { d('create virtual for %O with name %O and options %O', this.name, virtual.name, virtual.options); let v = mongooseSchema.virtual(virtual.name, virtual.options); if (virtual.get) { v = v.get(virtual.get); } if (virtual.set) { v = v.set(virtual.set); } } for (const method of mongooseOptions.methods) { d('create method for %O with name %O and function %O', this.name, method.name, method.fn); mongooseSchema.methods[method.name] = method.fn; } for (const method of mongooseOptions.statics) { mongooseSchema.statics[method.name] = method.fn; } for (const plugin of mongooseOptions.plugins) { const options = _.extend({}, mongooseOptions.config, plugin.options); mongooseSchema.plugin(plugin.fn, options); } for (const index of mongooseOptions.indexes) { d('create index for %O with fields %O and options %O', this.name, index.fields, index.options); mongooseSchema.index(index.fields, index.options); } for (const validate of mongooseOptions.updateValidators) { mongooseSchema .path(validate.path) .validate(combineValidator(validate.fn), validate.errorMsg, validate.type); } mongooseOptions.mongooseSchema = mongooseSchema; return mongooseOptions; } static $register(mongooseInstance) { return registerMultiTenancy(this, mongooseInstance); } static $registerA7Model(mongooseInstance) { return registerMultiTenancy(this, mongooseInstance); } } exports.Model = Model; const mongooseInstanceMap = {}; exports.lazyFns = []; exports.shareFns = ['on']; function registerMultiTenancy(model, mongooseInstance) { if (!mongooseInstance) { mongooseInstance = require('mongoose'); mongooseInstance.sbaseConfig = model_config_1.sbaseMongooseConfig; } const sbaseConfig = mongooseInstance .sbaseConfig; if (!sbaseConfig.multiTenancy.enabled) { const m = mongooseInstance.model(model.name, model.$mongooseOptions().mongooseSchema); m.sbaseConfig = sbaseConfig; return m; } const tenants = ['default'].concat(sbaseConfig.multiTenancy.tenants); const tenantMap = {}; for (const tenancy of tenants) { let mi = mongooseInstanceMap[tenancy]; if (mi == null) { mi = new mongoose_1.Mongoose(); mi.connect(sbaseConfig.multiTenancy.uris, sbaseConfig.multiTenancy.options, (err) => { sbaseConfig.multiTenancy.onError(err, tenancy); }); sbaseConfig.multiTenancy.onMongooseInstanceCreated(mi, tenancy); mongooseInstanceMap[tenancy] = mi; } const m = mi.model(model.name, model.$mongooseOptions(sbaseConfig, tenancy).mongooseSchema); m.sbaseConfig = sbaseConfig; tenantMap[tenancy] = m; } const proxy = new Proxy({}, { get: (_obj, prop) => { if (prop === '$tenantMap') { return tenantMap; } if (exports.lazyFns.indexOf(prop) >= 0) { const ret = function () { const t = sbaseConfig.multiTenancy.tenancyFn(prop); const m1 = tenantMap[t]; const actualFn = m1[prop]; return actualFn.apply(this, arguments); }; return ret; } if (exports.shareFns.indexOf(prop) >= 0) { const ret = (...args) => { return _.map(tenants, (t) => { const m2 = tenantMap[t]; return m2[prop].apply(m2, args); }); }; return ret; } const tenancy = sbaseConfig.multiTenancy.tenancyFn(prop); const m = tenantMap[tenancy]; m._proxy = proxy; if (prop === '$modelClass') { return m; } const res = m[prop]; return _.isFunction(res) ? res.bind(m) : res; }, set: (_obj, prop, value) => { const tenancy = sbaseConfig.multiTenancy.tenancyFn(prop); const m = tenantMap[tenancy]; m[prop] = value; return true; }, }); return proxy; } class DocumentModel extends Model { static cast() { return this._proxy || this; } } exports.DocumentModel = DocumentModel; const SCHEMA_KEY = Symbol('sbase:schema'); const CONFIG_KEY = Symbol('sbase:config'); const PRE_KEY = Symbol('sbase:pre'); const POST_KEY = Symbol('sbase:post'); const VIRTUAL_KEY = Symbol('sbase:virtual'); const PLUGIN_KEY = Symbol('sbase:plugin'); const INDEX_KEY = Symbol('sbase:index'); const UPDATE_VALIDATOR_KEY = Symbol('sbase:updateValidator'); const MIXIN_KEY = Symbol('sbase:mixin'); const MONGOOSE_OPTIONS_KEY = Symbol('sbase:mongooseOptions'); function Enum(e, schema = {}) { return Field(_.extend({}, schema, { type: String, enum: Object.values(e).concat([null]), })); } exports.Enum = Enum; function DBRef(ref, schema = {}) { return Field(_.extend({}, schema, { type: mongoose_1.SchemaTypes.ObjectId, ref, })); } exports.DBRef = DBRef; function DBRefArray(ref, schema = {}) { return Field(_.extend({}, schema, { type: [ { type: mongoose_1.SchemaTypes.ObjectId, ref, }, ], })); } exports.DBRefArray = DBRefArray; function ArrayField(type, schema = {}) { return Field(_.extend({}, schema, { type: [type], default: [], })); } exports.ArrayField = ArrayField; function Required(opt = true, schema = {}) { return Field(_.extend({}, schema, { required: _.isFunction(opt) ? combineValidator(opt) : opt, })); } exports.Required = Required; function IndexField(schema = {}) { return Field(_.extend({}, schema, { index: true, })); } exports.IndexField = IndexField; function Unique(schema = {}) { return Field(_.extend({}, schema, { index: true, unique: true, })); } exports.Unique = Unique; function Default(defaultValue, schema = {}) { return Field(_.extend({}, schema, { default: defaultValue, })); } exports.Default = Default; function MapField(type, schema = {}) { return Field(_.extend({}, schema, { type: Map, of: type, })); } exports.MapField = MapField; function Optional(schema = {}) { return Field(schema); } exports.Optional = Optional; function Validate(validator, schema = {}) { return Field(_.extend({}, schema, { validate: { validator: combineValidator(validator.validator), message: validator.message, }, })); } exports.Validate = Validate; function Field(schema = {}) { function mapModelSchema(o) { if (_.isArray(o)) { return _.map(o, (x) => mapModelSchema(x)); } else if (_.isFunction(o)) { if (o.prototype instanceof Model) { const func = Object.getOwnPropertyDescriptor(o.__proto__, '$mongooseOptions'); return func.value.call(o).mongooseSchema; } else { return o; } } else if (_.isObject(o) && o.__proto__.constructor.name === 'Object') { return _.mapObject(o, (x) => mapModelSchema(x)); } else { return o; } } return (target, propertyName) => { const schemas = Reflect.getOwnMetadata(SCHEMA_KEY, target) || {}; const existing = schemas[propertyName]; if (schema.type == null && existing == null) { const type = Reflect.getMetadata('design:type', target, propertyName); schema.type = type; } if (_.isUndefined(schema.default) && schema.type && schema.type.prototype instanceof Model) { schema.default = () => ({}); } schemas[propertyName] = _.extend({}, existing, mapModelSchema(schema)); Reflect.defineMetadata(SCHEMA_KEY, schemas, target); }; } exports.Field = Field; function Mixin(model) { return (constructor) => { const models = Reflect.getOwnMetadata(MIXIN_KEY, constructor.prototype) || []; models.push(model); Reflect.defineMetadata(MIXIN_KEY, models, constructor.prototype); }; } exports.Mixin = Mixin; function Config(config) { return (constructor) => { const configs = Reflect.getOwnMetadata(CONFIG_KEY, constructor.prototype) || {}; _.extend(configs, config); Reflect.defineMetadata(CONFIG_KEY, configs, constructor.prototype); }; } exports.Config = Config; function Plugin(plugin) { return (constructor) => { const plugins = Reflect.getOwnMetadata(PLUGIN_KEY, constructor.prototype) || []; plugins.push(plugin); Reflect.defineMetadata(PLUGIN_KEY, plugins, constructor.prototype); }; } exports.Plugin = Plugin; function Pre(pre) { return (constructor) => { helpers_1.pushMetadata(PRE_KEY, constructor.prototype, pre); }; } exports.Pre = Pre; function Pres(names, pre) { return (constructor) => { const pres = _.map(names, (name) => _.extend({ name }, pre)); helpers_1.pushMetadata(PRE_KEY, constructor.prototype, ...pres); }; } exports.Pres = Pres; function Virtual(options) { return (target, propertyName) => { const virtuals = Reflect.getOwnMetadata(VIRTUAL_KEY, target) || []; virtuals.push({ name: propertyName, options, }); Reflect.defineMetadata(VIRTUAL_KEY, virtuals, target); }; } exports.Virtual = Virtual; function Post(post) { return (constructor) => { const posts = Reflect.getOwnMetadata(POST_KEY, constructor.prototype) || []; posts.push(post); Reflect.defineMetadata(POST_KEY, posts, constructor.prototype); }; } exports.Post = Post; function Posts(names, post) { return (constructor) => { const posts = Reflect.getOwnMetadata(POST_KEY, constructor.prototype) || []; for (const name of names) { posts.push(_.extend({ name }, post)); } Reflect.defineMetadata(POST_KEY, posts, constructor.prototype); }; } exports.Posts = Posts; function Index(index) { return (constructor) => { const indexes = Reflect.getOwnMetadata(INDEX_KEY, constructor.prototype) || []; indexes.push(index); Reflect.defineMetadata(INDEX_KEY, indexes, constructor.prototype); }; } exports.Index = Index; function UpdateValidator(validate) { return (constructor) => { const validates = Reflect.getOwnMetadata(UPDATE_VALIDATOR_KEY, constructor.prototype) || []; validates.push(validate); Reflect.defineMetadata(UPDATE_VALIDATOR_KEY, validates, constructor.prototype); }; } exports.UpdateValidator = UpdateValidator; class NativeError extends global.Error { } exports.NativeError = NativeError; exports.preQueries = [ 'find', 'findOne', 'count', 'findOneAndUpdate', 'findOneAndRemove', 'update', ]; const STATIC_FILTER_NAMES = ['name', 'length', 'prototype']; function combineValidator(fn) { return function () { if (this instanceof mongoose_1.Query) { return fn.apply(this.getUpdate().$set, arguments); } else { return fn.apply(this, arguments); } }; } exports.combineValidator = combineValidator; //# sourceMappingURL=model.js.map