Skip to content

seiies/fist

 
 

Repository files navigation

fist NPM version Build Status Dependency Status devDependency Status

Fist - это nodejs-фреймворк для написания серверных приложений. Fist предлагает архитектуру, поддержка которой одинаково проста как для простых так и для сложных web-серверов.

var fist = require('fist');
var app = fist();

app.unit({
    path: 'time.appstart', 
    data: new Date()
});

app.unit({
    path: 'time.uptime',
    deps: ['time.appstart'],
    data: function (track, context) {
        return new Date() - context.getRes('time.appstart')
    }
});

app.unit({
    path: 'uptimeController', 
    deps: ['time.uptime'], 
    data: function (track, context) {
        return track.send(context.getRes('time.uptime'));
    }
});

app.route('/uptime/', 'uptimeController');

app.listen(1337);

Приложение фреймворка представляет собой плагинизируемый веб-сервер, состоящий из множества взаимосвязанных, зависящих друг от друга узлов, один из которых может обработать поступивший запрос, принятый роутером.

Приходящий в сервер запрос матчится первый подходящий локейшн, описанный в роутере. Каждый локейшн должен быть ассоциирован с узлом, операция разрешения которого запускается при успешном матчинге. Как правило, узел, ассоциированный с локейшеном является контролером и может выполнить ответ приложения. Но если он этого не сделает, то матчинг продолжится и операция повторится на следующем локейшне, до тех пор пока один из них не выполнит ответ.

//  при любом запросе проверять права на просмотр страницы
//  может отправить например 403 если прав нет
app.route('/ e', {
    // название роута
    name: 'checkRights',
    //  название узла с которым роут ассоциирован
    unit: 'rightChecker'
});

//  отображает главную страницу если есть права
app.route('/', {
    name: 'indexPage',
    unit: 'indexPageController'
});

//  настройки сайта
app.route('/settings/', {
    name: 'settingsPage',
    unit: 'settingsPageController'
});

//  далее идет декларация узлов
//  ***

Узлом приложения является инстанс класса fist/core/unit. Каждый узел должен иметь некоторый идентификатор и имплементирать метод data, который отвечает за разрешение узла. Узлы могут зависеть друг от друга, что должно быть указано в декларации. Это значит что до того как выполнится текущий узел, будут выполнены его зависимости, результаты которых будут доступны в методе data.

app.unit({
    //  идентификатор узла
    path: 'content.news',
    //  тело узла
    data: function () {
        var defer = vow.defer();
        doRequestForNews(function (err, res) {
            if ( err ) {
                defer.reject(err);
            } else {
                defer.resolve(res);
            }
        });
        return defer.promise();
    }
});

app.unit({
    path: 'indexPage',
    //  Зависимости узла
    deps: ['content.news'],
    data: function (track, context) {
        return track.send(doTemplate(context.getRes('content.news')));
    }
});

В каждый узел при его выполнении передается track и context. Объект track создается на каждый запрос и аггрегирует возможности request и response. Объект context - это контекст вызова узла, в нем содержатся результаты зависимостей узла.

Результатом разрешения узла является возвращенное из него значение или брошенное исключение. Если преполагается асинхронное выполнение узла, то можно возвратить promise. Узел считается разрешенным когда будет разрешено возвращенное из него значение.

fist работает на nodejs >= 0.10

#Приложение ##API ###fist([params]) Инстанцирует приложение

var fist = require('fist');
var configs = require('./configs');
//  Инстанцирую приложение
var app = fist(configs);

###app.listen() Запускает сервер приложения

app.listen(1337);

###app.plug(plugin...) Добавляет в приложение плагин.

app.plug(function (done) {
    this.myFeature = 42;
    done();
});

###app.route(pattern, data) Линкует роут с узлом.

app.route('/', {
    name: 'index',
    unit: 'indexController'
});

###app.unit(members[, statics]) Добавляет в приложение функциональный узел

app.unit({
    path: 'indexController',
    data: function () {
        
        return doSomething(this.__self.foo());
    }
}, {
    foo: function () {
        
        return 42;
    }
});

###app.ready() Запускает инициализацию приложения и возвращает promise, статус которого является статусом инициализации приложения. Это действие выполняется автоматически при старте сервера приложения. ###app.params Все параметры приложения, которые были переданы в конструктор ###app.renderers Объект, ключами которого являются имена шаблонов, а значениями - функции, которые вызываются в контексте track. Используется в методе track.render для шаблонизации данных.

###app._createCache(params) protected

Этот метод вызывается при инстанцировании. Возвращаемый объект реализует механизм кэширования и должен имплементировать некоторый интерфейс ####cache.set(key, value, maxAge, callback) ####cache.get(key, callback)

###app.channel(name) Создает канал событий. Канал событий для заданного name создается единожды.

##События ###sys ####pending Приложение начинает инициализироваться ####ready Приложение готово обрабатывать запросы ####eready Ошибка инициализации приложения ####request В приложение поступил запрос ####match Роутер сматчил запрос ####ematch Роутер не нашел подходящего шаблона для запроса ####response Приложение выполнило ответ

###ctx ####pending Начинается разрешение узла ####accept Узел разрешен без ошибки ####reject Узeл разрешен с ошибкой ####notify Узел послал уведомление

#Плагины Плагином приложения является обычная функция, которая вызывает резолвер по завершении своей работы если выполнение плагина происходит асинхронно. Плагины могут использоваться для конфигурирования и расширения возможностей приложения. Когда запускается сервер, сначала выполняются плагины, и когда они все отработают приложение начинает отвечать на запросы. Пока приложение инициализируется, запросы откладываются на обработку после инициализации. Если хотя бы один плагин был разрешен с ошибкой, то приложение проинициализируется, но начнет отвечать 500 Internal Server Error. Для того чтобы отклонить выполнение плагина нужно передать любой аргумент в резолвер или бросить исключение.

app.plug(function (done) {
    doSomethingAsync(function (err) {
        if ( err ) {
            done(err);
        } else {
            done();
        }
    });
});
app.plug(function () {
    doSomethingSync();
});

Если есть возможность устранить проблему, то по факту ее устранения можно перезапустить приложение вызвав app.ready(true) #Узлы Узлом приложения является инкапсулированая логическая часть приложения, динамичность поведения которой зависит от контекста вызова и параметров запроса. Узлы декларируются методом app.unit. Необходимо обязательно указать path узла и имплементировать метод data. Узел может зависеть от результатов других узлов, тогда нужно указать массив deps с идентификаторами узлов.

app.unit({
    path: 'fortyTwo',
    deps: ['someUnit'],
    data: function () {
        return 42;
    }
});

Объект передаваемый в метод app.unit является расширением прототипа узла. По умолчанию каждый узел наследует от fist/core/unit, но можно наследовать от любого узла. Для этого необходимо указать base, что является именем узла, от которого нужно унаследовать. В приложении могут быть абстрактные узлы, декларация которых не требуется, но от которых нужно унаследовать. Чтобы создать такой узел в его path первый символ должен быть не буквенный. Подобные узлы нет смысла добавлять в зависимости, потому что они не участвуют в операции разрешения.

app.unit({
    path: '_model',
    data: function () {
        return doAuth();
    }
})
app.unit({
    base: '_model',
    path: 'users',
    data: function (track, context) {
        return this.__base(track, context).then(getUsers);
    }
});

Также декларации узлов поддерживают mixin

function Stringifyable () {}

Stringifyable.prototype = {
    stringify: function () {
        return JSON.stringify(this);
    } 
};

app.unit({
    mix: [Stringifyable],
    path: 'test',
    data: function () {
        return this.stringify();
    }
});

###unit.addDeps(deps) Добавляет зависимости в узел.

app.unit({
    base: 'users',
    path: 'extendedUsers',
    __constructor: function (params) {
        this.__base(params);
        this.addDeps('userExtensions');
    },
    data: function (track, context) {
        return doSomethingWithUsers(this.__base(track, context));
    }
});

###unit.params Параметры узла. Все узлы инстанцируются со всеми параметрами приложения.

var config = {a: 42};
var app = new Framework(config);
app.unit({
    path: 'test',
    __constructor: function (params) {
        assert.deepEqual(params, config);
    }
});

###Кэширование У узлов есть возможность кэширования результатов выполнения. Для этого нужно указать в декларации свойство _maxAge и имплементировать метод _getCacheKeyParts который должен вернуть массив строк - токенов ключа для кэширования.

app.unit({
    path: 'newsPost',
    _maxAge: 500, //ms
    _getCacheKeyParts: function (track, context) {
        return [context.arg('blogId'), context.arg('postId')];
    },
    data: function () {
        return loadPost();
    }
})

Механизм кэширования можно заменить на свой. #Track Этот объект является контекстом запроса. В нем содержатся средства для чтения из request и для записи в response.

###track.header(name[, value]) Устанавливает заголовок в response или читает его из request

//  Поставить шапку в ответ
track.header('Content-Type', 'text/html');

track.header('Cookie'); // -> name=value

###track.cookie(name[, value[, opts]]) Читает куку из request или ставит ее в response

track.cookie('name'); // -> value

//  Выставить куку
track.cookie('name', 'value', {
    path: '/'
});

###track.send([status[, body]]) Выполняет ответ приложения

track.send(200, ':)');

Этот метод не выполняет непосредственной записи в response, но возвращает специальный объект, разрешение узла которым игнорирует вызов зависимых узлов, и по завершении разрешения узла-контроллера запись в response произойдет автоматически

var resolver = track.send();
//  записи в response не было, лишь был создан объект ответа приложения

return resolver;

###track.redirect([status, ]url) Создает перенаправление на клиенте, возвращает объект ответа приложения

return track.redirect(301, 'http://www.yandex.ru');

###track.buildPath(routeName[, opts]) Создает url из шаблона запроса

app.route('/(<pageName>/)', {name: 'anyPage', unit: 'universalController'});
//  ***
track.buildPath('anyPage', {pageName: 'test', x: 42}); // -> /test/?x=42

###track.url Объект распаршенного url запроса ###track.match Объект параметров запроса, сматчившихся на шаблон url-а ###track.route Имя маршрута, на который сматчился запрос

Как уже говорилось выше, объект track - это по сути, полиморфная обертка над request и response, в которой собраны самые популярные возможности, но с помощью него можно сделать не все. Например, нельзя прочитать уже установленный заголовок в response или узнать statusCode. Поэтому в track есть доступ к безопасным оберткам вокруг этих оригинальных объектов.

###track.request Содержит API для работы c request. ####track.request.getBody() Скачивает и парсит тело запроса

track.request.getBody().then(function (body) {
    //  do something with body
});

###track.response Содержит API для работы с response

#Контекст Второй аргумент, который передается в тело узла - context. Этот объект является контекстом вызова узла.

###context.result Объект, содержащий результаты зависимостей, разрешенных без ошибки ###context.errors Объект, содержащий результаты зависимостей, разрешенных с ошибкой ###context.getRes(path) Возвращает вложенное в context.result значение

//  нет гарантии что существует context.result.info.news.posts
var posts = ((context.result.info || {}).news || {}).posts; // так писать неудобно
// а так удобно и не страшны ReferenceError-ы
var posts = context.getRes('info.news.posts'); 

###context.getErr(path) Аналогично context.getRes только работает с объектом context.errors

###context.trigger(eventName, data) Поджигает событие особого типа на канале ctx

app.channel('sys').on('my-event', function (event) {
    // id запроса
    assert.isString(event.trackId);
    //  узел в котором было подожжено событие
    assert.strictEqual(event.path, 'demo');
    //  время, прошедшее с момента создания контекста узла до триггера
    assert.isNumber(event.time);

    assert.strictEqual(event.data, 42);
});

app.unit({
    path: 'demo',
    data: function (track, context) {
        context.trigger('my-event', 42);
    }
});

###context.append([deps]) Добавляет в контекст новые результаты зависимостей

context.append(['dep1', 'dep2']).then(function () {
    assert.ok(context.getRes('dep1'));
    assert.ok(context.getRes('dep1'));
});

###context.render(templateId) Шаблонизирует контекст шаблоном templateId

return context.render('index-page');

###context.arg(name) Возвращает аргумент вызова узла

#Роутер Роутер является неотъемлемой частью фреймворка. Синтаксис шаблонов роутера можно найти тут

About

nodejs application framework

Resources

License

Stars

Watchers

Forks

Packages

No packages published