UNPKG

@leansdk/leanrc

Version:

LeanRC is a MVC framework for creating graceful applications

797 lines (722 loc) 25.5 kB
(function() { // This file is part of LeanRC. // LeanRC is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // LeanRC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public License // along with LeanRC. If not, see <https://www.gnu.org/licenses/>. /* http://edgeguides.rubyonrails.org/active_record_migrations.html http://api.rubyonrails.org/ http://guides.rubyonrails.org/v3.2/migrations.html http://rusrails.ru/rails-database-migrations http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html */ /* ```coffee module.exports = (Module)-> class BaseMigration extends Module::Migration @inheritProtected() @include Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных @module Module return BaseMigration.initialize() ``` ```coffee module.exports = (Module)-> {MIGRATIONS} = Module:: class PrepareModelCommand extends Module::SimpleCommand @inheritProtected() @module Module @public execute: Function, default: -> #... @facade.registerProxy Module::BaseCollection.new MIGRATIONS, delegate: Module::BaseMigration #... PrepareModelCommand.initialize() ``` ```coffee module.exports = (Module)-> class CreateUsersCollectionMigration extends Module::BaseMigration @inheritProtected() @include Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных @module Module @up -> yield @createCollection 'users' yield @addField 'users', name, 'string' yield @addField 'users', description, 'text' yield @addField 'users', createdAt, 'date' yield @addField 'users', updatedAt, 'date' yield @addField 'users', deletedAt, 'date' yield return @down -> yield @dropCollection 'users' yield return return CreateUsersCollectionMigration.initialize() ``` Это эквивалентно ```coffee module.exports = (Module)-> class CreateUsersCollectionMigration extends Module::BaseMigration @inheritProtected() @include Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных @module Module @change -> @createCollection 'users' @addField 'users', name, 'string' @addField 'users', description, 'text' @addField 'users', createdAt, 'date' @addField 'users', updatedAt, 'date' @addField 'users', deletedAt, 'date' return CreateUsersCollectionMigration.initialize() ``` */ module.exports = function(Module) { var AnyT, AsyncFuncG, AsyncFunctionT, EnumG, FuncG, InterfaceG, ListG, MaybeG, Migration, MigrationInterface, PointerT, Record, RecordInterface, StructG, SubsetG, UnionG, _, assign, co, forEach; ({ AnyT, AsyncFunctionT, PointerT, FuncG, ListG, StructG, EnumG, MaybeG, UnionG, InterfaceG, AsyncFuncG, SubsetG, MigrationInterface, RecordInterface, Record, Utils: {_, forEach, assign, co} } = Module.prototype); return Migration = (function() { var DOWN, REVERSE_MAP, SUPPORTED_TYPES, UP, iplSteps; class Migration extends Record {}; Migration.inheritProtected(); Migration.implements(MigrationInterface); Migration.module(Module); ({UP, DOWN, SUPPORTED_TYPES} = Migration.prototype); // @const UP: UP = Symbol 'UP' // @const DOWN: DOWN = Symbol 'DOWN' // @const SUPPORTED_TYPES: SUPPORTED_TYPES = { // json: 'json' // binary: 'binary' // boolean: 'boolean' // date: 'date' // datetime: 'datetime' // number: 'number' // decimal: 'decimal' // float: 'float' // integer: 'integer' // primary_key: 'primary_key' // string: 'string' // text: 'text' // time: 'time' // timestamp: 'timestamp' // array: 'array' // hash: 'hash' // } Migration.const({ REVERSE_MAP: REVERSE_MAP = { createCollection: 'dropCollection', dropCollection: 'dropCollection', createEdgeCollection: 'dropEdgeCollection', dropEdgeCollection: 'dropEdgeCollection', addField: 'removeField', removeField: 'removeField', addIndex: 'removeIndex', removeIndex: 'removeIndex', addTimestamps: 'removeTimestamps', removeTimestamps: 'addTimestamps', changeCollection: 'changeCollection', changeField: 'changeField', renameField: 'renameField', renameIndex: 'renameIndex', renameCollection: 'renameCollection' } }); iplSteps = PointerT(Migration.private({ steps: MaybeG(ListG(StructG({ args: Array, method: EnumG(['createCollection', 'createEdgeCollection', 'addField', 'addIndex', 'addTimestamps', 'changeCollection', 'changeField', 'renameField', 'renameIndex', 'renameCollection', 'dropCollection', 'dropEdgeCollection', 'removeField', 'removeIndex', 'removeTimestamps', 'reversible']) }))) })); Migration.public({ steps: ListG(StructG({ args: Array, method: EnumG(['createCollection', 'createEdgeCollection', 'addField', 'addIndex', 'addTimestamps', 'changeCollection', 'changeField', 'renameField', 'renameIndex', 'renameCollection', 'dropCollection', 'dropEdgeCollection', 'removeField', 'removeIndex', 'removeTimestamps', 'reversible']) })) }, { get: function() { var ref; return assign([], (ref = this[iplSteps]) != null ? ref : []); } }); // так же в рамках DSL нужны: // Creation //@createCollection #name, options Migration.public(Migration.static({ createCollection: FuncG([String, MaybeG(Object)]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'createCollection' }); } })); Migration.public(Migration.async({ createCollection: FuncG([String, MaybeG(Object)]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); // @createEdgeCollection #для хранения связей М:М #collection_1, collection_2, options Migration.public(Migration.static({ createEdgeCollection: FuncG([String, String, MaybeG(Object)]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'createEdgeCollection' }); } })); Migration.public(Migration.async({ createEdgeCollection: FuncG([String, String, MaybeG(Object)]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@addField #collection_name, field_name, options #{type} Migration.public(Migration.static({ addField: FuncG([ String, String, UnionG(EnumG(SUPPORTED_TYPES), InterfaceG({ type: EnumG(SUPPORTED_TYPES), default: AnyT })) ]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'addField' }); } })); Migration.public(Migration.async({ addField: FuncG([ String, String, UnionG(EnumG(SUPPORTED_TYPES), InterfaceG({ type: EnumG(SUPPORTED_TYPES), default: AnyT })) ]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@addIndex #collection_name, field_names, options Migration.public(Migration.static({ addIndex: FuncG([ String, ListG(String), InterfaceG({ type: EnumG('hash', 'skiplist', 'persistent', 'geo', 'fulltext'), unique: MaybeG(Boolean), sparse: MaybeG(Boolean) }) ]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'addIndex' }); } })); Migration.public(Migration.async({ addIndex: FuncG([ String, ListG(String), InterfaceG({ type: EnumG('hash', 'skiplist', 'persistent', 'geo', 'fulltext'), unique: MaybeG(Boolean), sparse: MaybeG(Boolean) }) ]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@addTimestamps # создание полей createdAt, updatedAt, deletedAt #collection_name, options Migration.public(Migration.static({ addTimestamps: FuncG([String, MaybeG(Object)]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'addTimestamps' }); } })); Migration.public(Migration.async({ addTimestamps: FuncG([String, MaybeG(Object)]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); // Modification //@changeCollection #name, options Migration.public(Migration.static({ changeCollection: FuncG([String, Object]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'changeCollection' }); } })); Migration.public(Migration.async({ changeCollection: FuncG([String, Object]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@changeField #collection_name, field_name, options #{type} Migration.public(Migration.static({ changeField: FuncG([ String, String, UnionG(EnumG(SUPPORTED_TYPES), InterfaceG({ type: EnumG(SUPPORTED_TYPES) })) ]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'changeField' }); } })); Migration.public(Migration.async({ changeField: FuncG([ String, String, UnionG(EnumG(SUPPORTED_TYPES), InterfaceG({ type: EnumG(SUPPORTED_TYPES) })) ]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@renameField #collection_name, field_name, new_field_name Migration.public(Migration.static({ renameField: FuncG([String, String, String]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'renameField' }); } })); Migration.public(Migration.async({ renameField: FuncG([String, String, String]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@renameIndex #collection_name, old_name, new_name Migration.public(Migration.static({ renameIndex: FuncG([String, String, String]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'renameIndex' }); } })); Migration.public(Migration.async({ renameIndex: FuncG([String, String, String]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@renameCollection #collection_name, old_name, new_name Migration.public(Migration.static({ renameCollection: FuncG([String, String]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'renameCollection' }); } })); Migration.public(Migration.async({ renameCollection: FuncG([String, String]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //Deletion //@dropCollection #name Migration.public(Migration.static({ dropCollection: FuncG(String) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'dropCollection' }); } })); Migration.public(Migration.async({ dropCollection: FuncG(String) }, { default: function*() { throw new Error('Not implemented specific method'); } })); // @dropEdgeCollection #collection_1, collection_2 Migration.public(Migration.static({ dropEdgeCollection: FuncG([String, String]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'dropEdgeCollection' }); } })); Migration.public(Migration.async({ dropEdgeCollection: FuncG([String, String]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@removeField #collection_name, field_name Migration.public(Migration.static({ removeField: FuncG([String, String]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'removeField' }); } })); Migration.public(Migration.async({ removeField: FuncG([String, String]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@removeIndex #collection_name, field_names, options Migration.public(Migration.static({ removeIndex: FuncG([ String, ListG(String), InterfaceG({ type: EnumG('hash', 'skiplist', 'persistent', 'geo', 'fulltext'), unique: MaybeG(Boolean), sparse: MaybeG(Boolean) }) ]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'removeIndex' }); } })); Migration.public(Migration.async({ removeIndex: FuncG([ String, ListG(String), InterfaceG({ type: EnumG('hash', 'skiplist', 'persistent', 'geo', 'fulltext'), unique: MaybeG(Boolean), sparse: MaybeG(Boolean) }) ]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); //@removeTimestamps # удаление полей createdAt, updatedAt, deletedAt #collection_name, options Migration.public(Migration.static({ removeTimestamps: FuncG([String, MaybeG(Object)]) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'removeTimestamps' }); } })); Migration.public(Migration.async({ removeTimestamps: FuncG([String, MaybeG(Object)]) }, { default: function*() { throw new Error('Not implemented specific method'); } })); // Special // нужен для того, чтобы обернуть операцию изменения множества документов в удобном виде внутри `change` чтобы не писать много кода в up и down // пример использования: /* ``` {wrap} = RC::Utils.co * без асинхронности - 'addField', 'reversible' и 'removeField' части DSL. * Они сохранят в метаданные все необходимое. * а реальный запускаемый код (автоматический или кастомынй) * будет в 'up' и 'down' @change -> @addField 'users', 'first_name', 'string' @addField 'users', 'last_name', 'string' @reversible wrap (dir)-> UsersCollection = @collection.facade.retrieveProxy USERS yield UsersCollection.forEach wrap (u)-> yield dir.up wrap -> [u.first_name, u.last_name] = u.full_name.split(' ') yield return yield dir.down wrap -> u.full_name = "#{u.first_name} #{u.last_name}" yield return yield u.save() yield return yield return @removeField 'users', 'full_name' return ``` */ Migration.public(Migration.static({ reversible: FuncG(AsyncFuncG(StructG({ up: AsyncFuncG(AsyncFunctionT), down: AsyncFuncG(AsyncFunctionT) }))) }, { default: function(...args) { this.prototype[iplSteps].push({ args, method: 'reversible' }); } })); // Custom // будет выполняться функция содержащая платформозависимый код. // пример использования /* ``` {wrap} = RC::Utils.co @up -> yield @execute wrap -> { db } = require '@arangodb' unless db._collection 'cucumbers' db._createDocumentCollection 'cucumbers', waitForSync: yes db._collection('cucumbers').ensureIndex type: 'hash' fields: ['type'] yield return yield return @down -> yield @execute wrap -> { db } = require '@arangodb' if db._collection 'cucumbers' db._drop 'cucumbers' yield return yield return ``` */ Migration.public(Migration.async({ execute: FuncG(AsyncFunctionT) }, { default: function*(lambda) { yield lambda.apply(this, []); } })); // управляющие методы //@migrate #direction - переопределять не надо, тут главная точка вызова снаружи. Migration.public(Migration.async({ migrate: FuncG([EnumG(UP, DOWN)]) }, { default: function*(direction) { switch (direction) { case UP: yield this.up(); break; case DOWN: yield this.down(); } } })); // если объявлена реализация метода `change`, то `up` и `down` объявлять не нужно (будут автоматически выдавать ответ на основе методанных объявленных в `change`) // использовать показанные выше DSL-методы надо именно в `change` Migration.public(Migration.static({ change: FuncG(Function) }, { default: function(lambda) { var base; if ((base = this.prototype)[iplSteps] == null) { base[iplSteps] = []; } lambda.apply(this, []); } })); // с кодом Collections и Records объявленных в приложении надо работать именно в `up` и в `down` // асинхронные, потому что будут работать с базой данных возможно через I/O // здесь должна быть объявлена логика "автоматическая" - если вызов `change` создает метаданные, то заиспользовать эти метаданные для выполнения. Если метаданных нет, то скорее всего либо это пока еще пустая миграция без кода вообще, либо в унаследованном классе будут переопределны и `up` и `down` Migration.public(Migration.async({ up: Function }, { default: function*() { var ref, ref1, steps; steps = (ref = (ref1 = this[iplSteps]) != null ? ref1.slice(0) : void 0) != null ? ref : []; yield forEach(steps, function*({method, args}) { var lambda; if (method === 'reversible') { [lambda] = args; return (yield lambda.call(this, { up: co.wrap(function*(f) { return (yield f()); }), down: co.wrap(function*() { return (yield Module.prototype.Promise.resolve()); }) })); } else { return (yield this[method](...args)); } }, this); } })); Migration.public(Migration.static({ up: FuncG(AsyncFunctionT) }, { default: function(lambda) { var base; if ((base = this.prototype)[iplSteps] == null) { base[iplSteps] = []; } this.public(this.async({ up: AsyncFunctionT }, { default: lambda })); } })); Migration.public(Migration.async({ down: Function }, { default: function*() { var ref, ref1, steps; steps = (ref = (ref1 = this[iplSteps]) != null ? ref1.slice(0) : void 0) != null ? ref : []; steps.reverse(); yield forEach(steps, function*({method, args}) { var collectionName, lambda, newName, oldName; if (method === 'reversible') { [lambda] = args; return (yield lambda.call(this, { up: co.wrap(function*() { return (yield Module.prototype.Promise.resolve()); }), down: co.wrap(function*(f) { return (yield f()); }) })); } else if (_.includes(['renameField', 'renameIndex'], method)) { [collectionName, oldName, newName] = args; return (yield this[method](collectionName, newName, oldName)); } else if (method === 'renameCollection') { [collectionName, newName] = args; return (yield this[method](newName, collectionName)); } else { return (yield this[REVERSE_MAP[method]](...args)); } }, this); } })); Migration.public(Migration.static({ down: FuncG(AsyncFunctionT) }, { default: function(lambda) { var base; if ((base = this.prototype)[iplSteps] == null) { base[iplSteps] = []; } this.public(this.async({ down: AsyncFunctionT }, { default: lambda })); } })); Migration.public(Migration.static(Migration.async({ restoreObject: FuncG([SubsetG(Module), Object], RecordInterface) }, { default: function*() { throw new Error(`restoreObject method not supported for ${this.name}`); } }))); Migration.public(Migration.static(Migration.async({ replicateObject: FuncG(RecordInterface, Object) }, { default: function*() { throw new Error(`replicateObject method not supported for ${this.name}`); } }))); Migration.initialize(); return Migration; }).call(this); }; }).call(this);