├── index.js ├── src ├── __tests__ │ ├── basic │ │ ├── data │ │ │ ├── aliases.js │ │ │ ├── rulesPromise.js │ │ │ ├── rules.js │ │ │ ├── cascadeRules.js │ │ │ ├── cascadeRulesPromise.js │ │ │ ├── service.js │ │ │ └── deps.js │ │ ├── rulesWithDependencies.js │ │ ├── slotsRulesPromise.js │ │ ├── getPaths.js │ │ ├── slots.js │ │ ├── slotsRules.js │ │ └── cascadeRules.js │ └── realWorld │ │ ├── cms │ │ ├── routes.js │ │ ├── rules.js │ │ ├── server.js │ │ ├── service.js │ │ └── request.js │ │ └── cms.js ├── utils.js ├── context.js ├── slots.js └── branch.js ├── lib ├── __tests__ │ ├── basic │ │ ├── data │ │ │ ├── aliases.js.map │ │ │ ├── aliases.js │ │ │ ├── cascadeRules.js │ │ │ ├── rulesPromise.js.map │ │ │ ├── cascadeRules.js.map │ │ │ ├── rules.js.map │ │ │ ├── cascadeRulesPromise.js.map │ │ │ ├── rulesPromise.js │ │ │ ├── cascadeRulesPromise.js │ │ │ ├── rules.js │ │ │ ├── service.js.map │ │ │ ├── service.js │ │ │ ├── deps.js │ │ │ └── deps.js.map │ │ ├── rulesWithDependencies.js │ │ ├── slotsRulesPromise.js │ │ ├── rulesWithDependencies.js.map │ │ ├── slotsRulesPromise.js.map │ │ ├── getPaths.js.map │ │ ├── getPaths.js │ │ ├── slots.js │ │ ├── slotsRules.js │ │ ├── cascadeRules.js │ │ ├── slots.js.map │ │ ├── slotsRules.js.map │ │ └── cascadeRules.js.map │ └── realWorld │ │ ├── cms │ │ ├── routes.js.map │ │ ├── routes.js │ │ ├── rules.js │ │ ├── service.js │ │ ├── server.js │ │ ├── rules.js.map │ │ ├── service.js.map │ │ ├── server.js.map │ │ ├── request.js │ │ └── request.js.map │ │ ├── cms.js │ │ └── cms.js.map ├── utils.js ├── utils.js.map ├── context.js.map ├── context.js ├── branch.js ├── slots.js.map ├── slots.js └── branch.js.map ├── LICENSE ├── package.json ├── .gitignore └── README.md /index.js: -------------------------------------------------------------------------------- 1 | require("core-js/es6/promise"); 2 | module.exports = require('./lib/slots'); -------------------------------------------------------------------------------- /src/__tests__/basic/data/aliases.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "user": () => { 3 | this.get("users") 4 | } 5 | } -------------------------------------------------------------------------------- /src/__tests__/realWorld/cms/routes.js: -------------------------------------------------------------------------------- 1 | import Router from "routr-map"; 2 | export default new Router({ 3 | "pages": { 4 | ":id": { 5 | "comments": {} 6 | } 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/__tests__/basic/data/rulesPromise.js: -------------------------------------------------------------------------------- 1 | import service from "./service" 2 | 3 | export default { 4 | "route": function ({name, params: { id }}) { 5 | this.set(name, service[name + "Promise"](id)) 6 | } 7 | } -------------------------------------------------------------------------------- /src/__tests__/realWorld/cms.js: -------------------------------------------------------------------------------- 1 | import rules from "./cms/rules"; 2 | require('core-js'); 3 | describe('Content management Text', () => { 4 | rules.set("request", { url: "/pages/1"}); 5 | console.dir(rules.state.toJS()); 6 | }); 7 | 8 | -------------------------------------------------------------------------------- /src/__tests__/basic/data/rules.js: -------------------------------------------------------------------------------- 1 | import service from "./service" 2 | 3 | export default { 4 | "route": function ({name, params }) { 5 | params = params || {id: 1}; 6 | this.set(name, service[name](params.id)); 7 | } 8 | } -------------------------------------------------------------------------------- /src/__tests__/basic/data/cascadeRules.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "request": function (url) { 3 | this.set("route", url); 4 | }, 5 | "route": function (url) { 6 | this.set("users", url); 7 | }, 8 | "users": function () { 9 | 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/__tests__/basic/data/cascadeRulesPromise.js: -------------------------------------------------------------------------------- 1 | import Promise from "bluebird"; 2 | 3 | export default { 4 | "request": function (url) { 5 | this.set("route", url); 6 | }, 7 | "route": function (url) { 8 | this.set("users", Promise.resolve(url)); 9 | } 10 | } -------------------------------------------------------------------------------- /lib/__tests__/basic/data/aliases.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/basic/data/aliases.js"],"names":[],"mappings":";;;;;qBAAe;AACX,UAAM,EAAE,gBAAM;AACV,kBAAK,GAAG,CAAC,OAAO,CAAC,CAAA;KACpB;CACJ","file":"aliases.js","sourcesContent":["export default {\n \"user\": () => {\n this.get(\"users\")\n }\n}"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/data/aliases.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = { 7 | "user": function user() { 8 | undefined.get("users"); 9 | } 10 | }; 11 | module.exports = exports["default"]; 12 | //# sourceMappingURL=aliases.js.map -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/routes.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/realWorld/cms/routes.js"],"names":[],"mappings":";;;;;;;;wBAAmB,WAAW;;;;qBACf,0BAAW;AACtB,WAAO,EAAE;AACL,aAAK,EAAE;AACH,sBAAU,EAAE,EAAE;SACjB;KACJ;CACJ,CAAC","file":"routes.js","sourcesContent":["import Router from \"routr-map\";\nexport default new Router({\n \"pages\": {\n \":id\": {\n \"comments\": {}\n }\n }\n});\n"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/data/cascadeRules.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = { 7 | "request": function request(url) { 8 | this.set("route", url); 9 | }, 10 | "route": function route(url) { 11 | this.set("users", url); 12 | }, 13 | "users": function users() {} 14 | 15 | }; 16 | module.exports = exports["default"]; 17 | //# sourceMappingURL=cascadeRules.js.map -------------------------------------------------------------------------------- /src/__tests__/basic/rulesWithDependencies.js: -------------------------------------------------------------------------------- 1 | import Slots from "../../slots"; 2 | import rules from "./data/deps"; 3 | 4 | describe ('Slots Rules with dependencies', () => { 5 | const slots = new Slots(rules, {}); 6 | 7 | it ('should set state and execute rules', () => { 8 | slots.set('request', { url: 'Help' }).commit(); 9 | expect(slots.get('page.title')).toBe('Help'); 10 | expect(slots.get('request.url')).toBe('Help'); 11 | }); 12 | 13 | }); -------------------------------------------------------------------------------- /src/__tests__/basic/data/service.js: -------------------------------------------------------------------------------- 1 | import Promise from "core-js/es6/promise"; 2 | 3 | export default { 4 | page(id) { 5 | return { 6 | 1: { 7 | title: "Help" 8 | }, 9 | 2: { 10 | title: "About" 11 | }, 12 | 3: { 13 | title: "News" 14 | } 15 | }[id]; 16 | }, 17 | 18 | pagePromise(id) { 19 | return Promise.resolve(this.page(id)); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4 | 5 | var _cmsRules = require('./cms/rules'); 6 | 7 | var _cmsRules2 = _interopRequireDefault(_cmsRules); 8 | 9 | require('core-js'); 10 | describe('Content management Text', function () { 11 | _cmsRules2['default'].set('request', { url: '/pages/1' }); 12 | console.dir(_cmsRules2['default'].state.toJS()); 13 | }); 14 | //# sourceMappingURL=cms.js.map -------------------------------------------------------------------------------- /src/__tests__/basic/slotsRulesPromise.js: -------------------------------------------------------------------------------- 1 | import Slots from "../../slots"; 2 | import rules from "./data/rulesPromise"; 3 | 4 | describe ('Slots Rules', () => { 5 | 6 | it ('should set state and execute rules', () => { 7 | const slots = new Slots(rules, {}, (state) => { 8 | expect(slots.get('route.name')).toBe('page'); 9 | //expect(slots.get('page.title')).toBe('Help'); // TODO: test async 10 | }); 11 | slots.set('route', {name: 'page', params: {id: 1}}); 12 | }); 13 | }); -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/__tests__/realWorld/cms.js"],"names":[],"mappings":";;;;wBAAkB,aAAa;;;;AAC/B,OAAO,CAAC,SAAS,CAAC,CAAC;AACnB,QAAQ,CAAC,yBAAyB,EAAE,YAAM;AACtC,0BAAM,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,UAAU,EAAC,CAAC,CAAC;AACzC,WAAO,CAAC,GAAG,CAAC,sBAAM,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;CACnC,CAAC,CAAC","file":"cms.js","sourcesContent":["import rules from \"./cms/rules\";\nrequire('core-js');\ndescribe('Content management Text', () => {\n rules.set(\"request\", { url: \"/pages/1\"});\n console.dir(rules.state.toJS());\n});\n\n"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/data/rulesPromise.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/basic/data/rulesPromise.js"],"names":[],"mappings":";;;;;;;;uBAAoB,WAAW;;;;qBAEhB;AACX,WAAO,EAAE,eAAU,IAAsB,EAAE;YAAvB,IAAI,GAAL,IAAsB,CAArB,IAAI;YAAY,EAAE,GAAnB,IAAsB,CAAf,MAAM,CAAI,EAAE;;AAClC,YAAI,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAQ,IAAI,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;KAChD;CACJ","file":"rulesPromise.js","sourcesContent":["import service from \"./service\"\n\nexport default {\n \"route\": function ({name, params: { id }}) {\n this.set(name, service[name + \"Promise\"](id))\n }\n}"]} -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _routrMap = require("routr-map"); 10 | 11 | var _routrMap2 = _interopRequireDefault(_routrMap); 12 | 13 | exports["default"] = new _routrMap2["default"]({ 14 | "pages": { 15 | ":id": { 16 | "comments": {} 17 | } 18 | } 19 | }); 20 | module.exports = exports["default"]; 21 | //# sourceMappingURL=routes.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/data/cascadeRules.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/basic/data/cascadeRules.js"],"names":[],"mappings":";;;;;qBAAe;AACX,aAAS,EAAE,iBAAU,GAAG,EAAE;AACtB,YAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;KAC1B;AACD,WAAO,EAAE,eAAU,GAAG,EAAE;AACpB,YAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;KAC1B;AACD,WAAO,EAAE,iBAAY,EAEpB;;CAEJ","file":"cascadeRules.js","sourcesContent":["export default {\n \"request\": function (url) {\n this.set(\"route\", url);\n },\n \"route\": function (url) {\n this.set(\"users\", url);\n },\n \"users\": function () {\n\n }\n\n}"]} -------------------------------------------------------------------------------- /src/__tests__/realWorld/cms/rules.js: -------------------------------------------------------------------------------- 1 | import request from "./request"; 2 | import router from "./routes"; 3 | import Slots from "../../../slots"; 4 | 5 | export default new Slots({ 6 | "request": function (req) { 7 | req.route = router.match(req.url); 8 | let path = req.url.substr(1).replace("/", "."); 9 | req.params = req.params || {}; 10 | Object.assign(req.params, req.route.params); 11 | this.ctx.commit(); 12 | this.set(path, request.get(router.url(req.route)) 13 | .promise().then(res => res.body)); 14 | return req; 15 | } 16 | }); -------------------------------------------------------------------------------- /src/__tests__/basic/data/deps.js: -------------------------------------------------------------------------------- 1 | import service from "./service" 2 | 3 | export default { 4 | "request": function ({ url }) { 5 | let route = {}; 6 | if (url == "Help") { 7 | route.params = {id: 1}; 8 | route.name = "page"; 9 | this.set("route", route); 10 | } 11 | }, 12 | 13 | "route": { 14 | set: function ({name, params }, { url }) { 15 | params = params || {id: 1}; 16 | this.set("url", url); 17 | this.set(name, service[name](params.id)); 18 | }, 19 | deps: ["request"] 20 | } 21 | } -------------------------------------------------------------------------------- /lib/__tests__/basic/data/rules.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/basic/data/rules.js"],"names":[],"mappings":";;;;;;;;uBAAoB,WAAW;;;;qBAEhB;AACX,WAAO,EAAE,eAAU,IAAe,EAAE;YAAhB,IAAI,GAAL,IAAe,CAAd,IAAI;YAAE,MAAM,GAAb,IAAe,CAAR,MAAM;;AAC5B,cAAM,GAAG,MAAM,IAAI,EAAC,EAAE,EAAE,CAAC,EAAC,CAAC;AAC3B,YAAI,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;KAC5C;CACJ","file":"rules.js","sourcesContent":["import service from \"./service\"\n\nexport default {\n \"route\": function ({name, params }) {\n params = params || {id: 1};\n this.set(name, service[name](params.id));\n }\n}"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/data/cascadeRulesPromise.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/basic/data/cascadeRulesPromise.js"],"names":[],"mappings":";;;;;;;;wBAAoB,UAAU;;;;qBAEf;AACX,aAAS,EAAE,iBAAU,GAAG,EAAE;AACtB,YAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;KAC1B;AACD,WAAO,EAAE,eAAU,GAAG,EAAE;AACpB,YAAI,CAAC,GAAG,CAAC,OAAO,EAAE,sBAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;KAC3C;CACJ","file":"cascadeRulesPromise.js","sourcesContent":["import Promise from \"bluebird\";\n\nexport default {\n \"request\": function (url) {\n this.set(\"route\", url);\n },\n \"route\": function (url) {\n this.set(\"users\", Promise.resolve(url));\n }\n}"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/data/rulesPromise.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _service = require("./service"); 10 | 11 | var _service2 = _interopRequireDefault(_service); 12 | 13 | exports["default"] = { 14 | "route": function route(_ref) { 15 | var name = _ref.name; 16 | var id = _ref.params.id; 17 | 18 | this.set(name, _service2["default"][name + "Promise"](id)); 19 | } 20 | }; 21 | module.exports = exports["default"]; 22 | //# sourceMappingURL=rulesPromise.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/data/cascadeRulesPromise.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _bluebird = require("bluebird"); 10 | 11 | var _bluebird2 = _interopRequireDefault(_bluebird); 12 | 13 | exports["default"] = { 14 | "request": function request(url) { 15 | this.set("route", url); 16 | }, 17 | "route": function route(url) { 18 | this.set("users", _bluebird2["default"].resolve(url)); 19 | } 20 | }; 21 | module.exports = exports["default"]; 22 | //# sourceMappingURL=cascadeRulesPromise.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/data/rules.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _service = require("./service"); 10 | 11 | var _service2 = _interopRequireDefault(_service); 12 | 13 | exports["default"] = { 14 | "route": function route(_ref) { 15 | var name = _ref.name; 16 | var params = _ref.params; 17 | 18 | params = params || { id: 1 }; 19 | this.set(name, _service2["default"][name](params.id)); 20 | } 21 | }; 22 | module.exports = exports["default"]; 23 | //# sourceMappingURL=rules.js.map -------------------------------------------------------------------------------- /src/__tests__/realWorld/cms/server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import router from "./routes"; 3 | import service from "./service"; 4 | 5 | var server = express(); 6 | 7 | server.get("/", function(req, res) { 8 | let { url, method } = req; 9 | let route = router.match(url); 10 | let resource = service[route.name]; 11 | if (!resource) { 12 | res.status(404).send("Not Found"); 13 | return; 14 | } 15 | let action = resource[method]; 16 | if (!action) { 17 | res.status(405).send("Method not allowed"); 18 | return; 19 | } 20 | action(req, res).then((result) => { 21 | res.status(200).send(result); 22 | return; 23 | }); 24 | }); 25 | 26 | export default server; -------------------------------------------------------------------------------- /src/__tests__/realWorld/cms/service.js: -------------------------------------------------------------------------------- 1 | import Promise from "core-js/es6/promise"; 2 | 3 | let db = { 4 | pages: { 5 | 1: { 6 | title: "Help" 7 | }, 8 | 2: { 9 | title: "About" 10 | }, 11 | 3: { 12 | title: "News" 13 | } 14 | } 15 | }; 16 | 17 | export default { 18 | pages() { 19 | return { 20 | GET: ({params: {id}}) => { 21 | if (!id) { 22 | return Promise.reject("Bad request"); 23 | } else if (!db.pages[id]) { 24 | return Promise.reject("Not found"); 25 | } 26 | return Promise.resolve(db.page[id]); 27 | } 28 | } 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /lib/__tests__/basic/rulesWithDependencies.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 4 | 5 | var _slots = require("../../slots"); 6 | 7 | var _slots2 = _interopRequireDefault(_slots); 8 | 9 | var _dataDeps = require("./data/deps"); 10 | 11 | var _dataDeps2 = _interopRequireDefault(_dataDeps); 12 | 13 | describe("Slots Rules with dependencies", function () { 14 | var slots = new _slots2["default"](_dataDeps2["default"], {}); 15 | 16 | it("should set state and execute rules", function () { 17 | slots.set("request", { url: "Help" }).commit(); 18 | expect(slots.get("page.title")).toBe("Help"); 19 | expect(slots.get("request.url")).toBe("Help"); 20 | }); 21 | }); 22 | //# sourceMappingURL=rulesWithDependencies.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/data/service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/basic/data/service.js"],"names":[],"mappings":";;;;;;;;gCAAoB,qBAAqB;;;;qBAE1B;AACX,QAAI,EAAA,cAAC,EAAE,EAAE;AACL,eAAO,CAAA;AACH,aAAC,EAAE;AACC,qBAAK,EAAE,MAAM;aAChB;AACD,aAAC,EAAE;AACC,qBAAK,EAAE,OAAO;aACjB;AACD,aAAC,EAAE;AACC,qBAAK,EAAE,MAAM;aAChB;UACJ,CAAC,EAAE,CAAC,CAAC;KACT;;AAED,eAAW,EAAA,qBAAC,EAAE,EAAE;AACZ,eAAO,8BAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;KACzC;CACJ","file":"service.js","sourcesContent":["import Promise from \"core-js/es6/promise\";\n\nexport default {\n page(id) {\n return {\n 1: {\n title: \"Help\"\n },\n 2: {\n title: \"About\"\n },\n 3: {\n title: \"News\"\n }\n }[id];\n },\n\n pagePromise(id) {\n return Promise.resolve(this.page(id));\n }\n}"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/slotsRulesPromise.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 4 | 5 | var _slots = require("../../slots"); 6 | 7 | var _slots2 = _interopRequireDefault(_slots); 8 | 9 | var _dataRulesPromise = require("./data/rulesPromise"); 10 | 11 | var _dataRulesPromise2 = _interopRequireDefault(_dataRulesPromise); 12 | 13 | describe("Slots Rules", function () { 14 | 15 | it("should set state and execute rules", function () { 16 | var slots = new _slots2["default"](_dataRulesPromise2["default"], {}, function (state) { 17 | expect(slots.get("route.name")).toBe("page"); 18 | //expect(slots.get('page.title')).toBe('Help'); // TODO: test async 19 | }); 20 | slots.set("route", { name: "page", params: { id: 1 } }); 21 | }); 22 | }); 23 | //# sourceMappingURL=slotsRulesPromise.js.map -------------------------------------------------------------------------------- /src/__tests__/basic/getPaths.js: -------------------------------------------------------------------------------- 1 | import Slots from "../../slots"; 2 | import rules from "./data/rulesPromise"; 3 | import im from "immutable"; 4 | 5 | describe ('getPaths', () => { 6 | 7 | //it ('should return correct paths', () => { 8 | // let val = im.fromJS({a: {b: 1, c: {f: 6}, d:3}, c: 3, m: {v: 3}}); 9 | // console.log(Slots.getMapPaths(val).toJS()); 10 | //}); 11 | 12 | it ('should find all rules on the way', () => { 13 | let rules = { 14 | "request.route": (route) => { 15 | 16 | } 17 | }; 18 | 19 | let state = { 20 | "request": { 21 | "url": "url", 22 | "route": { 23 | "name": "" 24 | } 25 | }, 26 | "request2":"" 27 | }; 28 | 29 | //expect(Slots.getPaths([], state)).toEqual(Slots.getPaths(["request"], state.request)); 30 | 31 | }) 32 | }); -------------------------------------------------------------------------------- /lib/__tests__/basic/data/service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _coreJsEs6Promise = require("core-js/es6/promise"); 10 | 11 | var _coreJsEs6Promise2 = _interopRequireDefault(_coreJsEs6Promise); 12 | 13 | exports["default"] = { 14 | page: function page(id) { 15 | return ({ 16 | 1: { 17 | title: "Help" 18 | }, 19 | 2: { 20 | title: "About" 21 | }, 22 | 3: { 23 | title: "News" 24 | } 25 | })[id]; 26 | }, 27 | 28 | pagePromise: function pagePromise(id) { 29 | return _coreJsEs6Promise2["default"].resolve(this.page(id)); 30 | } 31 | }; 32 | module.exports = exports["default"]; 33 | //# sourceMappingURL=service.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/rulesWithDependencies.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/__tests__/basic/rulesWithDependencies.js"],"names":[],"mappings":";;;;qBAAkB,aAAa;;;;wBACb,aAAa;;;;AAE/B,QAAQ,CAAE,+BAA+B,EAAE,YAAM;AAC7C,QAAM,KAAK,GAAG,8CAAiB,EAAE,CAAC,CAAC;;AAEnC,MAAE,CAAE,oCAAoC,EAAE,YAAM;AAC5C,aAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAC/C,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7C,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;KACjD,CAAC,CAAC;CAEN,CAAC,CAAC","file":"rulesWithDependencies.js","sourcesContent":["import Slots from \"../../slots\";\nimport rules from \"./data/deps\";\n\ndescribe ('Slots Rules with dependencies', () => {\n const slots = new Slots(rules, {});\n\n it ('should set state and execute rules', () => {\n slots.set('request', { url: 'Help' }).commit();\n expect(slots.get('page.title')).toBe('Help');\n expect(slots.get('request.url')).toBe('Help');\n });\n\n});"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/slotsRulesPromise.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/__tests__/basic/slotsRulesPromise.js"],"names":[],"mappings":";;;;qBAAkB,aAAa;;;;gCACb,qBAAqB;;;;AAEvC,QAAQ,CAAE,aAAa,EAAE,YAAM;;AAE3B,MAAE,CAAE,oCAAoC,EAAE,YAAM;AAC5C,YAAM,KAAK,GAAG,sDAAiB,EAAE,EAAE,UAAC,KAAK,EAAK;AAC1C,kBAAM,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;;SAEhD,CAAC,CAAC;AACH,aAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAC,EAAE,EAAE,CAAC,EAAC,EAAC,CAAC,CAAC;KACvD,CAAC,CAAC;CACN,CAAC,CAAC","file":"slotsRulesPromise.js","sourcesContent":["import Slots from \"../../slots\";\nimport rules from \"./data/rulesPromise\";\n\ndescribe ('Slots Rules', () => {\n\n it ('should set state and execute rules', () => {\n const slots = new Slots(rules, {}, (state) => {\n expect(slots.get('route.name')).toBe('page');\n //expect(slots.get('page.title')).toBe('Help'); // TODO: test async\n });\n slots.set('route', {name: 'page', params: {id: 1}});\n });\n});"]} -------------------------------------------------------------------------------- /src/__tests__/realWorld/cms/request.js: -------------------------------------------------------------------------------- 1 | import supertest from "supertest"; 2 | import superagent from "superagent"; 3 | import Promise from "core-js/es6/promise"; 4 | import server from "./server"; 5 | 6 | Request = superagent.Request; 7 | 8 | Request.prototype.promise = function() { 9 | var req = this; 10 | var error; 11 | 12 | return new Promise(function(resolve, reject) { 13 | req.end(function(err, res) { 14 | if (typeof res !== "undefined" && res.status >= 400) { 15 | var msg = 'cannot ' + req.method + ' ' + req.url + ' (' + res.status + ')'; 16 | error = new Error(msg); 17 | error.status = res.status; 18 | error.body = res.body; 19 | error.res = res; 20 | reject(error); 21 | } else if (err) { 22 | reject(new Error(err)); 23 | } else { 24 | resolve(res); 25 | } 26 | }); 27 | }) 28 | }; 29 | 30 | export default supertest(server); -------------------------------------------------------------------------------- /lib/__tests__/basic/data/deps.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _service = require("./service"); 10 | 11 | var _service2 = _interopRequireDefault(_service); 12 | 13 | exports["default"] = { 14 | "request": function request(_ref) { 15 | var url = _ref.url; 16 | 17 | var route = {}; 18 | if (url == "Help") { 19 | route.params = { id: 1 }; 20 | route.name = "page"; 21 | this.set("route", route); 22 | } 23 | }, 24 | 25 | "route": { 26 | set: function set(_ref2, _ref3) { 27 | var name = _ref2.name; 28 | var params = _ref2.params; 29 | var url = _ref3.url; 30 | 31 | params = params || { id: 1 }; 32 | this.set("url", url); 33 | this.set(name, _service2["default"][name](params.id)); 34 | }, 35 | deps: ["request"] 36 | } 37 | }; 38 | module.exports = exports["default"]; 39 | //# sourceMappingURL=deps.js.map -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexey Frolov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/rules.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _request2 = require("./request"); 10 | 11 | var _request3 = _interopRequireDefault(_request2); 12 | 13 | var _routes = require("./routes"); 14 | 15 | var _routes2 = _interopRequireDefault(_routes); 16 | 17 | var _slots = require("../../../slots"); 18 | 19 | var _slots2 = _interopRequireDefault(_slots); 20 | 21 | exports["default"] = new _slots2["default"]({ 22 | "request": function request(req) { 23 | req.route = _routes2["default"].match(req.url); 24 | var path = req.url.substr(1).replace("/", "."); 25 | req.params = req.params || {}; 26 | Object.assign(req.params, req.route.params); 27 | this.ctx.commit(); 28 | this.set(path, _request3["default"].get(_routes2["default"].url(req.route)).promise().then(function (res) { 29 | return res.body; 30 | })); 31 | return req; 32 | } 33 | }); 34 | module.exports = exports["default"]; 35 | //# sourceMappingURL=rules.js.map -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import util from "util"; 2 | 3 | export function isFunction(v) { 4 | return Object.prototype.toString.call(v) === "[object Function]"; 5 | } 6 | 7 | export function isObject(v) { 8 | return v !== null && typeof v === 'object'; 9 | } 10 | 11 | export function isPromise(v) { 12 | return v && isFunction(v.then); 13 | } 14 | 15 | export function isImmutable(v) { 16 | return v && isFunction(v.toJS); 17 | } 18 | 19 | export function isArray(v) { 20 | return Object.prototype.toString.call(v) === "[object Array]"; 21 | } 22 | 23 | export function isString(v) { 24 | return Object.prototype.toString.call(v) === "[object String]"; 25 | } 26 | 27 | export function isNumber(v) { 28 | return Object.prototype.toString.call(v) === "[object Number]"; 29 | } 30 | 31 | export function toJS(v) { 32 | return v && isImmutable(v) && v.toJS() || v; 33 | } 34 | 35 | export function insp(value) { 36 | value = isImmutable(value) ? value.toJS() : value; 37 | value = isArray(value) ? value.join(".") : value; 38 | value = isPromise(value) ? "__promise__" : value; 39 | return util.inspect(value, {colors: typeof window === "undefined", depth: 0}).replace('\n', ''); 40 | } -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _coreJsEs6Promise = require("core-js/es6/promise"); 10 | 11 | var _coreJsEs6Promise2 = _interopRequireDefault(_coreJsEs6Promise); 12 | 13 | var db = { 14 | pages: { 15 | 1: { 16 | title: "Help" 17 | }, 18 | 2: { 19 | title: "About" 20 | }, 21 | 3: { 22 | title: "News" 23 | } 24 | } 25 | }; 26 | 27 | exports["default"] = { 28 | pages: function pages() { 29 | return { 30 | GET: function GET(_ref) { 31 | var id = _ref.params.id; 32 | 33 | if (!id) { 34 | return _coreJsEs6Promise2["default"].reject("Bad request"); 35 | } else if (!db.pages[id]) { 36 | return _coreJsEs6Promise2["default"].reject("Not found"); 37 | } 38 | return _coreJsEs6Promise2["default"].resolve(db.page[id]); 39 | } 40 | }; 41 | } 42 | }; 43 | module.exports = exports["default"]; 44 | //# sourceMappingURL=service.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/getPaths.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/__tests__/basic/getPaths.js"],"names":[],"mappings":";;;;qBAAkB,aAAa;;;;gCACb,qBAAqB;;;;yBACxB,WAAW;;;;AAE1B,QAAQ,CAAE,UAAU,EAAE,YAAM;;;;;;;AAOxB,MAAE,CAAE,kCAAkC,EAAE,YAAM;AAC1C,YAAI,KAAK,GAAG;AACR,2BAAe,EAAE,sBAAC,KAAK,EAAK,EAE3B;SACJ,CAAC;;AAEF,YAAI,KAAK,GAAG;AACR,qBAAS,EAAE;AACP,qBAAK,EAAE,KAAK;AACZ,uBAAO,EAAE;AACL,0BAAM,EAAE,EAAE;iBACb;aACJ;AACD,sBAAU,EAAC,EAAE;SAChB,CAAC;;;KAIL,CAAC,CAAA;CACL,CAAC,CAAC","file":"getPaths.js","sourcesContent":["import Slots from \"../../slots\";\nimport rules from \"./data/rulesPromise\";\nimport im from \"immutable\";\n\ndescribe ('getPaths', () => {\n\n //it ('should return correct paths', () => {\n // let val = im.fromJS({a: {b: 1, c: {f: 6}, d:3}, c: 3, m: {v: 3}});\n // console.log(Slots.getMapPaths(val).toJS());\n //});\n\n it ('should find all rules on the way', () => {\n let rules = {\n \"request.route\": (route) => {\n\n }\n };\n\n let state = {\n \"request\": {\n \"url\": \"url\",\n \"route\": {\n \"name\": \"\"\n }\n },\n \"request2\":\"\"\n };\n\n //expect(Slots.getPaths([], state)).toEqual(Slots.getPaths([\"request\"], state.request));\n\n })\n});"]} -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _express = require("express"); 10 | 11 | var _express2 = _interopRequireDefault(_express); 12 | 13 | var _routes = require("./routes"); 14 | 15 | var _routes2 = _interopRequireDefault(_routes); 16 | 17 | var _service = require("./service"); 18 | 19 | var _service2 = _interopRequireDefault(_service); 20 | 21 | var server = (0, _express2["default"])(); 22 | 23 | server.get("/", function (req, res) { 24 | var url = req.url; 25 | var method = req.method; 26 | 27 | var route = _routes2["default"].match(url); 28 | var resource = _service2["default"][route.name]; 29 | if (!resource) { 30 | res.status(404).send("Not Found"); 31 | return; 32 | } 33 | var action = resource[method]; 34 | if (!action) { 35 | res.status(405).send("Method not allowed"); 36 | return; 37 | } 38 | action(req, res).then(function (result) { 39 | res.status(200).send(result); 40 | return; 41 | }); 42 | }); 43 | 44 | exports["default"] = server; 45 | module.exports = exports["default"]; 46 | //# sourceMappingURL=server.js.map -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/rules.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/realWorld/cms/rules.js"],"names":[],"mappings":";;;;;;;;wBAAoB,WAAW;;;;sBACZ,UAAU;;;;qBACX,gBAAgB;;;;qBAEnB,uBAAU;AACrB,aAAS,EAAE,iBAAU,GAAG,EAAG;AACvB,WAAG,CAAC,KAAK,GAAG,oBAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAClC,YAAI,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC/C,WAAG,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;AAC9B,cAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC5C,YAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;AAClB,YAAI,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAQ,GAAG,CAAC,oBAAO,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAC5C,OAAO,EAAE,CAAC,IAAI,CAAC,UAAA,GAAG;mBAAI,GAAG,CAAC,IAAI;SAAA,CAAC,CAAC,CAAC;AACtC,eAAO,GAAG,CAAC;KACd;CACJ,CAAC","file":"rules.js","sourcesContent":["import request from \"./request\";\nimport router from \"./routes\";\nimport Slots from \"../../../slots\";\n\nexport default new Slots({\n \"request\": function (req) {\n req.route = router.match(req.url);\n let path = req.url.substr(1).replace(\"/\", \".\");\n req.params = req.params || {};\n Object.assign(req.params, req.route.params);\n this.ctx.commit();\n this.set(path, request.get(router.url(req.route))\n .promise().then(res => res.body));\n return req;\n }\n});"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/getPaths.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 4 | 5 | var _slots = require("../../slots"); 6 | 7 | var _slots2 = _interopRequireDefault(_slots); 8 | 9 | var _dataRulesPromise = require("./data/rulesPromise"); 10 | 11 | var _dataRulesPromise2 = _interopRequireDefault(_dataRulesPromise); 12 | 13 | var _immutable = require("immutable"); 14 | 15 | var _immutable2 = _interopRequireDefault(_immutable); 16 | 17 | describe("getPaths", function () { 18 | 19 | //it ('should return correct paths', () => { 20 | // let val = im.fromJS({a: {b: 1, c: {f: 6}, d:3}, c: 3, m: {v: 3}}); 21 | // console.log(Slots.getMapPaths(val).toJS()); 22 | //}); 23 | 24 | it("should find all rules on the way", function () { 25 | var rules = { 26 | "request.route": function requestRoute(route) {} 27 | }; 28 | 29 | var state = { 30 | "request": { 31 | "url": "url", 32 | "route": { 33 | "name": "" 34 | } 35 | }, 36 | "request2": "" 37 | }; 38 | 39 | //expect(Slots.getPaths([], state)).toEqual(Slots.getPaths(["request"], state.request)); 40 | }); 41 | }); 42 | //# sourceMappingURL=getPaths.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/data/deps.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/basic/data/deps.js"],"names":[],"mappings":";;;;;;;;uBAAoB,WAAW;;;;qBAEhB;AACX,aAAS,EAAE,iBAAU,IAAO,EAAE;YAAP,GAAG,GAAL,IAAO,CAAL,GAAG;;AACtB,YAAI,KAAK,GAAG,EAAE,CAAC;AACf,YAAI,GAAG,IAAI,MAAM,EAAE;AACf,iBAAK,CAAC,MAAM,GAAG,EAAC,EAAE,EAAE,CAAC,EAAC,CAAC;AACvB,iBAAK,CAAC,IAAI,GAAG,MAAM,CAAC;AACpB,gBAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;SAC5B;KACJ;;AAED,WAAO,EAAE;AACL,WAAG,EAAE,aAAU,KAAe,EAAE,KAAO,EAAE;gBAAzB,IAAI,GAAL,KAAe,CAAd,IAAI;gBAAE,MAAM,GAAb,KAAe,CAAR,MAAM;gBAAM,GAAG,GAAL,KAAO,CAAL,GAAG;;AACjC,kBAAM,GAAG,MAAM,IAAI,EAAC,EAAE,EAAE,CAAC,EAAC,CAAC;AAC3B,gBAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACrB,gBAAI,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;SAC5C;AACD,YAAI,EAAE,CAAC,SAAS,CAAC;KACpB;CACJ","file":"deps.js","sourcesContent":["import service from \"./service\"\n\nexport default {\n \"request\": function ({ url }) {\n let route = {};\n if (url == \"Help\") {\n route.params = {id: 1};\n route.name = \"page\";\n this.set(\"route\", route);\n }\n },\n\n \"route\": {\n set: function ({name, params }, { url }) {\n params = params || {id: 1};\n this.set(\"url\", url);\n this.set(name, service[name](params.id));\n },\n deps: [\"request\"]\n }\n}"]} -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/realWorld/cms/service.js"],"names":[],"mappings":";;;;;;;;gCAAoB,qBAAqB;;;;AAEzC,IAAI,EAAE,GAAG;AACL,SAAK,EAAE;AACH,SAAC,EAAE;AACC,iBAAK,EAAE,MAAM;SAChB;AACD,SAAC,EAAE;AACC,iBAAK,EAAE,OAAO;SACjB;AACD,SAAC,EAAE;AACC,iBAAK,EAAE,MAAM;SAChB;KACJ;CACJ,CAAC;;qBAEa;AACX,SAAK,EAAA,iBAAG;AACJ,eAAO;AACH,eAAG,EAAE,aAAC,IAAc,EAAK;oBAAT,EAAE,GAAZ,IAAc,CAAb,MAAM,CAAG,EAAE;;AACd,oBAAI,CAAC,EAAE,EAAE;AACL,2BAAO,8BAAQ,MAAM,CAAC,aAAa,CAAC,CAAC;iBACxC,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE;AACtB,2BAAO,8BAAQ,MAAM,CAAC,WAAW,CAAC,CAAC;iBACtC;AACD,uBAAO,8BAAQ,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;aACvC;SACJ,CAAA;KAEJ;CACJ","file":"service.js","sourcesContent":["import Promise from \"core-js/es6/promise\";\n\nlet db = {\n pages: {\n 1: {\n title: \"Help\"\n },\n 2: {\n title: \"About\"\n },\n 3: {\n title: \"News\"\n }\n }\n};\n\nexport default {\n pages() {\n return {\n GET: ({params: {id}}) => {\n if (!id) {\n return Promise.reject(\"Bad request\");\n } else if (!db.pages[id]) {\n return Promise.reject(\"Not found\");\n }\n return Promise.resolve(db.page[id]);\n }\n }\n\n }\n}"]} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slt", 3 | "version": "2.0.0", 4 | "description": "Take care of your state", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "nodemon -w __tests__ -w ./src -e js,jsx -x npm test" 9 | }, 10 | "author": "Alexey Frolov ", 11 | "license": "MIT", 12 | "keywords": [ 13 | "react", 14 | "flux", 15 | "relay", 16 | "state", 17 | "immutable", 18 | "isomorphic", 19 | "promises" 20 | ], 21 | "devDependencies": { 22 | "babel-jest": "^5.2.0", 23 | "jest-cli": "^0.4.5", 24 | "express": "^4.13.1", 25 | "supertest": "^1.0.1", 26 | "core-js": "^0.9.18" 27 | }, 28 | "dependencies": { 29 | "debug": "^2.2.0", 30 | "immutable": "^3.7.4" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git@github.com:AlexeyFrolov/slt.git" 35 | }, 36 | "jest": { 37 | "testFileExtensions": [ 38 | "es6", 39 | "js" 40 | ], 41 | "moduleFileExtensions": [ 42 | "js", 43 | "json", 44 | "es6" 45 | ], 46 | "unmockedModulePathPatterns": [ 47 | "/lib/*", 48 | "/src/*", 49 | "/node_modules/", 50 | "/data/" 51 | ], 52 | "testPathIgnorePatterns": [ 53 | "/node_modules/", 54 | "/data/", 55 | "/cms/", 56 | "/src/" 57 | ], 58 | "collectCoverage": false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/__tests__/basic/slots.js: -------------------------------------------------------------------------------- 1 | import Slots from "../../slots"; 2 | 3 | describe('Slots', () => { 4 | let slots; 5 | it ('should set Rules and State', () => { 6 | let set = function (route) { 7 | }; 8 | let rules = { 9 | "route": set 10 | }; 11 | 12 | let state = { 13 | s: { 14 | b: [1,2,3] 15 | }, 16 | x: [0] 17 | }; 18 | 19 | slots = new Slots(rules, state); 20 | expect(slots.rules).toEqual( { 21 | "route": { 22 | set: set, 23 | deps: [] 24 | } 25 | }); 26 | 27 | expect(slots.getState().toJS()).toEqual(state); 28 | }); 29 | 30 | it ('should set and get', () => { 31 | let path = ["s", "b"]; 32 | slots.set(path, [4]).commit(); 33 | expect(slots.get(path)).toEqual([4,2,3]); 34 | //expect(slots.get(path.join('.'))).toEqual([4,2,3]); 35 | //expect(slots.get()).toEqual({ 36 | // s: { 37 | // b: [4,2,3] 38 | // }, 39 | // x: [0] 40 | //}); 41 | }); 42 | 43 | //const flox2 = new Slots({}, {}); 44 | //flox2.set('route', {name: 'page', params: {id: 1}}).commit(); 45 | //const flox3 = new Slots({}, flox2.getState().toJS()); 46 | //it ('should restore state', () => { 47 | // expect(flox3.getState().toJS()).toEqual(flox2.getState().toJS()); 48 | //}); 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/server.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/realWorld/cms/server.js"],"names":[],"mappings":";;;;;;;;uBAAoB,SAAS;;;;sBACV,UAAU;;;;uBACR,WAAW;;;;AAEhC,IAAI,MAAM,GAAG,2BAAS,CAAC;;AAEvB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAS,GAAG,EAAE,GAAG,EAAE;QACzB,GAAG,GAAa,GAAG,CAAnB,GAAG;QAAE,MAAM,GAAK,GAAG,CAAd,MAAM;;AACjB,QAAI,KAAK,GAAG,oBAAO,KAAK,CAAC,GAAG,CAAC,CAAC;AAC9B,QAAI,QAAQ,GAAG,qBAAQ,KAAK,CAAC,IAAI,CAAC,CAAC;AACnC,QAAI,CAAC,QAAQ,EAAE;AACX,WAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAClC,eAAO;KACV;AACD,QAAI,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9B,QAAI,CAAC,MAAM,EAAE;AACT,WAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAC3C,eAAO;KACV;AACD,UAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,UAAC,MAAM,EAAK;AAC9B,WAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7B,eAAO;KACV,CAAC,CAAC;CACN,CAAC,CAAC;;qBAEY,MAAM","file":"server.js","sourcesContent":["import express from \"express\";\nimport router from \"./routes\";\nimport service from \"./service\";\n\nvar server = express();\n\nserver.get(\"/\", function(req, res) {\n let { url, method } = req;\n let route = router.match(url);\n let resource = service[route.name];\n if (!resource) {\n res.status(404).send(\"Not Found\");\n return;\n }\n let action = resource[method];\n if (!action) {\n res.status(405).send(\"Method not allowed\");\n return;\n }\n action(req, res).then((result) => {\n res.status(200).send(result);\n return;\n });\n});\n\nexport default server;"]} -------------------------------------------------------------------------------- /src/__tests__/basic/slotsRules.js: -------------------------------------------------------------------------------- 1 | import Slots from "../../slots"; 2 | import rules from "./data/rules"; 3 | 4 | describe ('Slots Rules', () => { 5 | const slots = new Slots(rules, {}); 6 | 7 | it ('should set state and execute rules', () => { 8 | slots.set('route', {name: 'page', params: {id: 1}}).commit(); 9 | expect(slots.get('page.title')).toBe('Help'); 10 | }); 11 | 12 | const cb2 = jasmine.createSpy(); 13 | const slots2 = new Slots(rules, {}); 14 | slots2.onDidSet(cb2); 15 | it ('should call callback if state has changed', () => { 16 | slots2.set('route', {name: 'page', params: {id: 1}}).commit(); 17 | expect(slots2.get('page.title')).toBe('Help'); 18 | expect(cb2).toHaveBeenCalled(); 19 | 20 | const cb4 = jasmine.createSpy(); 21 | const slots4 = new Slots(rules, slots2.getState().toJS()); 22 | slots4.onDidSet(cb4); 23 | slots4.set('route', {name: 'page', params: {id: 1}}).commit(); 24 | expect(cb4).toHaveBeenCalled(); 25 | }); 26 | 27 | const slots3 = new Slots(rules, {}); 28 | 29 | it ('should set state and execute rules', () => { 30 | slots3.set([], {route:{ name: 'page', params: {id: 1} } }).commit(); 31 | expect(slots3.get('page.title')).toBe('Help'); 32 | }); 33 | 34 | //const slots5 = new Slots(rules, {}); 35 | // 36 | //it ('should set state and execute rules', () => { 37 | // slots5.set('route.name', 'page').commit(); 38 | // expect(slots5.get('page.title')).toBe('Help'); 39 | //}); 40 | 41 | }); -------------------------------------------------------------------------------- /src/__tests__/basic/cascadeRules.js: -------------------------------------------------------------------------------- 1 | import rules from "./data/cascadeRules"; 2 | import Slots from "../../slots"; 3 | 4 | const slots = new Slots(rules, {}); 5 | 6 | describe('Slots cascade', () => { 7 | 8 | it('should set state and execute rules on scalar', () => { 9 | let url = "test"; 10 | slots.set('request', url).commit(); 11 | expect(slots.get('request')).toBe(url); 12 | expect(slots.get('users')).toBe(url); 13 | 14 | }); 15 | 16 | it('should update state and execute rules on scalar', () => { 17 | let url2 = "test2"; 18 | slots.set('request', url2).commit(); 19 | expect(slots.get('request')).toBe(url2); 20 | expect(slots.get('users')).toBe(url2); 21 | }); 22 | 23 | it('should set w/o path', () => { 24 | let url3 = "test3"; 25 | slots.set([], { request: url3 }).commit(); 26 | expect(slots.get('request')).toBe(url3); 27 | expect(slots.get('users')).toBe(url3); 28 | }); 29 | 30 | it('should multi set w/o path', () => { 31 | let url4 = "test4"; 32 | slots.set([], { request: url4, any: 3, another: { test: "test" } }).commit(); 33 | expect(slots.get('request')).toBe(url4); 34 | expect(slots.get('users')).toBe(url4); 35 | expect(slots.get('any')).toBe(3); 36 | expect(slots.get('another')).toEqual({ test: "test" }); 37 | }); 38 | 39 | //it('should overwrite by rule', () => { 40 | // let url5 = "test5"; 41 | // slots.set([], { request: url5, users: 3 }).commit(); 42 | // expect(slots.get('request')).toBe(url5); 43 | // expect(slots.get('users')).toBe(url5); 44 | //}); 45 | }); -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _supertest = require("supertest"); 10 | 11 | var _supertest2 = _interopRequireDefault(_supertest); 12 | 13 | var _superagent = require("superagent"); 14 | 15 | var _superagent2 = _interopRequireDefault(_superagent); 16 | 17 | var _coreJsEs6Promise = require("core-js/es6/promise"); 18 | 19 | var _coreJsEs6Promise2 = _interopRequireDefault(_coreJsEs6Promise); 20 | 21 | var _server = require("./server"); 22 | 23 | var _server2 = _interopRequireDefault(_server); 24 | 25 | Request = _superagent2["default"].Request; 26 | 27 | Request.prototype.promise = function () { 28 | var req = this; 29 | var error; 30 | 31 | return new _coreJsEs6Promise2["default"](function (resolve, reject) { 32 | req.end(function (err, res) { 33 | if (typeof res !== "undefined" && res.status >= 400) { 34 | var msg = "cannot " + req.method + " " + req.url + " (" + res.status + ")"; 35 | error = new Error(msg); 36 | error.status = res.status; 37 | error.body = res.body; 38 | error.res = res; 39 | reject(error); 40 | } else if (err) { 41 | reject(new Error(err)); 42 | } else { 43 | resolve(res); 44 | } 45 | }); 46 | }); 47 | }; 48 | 49 | exports["default"] = (0, _supertest2["default"])(_server2["default"]); 50 | module.exports = exports["default"]; 51 | //# sourceMappingURL=request.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/slots.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4 | 5 | var _slots = require('../../slots'); 6 | 7 | var _slots2 = _interopRequireDefault(_slots); 8 | 9 | describe('Slots', function () { 10 | var slots = undefined; 11 | it('should set Rules and State', function () { 12 | var set = function set(route) {}; 13 | var rules = { 14 | 'route': set 15 | }; 16 | 17 | var state = { 18 | s: { 19 | b: [1, 2, 3] 20 | }, 21 | x: [0] 22 | }; 23 | 24 | slots = new _slots2['default'](rules, state); 25 | expect(slots.rules).toEqual({ 26 | 'route': { 27 | set: set, 28 | deps: [] 29 | } 30 | }); 31 | 32 | expect(slots.getState().toJS()).toEqual(state); 33 | }); 34 | 35 | it('should set and get', function () { 36 | var path = ['s', 'b']; 37 | slots.set(path, [4]).commit(); 38 | expect(slots.get(path)).toEqual([4, 2, 3]); 39 | //expect(slots.get(path.join('.'))).toEqual([4,2,3]); 40 | //expect(slots.get()).toEqual({ 41 | // s: { 42 | // b: [4,2,3] 43 | // }, 44 | // x: [0] 45 | //}); 46 | }); 47 | 48 | //const flox2 = new Slots({}, {}); 49 | //flox2.set('route', {name: 'page', params: {id: 1}}).commit(); 50 | //const flox3 = new Slots({}, flox2.getState().toJS()); 51 | //it ('should restore state', () => { 52 | // expect(flox3.getState().toJS()).toEqual(flox2.getState().toJS()); 53 | //}); 54 | }); 55 | //# sourceMappingURL=slots.js.map -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.isFunction = isFunction; 7 | exports.isObject = isObject; 8 | exports.isPromise = isPromise; 9 | exports.isImmutable = isImmutable; 10 | exports.isArray = isArray; 11 | exports.isString = isString; 12 | exports.isNumber = isNumber; 13 | exports.toJS = toJS; 14 | exports.insp = insp; 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 17 | 18 | var _util = require("util"); 19 | 20 | var _util2 = _interopRequireDefault(_util); 21 | 22 | function isFunction(v) { 23 | return Object.prototype.toString.call(v) === "[object Function]"; 24 | } 25 | 26 | function isObject(v) { 27 | return v !== null && typeof v === "object"; 28 | } 29 | 30 | function isPromise(v) { 31 | return v && isFunction(v.then); 32 | } 33 | 34 | function isImmutable(v) { 35 | return v && isFunction(v.toJS); 36 | } 37 | 38 | function isArray(v) { 39 | return Object.prototype.toString.call(v) === "[object Array]"; 40 | } 41 | 42 | function isString(v) { 43 | return Object.prototype.toString.call(v) === "[object String]"; 44 | } 45 | 46 | function isNumber(v) { 47 | return Object.prototype.toString.call(v) === "[object Number]"; 48 | } 49 | 50 | function toJS(v) { 51 | return v && isImmutable(v) && v.toJS() || v; 52 | } 53 | 54 | function insp(value) { 55 | value = isImmutable(value) ? value.toJS() : value; 56 | value = isArray(value) ? value.join(".") : value; 57 | value = isPromise(value) ? "__promise__" : value; 58 | return _util2["default"].inspect(value, { colors: typeof window === "undefined", depth: 0 }).replace("\n", ""); 59 | } 60 | //# sourceMappingURL=utils.js.map -------------------------------------------------------------------------------- /lib/__tests__/realWorld/cms/request.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../../src/__tests__/realWorld/cms/request.js"],"names":[],"mappings":";;;;;;;;yBAAsB,WAAW;;;;0BACV,YAAY;;;;gCACf,qBAAqB;;;;sBACtB,UAAU;;;;AAE7B,OAAO,GAAG,wBAAW,OAAO,CAAC;;AAE7B,OAAO,CAAC,SAAS,CAAC,OAAO,GAAG,YAAW;AACnC,QAAI,GAAG,GAAG,IAAI,CAAC;AACf,QAAI,KAAK,CAAC;;AAEV,WAAO,kCAAY,UAAS,OAAO,EAAE,MAAM,EAAE;AACzC,WAAG,CAAC,GAAG,CAAC,UAAS,GAAG,EAAE,GAAG,EAAE;AACvB,gBAAI,OAAO,GAAG,KAAK,WAAW,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,EAAE;AACjD,oBAAI,GAAG,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;AAC3E,qBAAK,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;AACvB,qBAAK,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;AAC1B,qBAAK,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;AACtB,qBAAK,CAAC,GAAG,GAAG,GAAG,CAAC;AAChB,sBAAM,CAAC,KAAK,CAAC,CAAC;aACjB,MAAM,IAAI,GAAG,EAAE;AACZ,sBAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;aAC1B,MAAM;AACH,uBAAO,CAAC,GAAG,CAAC,CAAC;aAChB;SACJ,CAAC,CAAC;KACN,CAAC,CAAA;CACL,CAAC;;qBAEa,gDAAiB","file":"request.js","sourcesContent":["import supertest from \"supertest\";\nimport superagent from \"superagent\";\nimport Promise from \"core-js/es6/promise\";\nimport server from \"./server\";\n\nRequest = superagent.Request;\n\nRequest.prototype.promise = function() {\n var req = this;\n var error;\n\n return new Promise(function(resolve, reject) {\n req.end(function(err, res) {\n if (typeof res !== \"undefined\" && res.status >= 400) {\n var msg = 'cannot ' + req.method + ' ' + req.url + ' (' + res.status + ')';\n error = new Error(msg);\n error.status = res.status;\n error.body = res.body;\n error.res = res;\n reject(error);\n } else if (err) {\n reject(new Error(err));\n } else {\n resolve(res);\n }\n });\n })\n};\n\nexport default supertest(server);"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/slotsRules.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 4 | 5 | var _slots = require("../../slots"); 6 | 7 | var _slots2 = _interopRequireDefault(_slots); 8 | 9 | var _dataRules = require("./data/rules"); 10 | 11 | var _dataRules2 = _interopRequireDefault(_dataRules); 12 | 13 | describe("Slots Rules", function () { 14 | var slots = new _slots2["default"](_dataRules2["default"], {}); 15 | 16 | it("should set state and execute rules", function () { 17 | slots.set("route", { name: "page", params: { id: 1 } }).commit(); 18 | expect(slots.get("page.title")).toBe("Help"); 19 | }); 20 | 21 | var cb2 = jasmine.createSpy(); 22 | var slots2 = new _slots2["default"](_dataRules2["default"], {}); 23 | slots2.onDidSet(cb2); 24 | it("should call callback if state has changed", function () { 25 | slots2.set("route", { name: "page", params: { id: 1 } }).commit(); 26 | expect(slots2.get("page.title")).toBe("Help"); 27 | expect(cb2).toHaveBeenCalled(); 28 | 29 | var cb4 = jasmine.createSpy(); 30 | var slots4 = new _slots2["default"](_dataRules2["default"], slots2.getState().toJS()); 31 | slots4.onDidSet(cb4); 32 | slots4.set("route", { name: "page", params: { id: 1 } }).commit(); 33 | expect(cb4).toHaveBeenCalled(); 34 | }); 35 | 36 | var slots3 = new _slots2["default"](_dataRules2["default"], {}); 37 | 38 | it("should set state and execute rules", function () { 39 | slots3.set([], { route: { name: "page", params: { id: 1 } } }).commit(); 40 | expect(slots3.get("page.title")).toBe("Help"); 41 | }); 42 | 43 | //const slots5 = new Slots(rules, {}); 44 | // 45 | //it ('should set state and execute rules', () => { 46 | // slots5.set('route.name', 'page').commit(); 47 | // expect(slots5.get('page.title')).toBe('Help'); 48 | //}); 49 | }); 50 | //# sourceMappingURL=slotsRules.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/cascadeRules.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 4 | 5 | var _dataCascadeRules = require("./data/cascadeRules"); 6 | 7 | var _dataCascadeRules2 = _interopRequireDefault(_dataCascadeRules); 8 | 9 | var _slots = require("../../slots"); 10 | 11 | var _slots2 = _interopRequireDefault(_slots); 12 | 13 | var slots = new _slots2["default"](_dataCascadeRules2["default"], {}); 14 | 15 | describe("Slots cascade", function () { 16 | 17 | it("should set state and execute rules on scalar", function () { 18 | var url = "test"; 19 | slots.set("request", url).commit(); 20 | expect(slots.get("request")).toBe(url); 21 | expect(slots.get("users")).toBe(url); 22 | }); 23 | 24 | it("should update state and execute rules on scalar", function () { 25 | var url2 = "test2"; 26 | slots.set("request", url2).commit(); 27 | expect(slots.get("request")).toBe(url2); 28 | expect(slots.get("users")).toBe(url2); 29 | }); 30 | 31 | it("should set w/o path", function () { 32 | var url3 = "test3"; 33 | slots.set([], { request: url3 }).commit(); 34 | expect(slots.get("request")).toBe(url3); 35 | expect(slots.get("users")).toBe(url3); 36 | }); 37 | 38 | it("should multi set w/o path", function () { 39 | var url4 = "test4"; 40 | slots.set([], { request: url4, any: 3, another: { test: "test" } }).commit(); 41 | expect(slots.get("request")).toBe(url4); 42 | expect(slots.get("users")).toBe(url4); 43 | expect(slots.get("any")).toBe(3); 44 | expect(slots.get("another")).toEqual({ test: "test" }); 45 | }); 46 | 47 | //it('should overwrite by rule', () => { 48 | // let url5 = "test5"; 49 | // slots.set([], { request: url5, users: 3 }).commit(); 50 | // expect(slots.get('request')).toBe(url5); 51 | // expect(slots.get('users')).toBe(url5); 52 | //}); 53 | }); 54 | //# sourceMappingURL=cascadeRules.js.map -------------------------------------------------------------------------------- /lib/__tests__/basic/slots.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/__tests__/basic/slots.js"],"names":[],"mappings":";;;;qBAAkB,aAAa;;;;AAE/B,QAAQ,CAAC,OAAO,EAAE,YAAM;AACpB,QAAI,KAAK,YAAA,CAAC;AACV,MAAE,CAAE,4BAA4B,EAAE,YAAM;AACpC,YAAI,GAAG,GAAG,SAAN,GAAG,CAAc,KAAK,EAAE,EAC3B,CAAC;AACF,YAAI,KAAK,GAAG;AACR,mBAAO,EAAE,GAAG;SACf,CAAC;;AAEF,YAAI,KAAK,GAAG;AACR,aAAC,EAAE;AACC,iBAAC,EAAE,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC;aACb;AACD,aAAC,EAAE,CAAC,CAAC,CAAC;SACT,CAAC;;AAEF,aAAK,GAAG,uBAAU,KAAK,EAAE,KAAK,CAAC,CAAC;AAChC,cAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAE;AACzB,mBAAO,EAAE;AACL,mBAAG,EAAE,GAAG;AACR,oBAAI,EAAE,EAAE;aACX;SACJ,CAAC,CAAC;;AAEH,cAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;KAClD,CAAC,CAAC;;AAEH,MAAE,CAAE,oBAAoB,EAAE,YAAM;AAC5B,YAAI,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACtB,aAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAC9B,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC;;;;;;;;KAQ5C,CAAC,CAAC;;;;;;;;CAQN,CAAC,CAAC","file":"slots.js","sourcesContent":["import Slots from \"../../slots\";\n\ndescribe('Slots', () => {\n let slots;\n it ('should set Rules and State', () => {\n let set = function (route) {\n };\n let rules = {\n \"route\": set\n };\n\n let state = {\n s: {\n b: [1,2,3]\n },\n x: [0]\n };\n\n slots = new Slots(rules, state);\n expect(slots.rules).toEqual( {\n \"route\": {\n set: set,\n deps: []\n }\n });\n\n expect(slots.getState().toJS()).toEqual(state);\n });\n\n it ('should set and get', () => {\n let path = [\"s\", \"b\"];\n slots.set(path, [4]).commit();\n expect(slots.get(path)).toEqual([4,2,3]);\n //expect(slots.get(path.join('.'))).toEqual([4,2,3]);\n //expect(slots.get()).toEqual({\n // s: {\n // b: [4,2,3]\n // },\n // x: [0]\n //});\n });\n\n //const flox2 = new Slots({}, {});\n //flox2.set('route', {name: 'page', params: {id: 1}}).commit();\n //const flox3 = new Slots({}, flox2.getState().toJS());\n //it ('should restore state', () => {\n // expect(flox3.getState().toJS()).toEqual(flox2.getState().toJS());\n //});\n});\n\n"]} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | src/server/webpack-stats.json 3 | 4 | ### JetBrains template 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 6 | 7 | *.iml 8 | 9 | ## Directory-based project format: 10 | .idea/ 11 | # if you remove the above rule, at least ignore the following: 12 | 13 | # User-specific stuff: 14 | # .idea/workspace.xml 15 | # .idea/tasks.xml 16 | # .idea/dictionaries 17 | 18 | # Sensitive or high-churn files: 19 | # .idea/dataSources.ids 20 | # .idea/dataSources.xml 21 | # .idea/sqlDataSources.xml 22 | # .idea/dynamic.xml 23 | # .idea/uiDesigner.xml 24 | 25 | # Gradle: 26 | # .idea/gradle.xml 27 | # .idea/libraries 28 | 29 | # Mongo Explorer plugin: 30 | # .idea/mongoSettings.xml 31 | 32 | ## File-based project format: 33 | *.ipr 34 | *.iws 35 | 36 | ## Plugin-specific files: 37 | 38 | # IntelliJ 39 | /out/ 40 | 41 | # mpeltonen/sbt-idea plugin 42 | .idea_modules/ 43 | 44 | # JIRA plugin 45 | atlassian-ide-plugin.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | 52 | 53 | 54 | ### OSX template 55 | .DS_Store 56 | .AppleDouble 57 | .LSOverride 58 | 59 | # Icon must end with two \r 60 | Icon 61 | 62 | # Thumbnails 63 | ._* 64 | 65 | # Files that might appear in the root of a volume 66 | .DocumentRevisions-V100 67 | .fseventsd 68 | .Spotlight-V100 69 | .TemporaryItems 70 | .Trashes 71 | .VolumeIcon.icns 72 | 73 | # Directories potentially created on remote AFP share 74 | .AppleDB 75 | .AppleDesktop 76 | Network Trash Folder 77 | Temporary Items 78 | .apdisk 79 | 80 | 81 | 82 | ### Node template 83 | # Logs 84 | logs 85 | *.log 86 | 87 | # Runtime data 88 | pids 89 | *.pid 90 | *.seed 91 | 92 | # Directory for instrumented libs generated by jscoverage/JSCover 93 | lib-cov 94 | 95 | # Coverage directory used by tools like istanbul 96 | coverage 97 | 98 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 99 | .grunt 100 | 101 | # node-waf configuration 102 | .lock-wscript 103 | 104 | # Compiled binary addons (http://nodejs.org/api/addons.html) 105 | build/Release 106 | 107 | # Dependency directory 108 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 109 | node_modules 110 | 111 | 112 | config/secrets.js -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import { fromJS, is, Map, List} from "immutable"; 2 | import debug from "debug"; 3 | import { toJS } from "./utils"; 4 | import Branch from "./branch"; 5 | import Slots from "./slots"; 6 | const d = debug("slt"); 7 | const log = debug("slt:log"); 8 | 9 | class Context { 10 | constructor(slots) { 11 | this.rules = slots.rules; 12 | this.state = slots.state; 13 | this.initialState = slots.state; 14 | this.slots = slots; 15 | this.path = []; 16 | this.branches = []; 17 | this.promises = []; 18 | } 19 | 20 | reset() { 21 | this.state = this.initialState; 22 | this.promises = []; 23 | } 24 | 25 | setState(value) { 26 | return this.set([], value); 27 | } 28 | 29 | set(path = [], value = {}, mergeValue = true) { 30 | this.path = path; 31 | let prevState = this.state; 32 | let branch = new Branch(this.state, this.slots, this); 33 | this.branches.push(branch); 34 | this.slots._fire("beforeSet", prevState, this); 35 | let newState = branch.set(path, value, mergeValue).state; 36 | this.slots._fire("willSet", newState, this); //TODO: return false == do nothing 37 | this.state = newState; 38 | this.slots._fire("didSet", prevState, this); 39 | return this; 40 | } 41 | 42 | commit() { 43 | this.slots.commit(this); 44 | } 45 | 46 | getState() { 47 | return this.state; 48 | } 49 | 50 | getRules() { 51 | return this.rules.toJS(); 52 | } 53 | 54 | addPromise(promise) { 55 | this.promises.push(promise); 56 | } 57 | 58 | hasPromises() { 59 | return !!this.promises.length; 60 | } 61 | 62 | static makePath(path) { 63 | if (path === null) { 64 | return null; 65 | } 66 | return isArray(path) && path || isString(path) && path.split('.') || 67 | (() => { throw new Error ( 68 | `path should be an array or dot-separated string or null, 69 | ` + Object.prototype.toString.call(path) + ` given`) } )() 70 | } 71 | 72 | get(path = null, state = null) { 73 | state = state || this.state; 74 | if (!path) { 75 | return state.toJS(); 76 | } 77 | path = Slots.makePath(path); 78 | let value = state.getIn(path); 79 | return toJS(value); 80 | } 81 | 82 | } 83 | 84 | //Context.prototype.get = require("./slots").prototype.get; 85 | 86 | export default Context; -------------------------------------------------------------------------------- /lib/utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/utils.js"],"names":[],"mappings":";;;;;QAEgB,UAAU,GAAV,UAAU;QAIV,QAAQ,GAAR,QAAQ;QAIR,SAAS,GAAT,SAAS;QAIT,WAAW,GAAX,WAAW;QAIX,OAAO,GAAP,OAAO;QAIP,QAAQ,GAAR,QAAQ;QAIR,QAAQ,GAAR,QAAQ;QAIR,IAAI,GAAJ,IAAI;QAIJ,IAAI,GAAJ,IAAI;;;;oBAlCH,MAAM;;;;AAEhB,SAAS,UAAU,CAAC,CAAC,EAAE;AAC1B,WAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,mBAAmB,CAAC;CACpE;;AAEM,SAAS,QAAQ,CAAC,CAAC,EAAE;AACxB,WAAO,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC;CAC9C;;AAEM,SAAS,SAAS,CAAC,CAAC,EAAE;AACzB,WAAO,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;CAClC;;AAEM,SAAS,WAAW,CAAC,CAAC,EAAE;AAC3B,WAAO,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;CAClC;;AAEM,SAAS,OAAO,CAAC,CAAC,EAAE;AACvB,WAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,gBAAgB,CAAC;CACjE;;AAEM,SAAS,QAAQ,CAAC,CAAC,EAAE;AACxB,WAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,iBAAiB,CAAC;CAClE;;AAEM,SAAS,QAAQ,CAAC,CAAC,EAAE;AACxB,WAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,iBAAiB,CAAC;CAClE;;AAEM,SAAS,IAAI,CAAC,CAAC,EAAE;AACpB,WAAO,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;CAC/C;;AAEM,SAAS,IAAI,CAAC,KAAK,EAAE;AACxB,SAAK,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC;AAClD,SAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AACjD,SAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,aAAa,GAAG,KAAK,CAAC;AACjD,WAAO,kBAAK,OAAO,CAAC,KAAK,EAAE,EAAC,MAAM,EAAE,OAAO,MAAM,KAAK,WAAW,EAAE,KAAK,EAAE,CAAC,EAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;CACnG","file":"utils.js","sourcesContent":["import util from \"util\";\n\nexport function isFunction(v) {\n return Object.prototype.toString.call(v) === \"[object Function]\";\n}\n\nexport function isObject(v) {\n return v !== null && typeof v === 'object';\n}\n\nexport function isPromise(v) {\n return v && isFunction(v.then);\n}\n\nexport function isImmutable(v) {\n return v && isFunction(v.toJS);\n}\n\nexport function isArray(v) {\n return Object.prototype.toString.call(v) === \"[object Array]\";\n}\n\nexport function isString(v) {\n return Object.prototype.toString.call(v) === \"[object String]\";\n}\n\nexport function isNumber(v) {\n return Object.prototype.toString.call(v) === \"[object Number]\";\n}\n\nexport function toJS(v) {\n return v && isImmutable(v) && v.toJS() || v;\n}\n\nexport function insp(value) {\n value = isImmutable(value) ? value.toJS() : value;\n value = isArray(value) ? value.join(\".\") : value;\n value = isPromise(value) ? \"__promise__\" : value;\n return util.inspect(value, {colors: typeof window === \"undefined\", depth: 0}).replace('\\n', '');\n}"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/slotsRules.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/__tests__/basic/slotsRules.js"],"names":[],"mappings":";;;;qBAAkB,aAAa;;;;yBACb,cAAc;;;;AAEhC,QAAQ,CAAE,aAAa,EAAE,YAAM;AAC3B,QAAM,KAAK,GAAG,+CAAiB,EAAE,CAAC,CAAC;;AAEnC,MAAE,CAAE,oCAAoC,EAAE,YAAM;AAC5C,aAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAC,EAAE,EAAE,CAAC,EAAC,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAC7D,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;KAChD,CAAC,CAAC;;AAEH,QAAM,GAAG,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;AAChC,QAAM,MAAM,GAAG,+CAAiB,EAAE,CAAC,CAAC;AACpC,UAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACrB,MAAE,CAAE,2CAA2C,EAAE,YAAM;AACnD,cAAM,CAAC,GAAG,CAAC,OAAO,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAC,EAAE,EAAE,CAAC,EAAC,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAC9D,cAAM,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC9C,cAAM,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,CAAC;;AAE/B,YAAM,GAAG,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;AAChC,YAAM,MAAM,GAAG,+CAAiB,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAC1D,cAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACrB,cAAM,CAAC,GAAG,CAAC,OAAO,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAC,EAAE,EAAE,CAAC,EAAC,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAC9D,cAAM,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,CAAC;KAClC,CAAC,CAAC;;AAEH,QAAM,MAAM,GAAG,+CAAiB,EAAE,CAAC,CAAC;;AAEpC,MAAE,CAAE,oCAAoC,EAAE,YAAM;AAC5C,cAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAC,KAAK,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAC,EAAE,EAAE,CAAC,EAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AACpE,cAAM,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;KACjD,CAAC,CAAC;;;;;;;;CASN,CAAC,CAAC","file":"slotsRules.js","sourcesContent":["import Slots from \"../../slots\";\nimport rules from \"./data/rules\";\n\ndescribe ('Slots Rules', () => {\n const slots = new Slots(rules, {});\n\n it ('should set state and execute rules', () => {\n slots.set('route', {name: 'page', params: {id: 1}}).commit();\n expect(slots.get('page.title')).toBe('Help');\n });\n\n const cb2 = jasmine.createSpy();\n const slots2 = new Slots(rules, {});\n slots2.onDidSet(cb2);\n it ('should call callback if state has changed', () => {\n slots2.set('route', {name: 'page', params: {id: 1}}).commit();\n expect(slots2.get('page.title')).toBe('Help');\n expect(cb2).toHaveBeenCalled();\n\n const cb4 = jasmine.createSpy();\n const slots4 = new Slots(rules, slots2.getState().toJS());\n slots4.onDidSet(cb4);\n slots4.set('route', {name: 'page', params: {id: 1}}).commit();\n expect(cb4).toHaveBeenCalled();\n });\n\n const slots3 = new Slots(rules, {});\n\n it ('should set state and execute rules', () => {\n slots3.set([], {route:{ name: 'page', params: {id: 1} } }).commit();\n expect(slots3.get('page.title')).toBe('Help');\n });\n\n //const slots5 = new Slots(rules, {});\n //\n //it ('should set state and execute rules', () => {\n // slots5.set('route.name', 'page').commit();\n // expect(slots5.get('page.title')).toBe('Help');\n //});\n\n});"]} -------------------------------------------------------------------------------- /lib/__tests__/basic/cascadeRules.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/__tests__/basic/cascadeRules.js"],"names":[],"mappings":";;;;gCAAkB,qBAAqB;;;;qBACrB,aAAa;;;;AAE/B,IAAM,KAAK,GAAG,sDAAiB,EAAE,CAAC,CAAC;;AAEnC,QAAQ,CAAC,eAAe,EAAE,YAAM;;AAE5B,MAAE,CAAC,8CAA8C,EAAE,YAAM;AACrD,YAAI,GAAG,GAAG,MAAM,CAAC;AACjB,aAAK,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;AACnC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;KAExC,CAAC,CAAC;;AAEH,MAAE,CAAC,iDAAiD,EAAE,YAAM;AACxD,YAAI,IAAI,GAAG,OAAO,CAAC;AACnB,aAAK,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AACpC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACzC,CAAC,CAAC;;AAEH,MAAE,CAAC,qBAAqB,EAAE,YAAM;AAC5B,YAAI,IAAI,GAAG,OAAO,CAAC;AACnB,aAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAC1C,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACzC,CAAC,CAAC;;AAEH,MAAE,CAAC,2BAA2B,EAAE,YAAM;AAClC,YAAI,IAAI,GAAG,OAAO,CAAC;AACnB,aAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAC7E,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjC,cAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;KAC1D,CAAC,CAAC;;;;;;;;CAQN,CAAC,CAAC","file":"cascadeRules.js","sourcesContent":["import rules from \"./data/cascadeRules\";\nimport Slots from \"../../slots\";\n\nconst slots = new Slots(rules, {});\n\ndescribe('Slots cascade', () => {\n\n it('should set state and execute rules on scalar', () => {\n let url = \"test\";\n slots.set('request', url).commit();\n expect(slots.get('request')).toBe(url);\n expect(slots.get('users')).toBe(url);\n\n });\n\n it('should update state and execute rules on scalar', () => {\n let url2 = \"test2\";\n slots.set('request', url2).commit();\n expect(slots.get('request')).toBe(url2);\n expect(slots.get('users')).toBe(url2);\n });\n\n it('should set w/o path', () => {\n let url3 = \"test3\";\n slots.set([], { request: url3 }).commit();\n expect(slots.get('request')).toBe(url3);\n expect(slots.get('users')).toBe(url3);\n });\n\n it('should multi set w/o path', () => {\n let url4 = \"test4\";\n slots.set([], { request: url4, any: 3, another: { test: \"test\" } }).commit();\n expect(slots.get('request')).toBe(url4);\n expect(slots.get('users')).toBe(url4);\n expect(slots.get('any')).toBe(3);\n expect(slots.get('another')).toEqual({ test: \"test\" });\n });\n\n //it('should overwrite by rule', () => {\n // let url5 = \"test5\";\n // slots.set([], { request: url5, users: 3 }).commit();\n // expect(slots.get('request')).toBe(url5);\n // expect(slots.get('users')).toBe(url5);\n //});\n});"]} -------------------------------------------------------------------------------- /lib/context.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/context.js"],"names":[],"mappings":";;;;;;;;;;;;yBAAqC,WAAW;;qBAC9B,OAAO;;;;qBACJ,SAAS;;sBACX,UAAU;;;;qBACX,SAAS;;;;AAC3B,IAAM,CAAC,GAAG,wBAAM,KAAK,CAAC,CAAC;AACvB,IAAM,GAAG,GAAG,wBAAM,SAAS,CAAC,CAAC;;IAEvB,OAAO;AACE,aADT,OAAO,CACG,KAAK,EAAE;8BADjB,OAAO;;AAEL,YAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;AACzB,YAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;AACzB,YAAI,CAAC,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC;AAChC,YAAI,CAAC,KAAK,GAAG,KAAK,CAAC;AACnB,YAAI,CAAC,IAAI,GAAG,EAAE,CAAC;AACf,YAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;AACnB,YAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;KACtB;;iBATC,OAAO;;eAWJ,iBAAG;AACJ,gBAAI,CAAC,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC;AAC/B,gBAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;SACtB;;;eAEO,kBAAC,KAAK,EAAE;AACZ,mBAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;SAC9B;;;eAEE,eAA2C;gBAA1C,IAAI,gCAAG,EAAE;gBAAE,KAAK,gCAAG,EAAE;gBAAE,UAAU,gCAAG,IAAI;;AACxC,gBAAI,CAAC,IAAI,GAAG,IAAI,CAAC;AACjB,gBAAI,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;AAC3B,gBAAI,MAAM,GAAG,wBAAW,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACtD,gBAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC3B,gBAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;AAC/C,gBAAI,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC;AACzD,gBAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC5C,gBAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;AACtB,gBAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;AAC5C,mBAAO,IAAI,CAAC;SACf;;;eAEK,kBAAG;AACL,gBAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SAC3B;;;eAEO,oBAAG;AACP,mBAAO,IAAI,CAAC,KAAK,CAAC;SACrB;;;eAEO,oBAAG;AACP,mBAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B;;;eAES,oBAAC,OAAO,EAAE;AAChB,gBAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC/B;;;eAEU,uBAAG;AACV,mBAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;SACjC;;;eAYE,eAA4B;gBAA3B,IAAI,gCAAG,IAAI;gBAAE,KAAK,gCAAG,IAAI;;AACzB,iBAAK,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC;AAC5B,gBAAI,CAAC,IAAI,EAAE;AACP,uBAAO,KAAK,CAAC,IAAI,EAAE,CAAC;aACvB;AACD,gBAAI,GAAG,mBAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC5B,gBAAI,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC9B,mBAAO,WA5EN,IAAI,EA4EO,KAAK,CAAC,CAAC;SACtB;;;eAlBc,kBAAC,IAAI,EAAE;AAClB,gBAAI,IAAI,KAAK,IAAI,EAAE;AACf,uBAAO,IAAI,CAAC;aACf;AACD,mBAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAC9D,CAAC,YAAM;AAAE,sBAAM,IAAI,KAAK,CACpB,mFACQ,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;aAAE,CAAA,EAAI,CAAA;SACzE;;;WA7DC,OAAO;;;;;qBA6EE,OAAO","file":"context.js","sourcesContent":["import { fromJS, is, Map, List} from \"immutable\";\nimport debug from \"debug\";\nimport { toJS } from \"./utils\";\nimport Branch from \"./branch\";\nimport Slots from \"./slots\";\nconst d = debug(\"slt\");\nconst log = debug(\"slt:log\");\n\nclass Context {\n constructor(slots) {\n this.rules = slots.rules;\n this.state = slots.state;\n this.initialState = slots.state;\n this.slots = slots;\n this.path = [];\n this.branches = [];\n this.promises = [];\n }\n\n reset() {\n this.state = this.initialState;\n this.promises = [];\n }\n\n setState(value) {\n return this.set([], value);\n }\n\n set(path = [], value = {}, mergeValue = true) {\n this.path = path;\n let prevState = this.state;\n let branch = new Branch(this.state, this.slots, this);\n this.branches.push(branch);\n this.slots._fire(\"beforeSet\", prevState, this);\n let newState = branch.set(path, value, mergeValue).state;\n this.slots._fire(\"willSet\", newState, this); //TODO: return false == do nothing\n this.state = newState;\n this.slots._fire(\"didSet\", prevState, this);\n return this;\n }\n\n commit() {\n this.slots.commit(this);\n }\n\n getState() {\n return this.state;\n }\n\n getRules() {\n return this.rules.toJS();\n }\n\n addPromise(promise) {\n this.promises.push(promise);\n }\n\n hasPromises() {\n return !!this.promises.length;\n }\n\n static makePath(path) {\n if (path === null) {\n return null;\n }\n return isArray(path) && path || isString(path) && path.split('.') ||\n (() => { throw new Error (\n `path should be an array or dot-separated string or null,\n ` + Object.prototype.toString.call(path) + ` given`) } )()\n }\n\n get(path = null, state = null) {\n state = state || this.state;\n if (!path) {\n return state.toJS();\n }\n path = Slots.makePath(path);\n let value = state.getIn(path);\n return toJS(value);\n }\n\n}\n\n//Context.prototype.get = require(\"./slots\").prototype.get;\n\nexport default Context;"]} -------------------------------------------------------------------------------- /lib/context.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 12 | 13 | var _immutable = require("immutable"); 14 | 15 | var _debug = require("debug"); 16 | 17 | var _debug2 = _interopRequireDefault(_debug); 18 | 19 | var _utils = require("./utils"); 20 | 21 | var _branch = require("./branch"); 22 | 23 | var _branch2 = _interopRequireDefault(_branch); 24 | 25 | var _slots = require("./slots"); 26 | 27 | var _slots2 = _interopRequireDefault(_slots); 28 | 29 | var d = (0, _debug2["default"])("slt"); 30 | var log = (0, _debug2["default"])("slt:log"); 31 | 32 | var Context = (function () { 33 | function Context(slots) { 34 | _classCallCheck(this, Context); 35 | 36 | this.rules = slots.rules; 37 | this.state = slots.state; 38 | this.initialState = slots.state; 39 | this.slots = slots; 40 | this.path = []; 41 | this.branches = []; 42 | this.promises = []; 43 | } 44 | 45 | _createClass(Context, [{ 46 | key: "reset", 47 | value: function reset() { 48 | this.state = this.initialState; 49 | this.promises = []; 50 | } 51 | }, { 52 | key: "setState", 53 | value: function setState(value) { 54 | return this.set([], value); 55 | } 56 | }, { 57 | key: "set", 58 | value: function set() { 59 | var path = arguments[0] === undefined ? [] : arguments[0]; 60 | var value = arguments[1] === undefined ? {} : arguments[1]; 61 | var mergeValue = arguments[2] === undefined ? true : arguments[2]; 62 | 63 | this.path = path; 64 | var prevState = this.state; 65 | var branch = new _branch2["default"](this.state, this.slots, this); 66 | this.branches.push(branch); 67 | this.slots._fire("beforeSet", prevState, this); 68 | var newState = branch.set(path, value, mergeValue).state; 69 | this.slots._fire("willSet", newState, this); //TODO: return false == do nothing 70 | this.state = newState; 71 | this.slots._fire("didSet", prevState, this); 72 | return this; 73 | } 74 | }, { 75 | key: "commit", 76 | value: function commit() { 77 | this.slots.commit(this); 78 | } 79 | }, { 80 | key: "getState", 81 | value: function getState() { 82 | return this.state; 83 | } 84 | }, { 85 | key: "getRules", 86 | value: function getRules() { 87 | return this.rules.toJS(); 88 | } 89 | }, { 90 | key: "addPromise", 91 | value: function addPromise(promise) { 92 | this.promises.push(promise); 93 | } 94 | }, { 95 | key: "hasPromises", 96 | value: function hasPromises() { 97 | return !!this.promises.length; 98 | } 99 | }, { 100 | key: "get", 101 | value: function get() { 102 | var path = arguments[0] === undefined ? null : arguments[0]; 103 | var state = arguments[1] === undefined ? null : arguments[1]; 104 | 105 | state = state || this.state; 106 | if (!path) { 107 | return state.toJS(); 108 | } 109 | path = _slots2["default"].makePath(path); 110 | var value = state.getIn(path); 111 | return (0, _utils.toJS)(value); 112 | } 113 | }], [{ 114 | key: "makePath", 115 | value: function makePath(path) { 116 | if (path === null) { 117 | return null; 118 | } 119 | return isArray(path) && path || isString(path) && path.split(".") || (function () { 120 | throw new Error("path should be an array or dot-separated string or null,\n " + Object.prototype.toString.call(path) + " given"); 121 | })(); 122 | } 123 | }]); 124 | 125 | return Context; 126 | })(); 127 | 128 | //Context.prototype.get = require("./slots").prototype.get; 129 | 130 | exports["default"] = Context; 131 | module.exports = exports["default"]; 132 | //# sourceMappingURL=context.js.map -------------------------------------------------------------------------------- /src/slots.js: -------------------------------------------------------------------------------- 1 | import Promise from "core-js/es6/promise"; 2 | import { fromJS, is, Map, List} from "immutable"; 3 | import debug from "debug"; 4 | import { toJS, isArray, isString, isNumber, isFunction, isPromise, insp } from "./utils"; 5 | import Context from "./context"; 6 | const d = debug("slt"); 7 | const log = debug("slt:log"); 8 | 9 | class Slots { 10 | constructor(rules = {}, state = {}, aliases = {}, promiseClass = null) { 11 | this.rules = Object.keys(rules).reduce((res, key) => { 12 | res[key] = Slots.normalizeRule(rules[key]); 13 | return res; 14 | }, {}); 15 | this.state = fromJS(state); 16 | this.optimisticState = this.state; 17 | this.contexts = []; 18 | this.listeners = {}; 19 | this.promiseClass = promiseClass || Promise || null; 20 | } 21 | 22 | reset() { 23 | this.state = fromJS({}); 24 | this.listeners = {}; 25 | } 26 | 27 | setState(value) { 28 | return this.set([], value); 29 | } 30 | 31 | set(path = [], value = {}, mergeValue = true) { 32 | let ctx = new Context(this); 33 | this.contexts.push(ctx); 34 | ctx.set(path, value, mergeValue); 35 | return ctx; 36 | } 37 | 38 | commit (ctx) { 39 | let prevState = this.state; 40 | this._fire("willCommit", ctx.state); 41 | log("COMMIT %s", insp(ctx.state)); 42 | this.state = ctx.state; 43 | this._fire("didCommit", prevState); 44 | this._checkPromises(); 45 | log("LISTENERS DONE", insp(ctx.state)); 46 | return ctx; 47 | } 48 | 49 | on(eventName, fn) { 50 | if (this.listeners[eventName] === undefined) { 51 | this.listeners[eventName] = []; 52 | } 53 | if (this.listeners[eventName].filter(f => f.toString() === fn.toString()).length) { 54 | return this.listeners[eventName].length; 55 | } 56 | return this.listeners[eventName].push(fn); 57 | } 58 | 59 | _fire(eventName, ...args) { 60 | let listeners = this.listeners[eventName]; 61 | if (!listeners) { 62 | return; 63 | } 64 | listeners.forEach(fn => fn.apply(this, args)); 65 | } 66 | 67 | _checkPromises() { 68 | if (this.contexts.filter((context) => context.hasPromises()).length) { 69 | return; 70 | } 71 | this._fire("allPromisesFulfilled"); 72 | } 73 | 74 | onWillSet(fn) { 75 | return this.on("willSet", fn); 76 | } 77 | 78 | onDidSet(fn) { 79 | return this.on("didSet", fn); 80 | } 81 | 82 | onWillCommit(fn) { 83 | return this.on("willCommit", fn); 84 | } 85 | 86 | onDidCommit(fn) { 87 | return this.on("didCommit", fn); 88 | } 89 | 90 | onAllPromisesFulfilled(fn) { 91 | return this.on("allPromisesFulfilled", fn); 92 | } 93 | 94 | promise(promiseClass = null) { 95 | promiseClass = promiseClass || this.promiseClass; 96 | if (!promiseClass) { 97 | throw new Error("Cant call .promise(). No promiseClass specified."); 98 | } 99 | return new promiseClass ((resolve, reject) => { 100 | this.allDone((err, state) => { 101 | if (err) { 102 | reject(new Error(err)); 103 | } else { 104 | resolve(state); 105 | } 106 | }); 107 | }); 108 | } 109 | 110 | 111 | getContexts() { 112 | return this.contexts; 113 | } 114 | 115 | getState() { 116 | return this.state; 117 | } 118 | 119 | get(path = null, state = null) { 120 | state = state || this.state; 121 | if (!path) { 122 | return state.toJS(); 123 | } 124 | path = Slots.makePath(path); 125 | let value = state.getIn(path); 126 | return toJS(value); 127 | } 128 | 129 | getRule(path) { 130 | path = Slots.makePath(path); 131 | return this.rules[Slots.makeDottedPath(path)]; 132 | } 133 | 134 | getSetRule(path) { 135 | let rule = this.getRule(path); 136 | return rule && rule.set; 137 | } 138 | 139 | getDeps(path) { 140 | let rule = this.getRule(path); 141 | return rule && rule.deps; 142 | } 143 | 144 | 145 | isEqual(state) { 146 | return is(fromJS(state), this.state); 147 | } 148 | 149 | static normalizeRule(rule) { 150 | if (isFunction(rule)) { 151 | let fn = rule; 152 | rule = { 153 | "set": fn 154 | }; 155 | } 156 | if (!rule.deps) { 157 | rule.deps = []; 158 | } 159 | if (!isArray(rule.deps)) { 160 | throw new Error("Invalid rule"); 161 | } 162 | return rule; 163 | } 164 | 165 | static makePath(path) { 166 | if (path === null) { 167 | return null; 168 | } 169 | if (path.toArray) { 170 | path = path.toArray(); 171 | } 172 | return isArray(path) && path || (isString(path) || isNumber(path)) && path.toString().split('.') || 173 | (() => { throw new Error ( 174 | `path should be an array or dot-separated string or null, 175 | ` + Object.prototype.toString.call(path) + ` given`) } )() 176 | } 177 | 178 | static makeDottedPath(path) { 179 | return Slots.makePath(path).join("."); 180 | } 181 | 182 | } 183 | 184 | export default Slots; -------------------------------------------------------------------------------- /src/branch.js: -------------------------------------------------------------------------------- 1 | import { fromJS, is, Map, List} from "immutable"; 2 | import debug from "debug"; 3 | import { toJS, isFunction, isPromise, insp, isObject } from "./utils"; 4 | import Slots from "./slots"; 5 | const d = debug("slt"); 6 | const log = debug("slt:log"); 7 | 8 | class Branch { 9 | constructor(state, slots, ctx, parent = null) { 10 | this.slots = slots; 11 | this.state = state; 12 | this.ctx = ctx; 13 | this.parent = parent; 14 | this.path = []; 15 | this.value = null; 16 | this.locks = {}; 17 | } 18 | 19 | reset() { 20 | this.state = this.initialState; 21 | } 22 | 23 | newBranch(state) { 24 | let branch = new Branch(state, this.slots, this.ctx, this); 25 | return branch; 26 | } 27 | 28 | setState(value) { 29 | return this.set([], value); 30 | } 31 | 32 | set(path = [], value = {}, mergeValue = true) { 33 | //({path, value} = this.reducePathAndValue(Slots.makePath(path), value)); 34 | path = Slots.makePath(path); 35 | this.path = path; 36 | this.value = value; 37 | this.findAsync(new List (path), value); 38 | if (Object.keys(this.locks).length) { 39 | this.resolveAsync(); 40 | return; 41 | } 42 | log("SET %s TO %s", insp(path), insp(value)); 43 | let state = this.state; 44 | d("MERGED \n%s", insp(state)); 45 | const applyRules = (_path = new List(), _value = {}) => { 46 | let rule = this.getSetRule(_path); 47 | if (rule) { 48 | let deps = this.getDeps(_path).map(dep => { 49 | let dependency = toJS(state.getIn(Slots.makePath(dep))); 50 | if (typeof dependency === "undefined") { 51 | console.log(state); 52 | throw new Error("Rule on `" + _path.toArray().join(".") + 53 | "` requires `" + dep + "` state dependency"); 54 | } 55 | return dependency; 56 | }); 57 | log("RULE on path %s matched with value %s", insp(_path), insp(_value)); 58 | state = Branch.mergeValue(state, _path, _value, mergeValue); 59 | d("NEW BRANCH with state %s", insp(state)); 60 | let branch = this.newBranch(state); 61 | let result = rule.apply(branch, [_value, ...deps]); 62 | d("RESULT is %s", insp(result)); 63 | state = branch.state; 64 | if (isPromise(result)) { 65 | log("PROMISE RETURNED"); 66 | result.bind(this.ctx); // out of call stack 67 | this.ctx.promises.push(result); 68 | result.then(() => { 69 | log("PROMISE FULFILLED for SET %s", insp(_path)); 70 | this.ctx.promises.splice(this.ctx.promises.indexOf(result), 1); 71 | this.ctx.slots._checkPromises(this); 72 | }); 73 | } else if (typeof result !== "undefined") { 74 | state = state.setIn(_path, result); 75 | } 76 | } 77 | else { 78 | if (isObject(_value)) { 79 | Object.keys(_value).forEach(k => applyRules(_path.push(k), _value[k])); 80 | } else { // No rule found for this `set` 81 | state = Branch.mergeValue(state, path, value, mergeValue); 82 | } 83 | } 84 | }; 85 | applyRules(new List(path), value); 86 | this.state = state; 87 | return this; 88 | } 89 | 90 | resolveAsync() { 91 | Object.keys(this.locks).forEach((key) => { 92 | let lock = this.locks[key]; 93 | lock.promise.then((result) => { 94 | this.set(lock.path, result); 95 | delete this.locks[key]; 96 | }) 97 | }); 98 | } 99 | 100 | findAsync(path, value) { 101 | if (isPromise(value)) { 102 | this.locks[path] = {path, promise: value}; 103 | } 104 | else if (isObject(value)) { 105 | Object.keys(value).forEach(k => this.findAsync(path.push(k), value[k])); 106 | } 107 | } 108 | 109 | 110 | 111 | reducePathAndValue(path, value) { 112 | let i = path.length; 113 | let v = value; 114 | while (i--) { 115 | let p = path.slice(0, i); 116 | let tmp = {}; 117 | tmp[path.slice(i)] = v; 118 | v = tmp; 119 | if (this.getRule(p)) { 120 | path = p; 121 | value = v; 122 | } 123 | } 124 | return { path, value } 125 | } 126 | 127 | getState() { 128 | return this.state; 129 | } 130 | 131 | get(path = null, state = null) { 132 | state = state || this.state; 133 | if (!path) { 134 | return state.toJS(); 135 | } 136 | path = Slots.makePath(path); 137 | let value = state.getIn(path); 138 | return toJS(value); 139 | } 140 | 141 | getRule(path) { 142 | return this.slots.getRule(path); 143 | } 144 | 145 | getSetRule(path) { 146 | return this.slots.getSetRule(path); 147 | } 148 | 149 | getDeps(path) { 150 | return this.slots.getDeps(path); 151 | } 152 | 153 | static mergeValue(state, path, value, mergeValue) { 154 | return (isObject(value) && mergeValue) ? 155 | state.mergeDeepIn(path, value) : state.setIn(path, value); 156 | } 157 | } 158 | 159 | export default Branch; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DISCLAIMER 2 | I've developed this peace of software during implementation my own project, which was originally started with Flux. I realized that `Flux` adds a lot of unnecessary (not quite useful) entities and adds a lot of boilerplate to my codebase which is a reason why there is a lot of different implementations. Also I had no time to wait for Facebook Relay (http://facebook.github.io/react/blog/2015/02/20/introducing-relay-and-graphql.html) and I didn't want to bloat my codebase with knowingly out-of-date architecture. Now I glad to introduce my solution to app state managemet. It is not dependend/related on any lib except https://facebook.github.io/immutable-js/ which is used for inner state management and not exposed to user (also I have a plan to make pure immutable version w/o any conversion). 3 | Contributors are welcome! Official twitter is https://twitter.com/slt_js 4 | 5 | ## DESCRIPTION 6 | `Slots` could be considered as a missing part of React (not only). It's like `Flux`, but better. 7 | 8 | ## TODO 9 | * connections: express middleware, React and others as separate libs 10 | * add transaction context. Add rollbacks and state history navigation 11 | * add the master transaction context. Changes in other contexts sould be discarded if conflict occurs 12 | * add performance tests 13 | * add Promise interface support (e.g. `Slots.then` ect) 14 | * errors handling 15 | * async tests. More tests for Promises 16 | * rules which are dependent on state conditions (e.g. "apply rule IF") 17 | * add support for groups of (sub)rules. Could be useful for sharing common rules 18 | * monitoring capabilities (for progress bars, for example) and better debugging 19 | * pure immutable version w/o conversion fromJS and .toJS 20 | * cache configuration 21 | * more examples 22 | 23 | ## Differences from Flux 24 | Honestly it's hard to compare `Slots` to `Flux` due to quite different approach. But people are asking. 25 | * in `Slots` there is no concept of Actions/ActionExecutors. It has rules to maintain consistency of the state. 26 | * in `Slots` there is no multiple Stores. It holds data in the one immutable Map. 27 | * in `Slots` there is no waitFor. 28 | * `Slots` supports async operations through Promises 29 | * with `Slots` you are the master of your app state. Everything that's not commited will be discard. 30 | * you can choose beetween optimistic/pessimistic update 31 | * 32 | 33 | In short to understand `Slots` you need to know how works only one method: `set(path, value)` (`path` is a dot-separated path to the concrete property in the state map). This simplicity has a great value. 34 | 35 | ## Philosophy 36 | In each web app we can distinguish 2 types of state data: first is the source (request, session) data and second is derivative (response, artefacts) data. Derivative data (response and additional artefacts such as errors/widgets/recommendations/comments to post ect.) dependends on request (HTTP or another type of request). The idea is to hold request data in the state and apply rule that will fetch data (derivative) for that request. **This approach makes you reach fully consistent app state: it holds all data you need and all the reasons why it should hold it at any point of time.** 37 | 38 | A great analogy is an `` or `