{"_id":"@b-flower/bdn-pocket","_rev":"1-18e8c781312f0176eaae17993adb6c44","name":"@b-flower/bdn-pocket","description":"pocket tools for managing redux and redux-saga","dist-tags":{"latest":"1.2.0"},"versions":{"1.2.0":{"name":"@b-flower/bdn-pocket","version":"1.2.0","description":"pocket tools for managing redux and redux-saga","main":"lib/index.js","module":"es/index.js","jsnext:main":"es/index.js","scripts":{"test":"ava -s","watch":"ava -w","prepublishOnly":"npm run build","build:cjs":"cross-env BABEL_ENV=cjs babel src --out-dir lib","build:es":"cross-env BABEL_ENV=es babel src --out-dir es    ","build:umd":"cross-env BABEL_ENV=es NODE_ENV=development rollup -c","build:umd:min":"cross-env BABEL_ENV=es NODE_ENV=production rollup -c","build":"npm-run-all build:**"},"author":{"name":"Arnaud Mélon","url":"https://github.com/amelon"},"repository":{"type":"git","url":"git+ssh://git@bitbucket.org/bflower/bdn-pocket.git"},"license":"Apache-2.0","keywords":["redux","redux-saga","reselect","action","action-creator","stampit"],"ava":{"files":["__tests__/*.test.js"],"source":["src/*.js"],"match":[],"concurrency":5,"failFast":false,"tap":false,"powerAssert":false,"require":["babel-register"],"babel":"inherit"},"quokka":{"babel":true},"dependencies":{"@stamp/arg-over-prop":"^1.0.1","@stamp/configure":"^1.0.1","@stamp/is":"^1.0.0","@stamp/it":"^1.0.1","ramda":"^0.24.1","reselect":"^3.0.1"},"devDependencies":{"ava":"^0.22.0","babel-cli":"^6.26.0","babel-plugin-external-helpers":"^6.22.0","babel-preset-env":"^1.6.0","cross-env":"^5.0.5","eslint":"^4.4.1","eslint-config-airbnb-base":"^11.3.1","eslint-plugin-import":"^2.7.0","npm-run-all":"^4.0.2","rollup":"^0.49.2","rollup-plugin-babel":"^3.0.2","rollup-plugin-commonjs":"^8.2.0","rollup-plugin-node-resolve":"^3.0.0","rollup-plugin-replace":"^1.2.1","rollup-plugin-uglify":"^2.0.1"},"homepage":"https://bitbucket.org/bflower/bdn-pocket#readme","directories":{"lib":"lib"},"gitHead":"9a94ef89bd60058f85e005c0192f61318cfbb191","_id":"@b-flower/bdn-pocket@1.2.0","_npmVersion":"5.6.0","_nodeVersion":"8.9.3","_npmUser":{"name":"gabchang","email":"dotgab@gmail.com"},"dist":{"integrity":"sha512-D6rnDfsy6YmL96hYVFDH2UrrJuPK9JlRxEuc8/6UZOAE6Gx6JMlQk2kWALOQmWlYIQ4+lzbtLDoXpjYlRxC16Q==","shasum":"b49a0f583c067b194abb91152b98fcb6d9893531","tarball":"https://registry.npmjs.org/@b-flower/bdn-pocket/-/bdn-pocket-1.2.0.tgz","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD1wVSThAb5VbQVLL0V2XW0cvlJCm25hy0eGwRDP4QSFgIhAMn/gA34VQCRwg3LhZc/cO8fY5DQ7GUj/UktAWr6W4MG"}]},"maintainers":[{"name":"gabchang","email":"dotgab@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/bdn-pocket-1.2.0.tgz_1517320202459_0.16029447386972606"}}},"readme":"- [BDN-POCKET](#bdn-pocket)\n  - [Why bdn-pocket ?](#why-bdn-pocket)\n  - [Definitions](#definitions)\n  - [Project size](#project-size)\n  - [Principles](#principles)\n  - [Concepts](#concepts)\n    - [Action](#action)\n    - [Signal](#signal)\n    - [Message](#message)\n    - [Messenger](#messenger)\n  - [Usage](#usage)\n    - [PropTypes](#proptypes)\n      - [Available types](#available-types)\n    - [Signal](#signal)\n      - [Create](#create)\n      - [Change prefix](#change-prefix)\n      - [PropTypes](#proptypes)\n      - [Dispatch](#dispatch)\n      - [Watch (in saga)](#watch-in-saga)\n    - [Messenger](#messenger)\n      - [Create](#create)\n      - [Combine with [redux] `reducer` (`makeReducer`)](#combine-with-redux-`reducer`-`makereducer`)\n      - [Call message (from saga)](#call-message-from-saga)\n      - [Path reducer (`makePathReducer`)](#path-reducer-`makepathreducer`)\n    - [Selector & SliceSelector](#selector-sliceselector)\n\n# BDN-POCKET\n\neasily manage state management and state selection with [redux] and [redux-saga]\n\n## Why bdn-pocket ?\n\nbdn-pocket is a set of tools that helps to manage state and allow link between [redux] and [redux-saga] more easily.\n\nIt brings some new concepts like `Signal`, `Message` and `Messenger` that helps to separate `ActionCreator` in different categories with separation of responsabilities.\n\nIt allows `Selector` with `arguments` and a clean memoization (inspired by [reselect] and using it)\n\nIt enforces readibility and runtime validation with integration of `propTypes` on `Action` and `Selector`.\n\nbdn-pocket has been built by [b-eden] development team.\nThis project is an extract of differents concepts and development already existing in [b-eden] project and gathered now in this projet with some enhancements.\n\nbdn-pocket uses [stampit] which brings composition and configuration with ease.\n\n[b-eden] team plans to replace existing [b-eden] code with this library.\n\nYou can use this library with small project.\n\n## Definitions\n`Action` = `actionCreator` => it generates [redux] `action`s\n\n## Project size\nThis library is not intended to be used for small projects.\n\n`Action` is powerfull and flexible tools that helps to create `action`s.\n\n`Selector` extends [reselect] with some new features and easy composition.\n\nThis library has been built for large projects using [redux] and [redux-saga].\n\nRead [Principles] for explanation.\n\n\n## Principles\nAt [b-eden] team we use [redux] with [redux-saga] for more than 2 years and that lead to some principles.\n\n* No side effect in a container (and component of course)\n* A container should not dispatch an action that change the state\n* All side effects should be done in [redux-saga]\n\nThose principles help [b-eden] dev team to build a robust, maintenable, readable with comprehensive architecture app.\n\nWith separation of concern of `Action` between `Signal` and `Message`\n\n## Concepts\n\n### Action\n`Action` is the same concept as redux one.\n\n\nBut in [b-eden], `Action` are never used in favor of 2 new concepts (`Signal`, `Messenger`).\n\nIn bdn-pocket `Action` is an `action` creator.\n\n* `Action` generates `action`s\n* `Signal` generates `signal`s\n\n### Signal\nA `Signal` is purely an `Action`, it creates an `action`.\n\n````javascript\nimport { Signal, Action } from 'bdn-pocket'\nconsole.log(Signal === Action) // => true\n````\n\nA `signal` must follow these principles:\n\n* a (dispatched) `signal` will never be used to change the [redux] `state`\n* a `container` will always call a `Signal` (not a `Message`)\n\n\n### Message\n\nA `message` is an `action` called from a `Messenger`.\nIt is associated to a `reducer`.\n\nIn bdn-pocket `message` is just a definition.\nObject `Message` does not exists as it is an `Action` in a `Messenger`.\n\n### Messenger\n\nA `Messenger` is a tool that links a `message` defintion (`Action`) with a `reducer` (`state`).\n\nA `message` is an `action` that will be associated with a `reducer` and will produce an new `state`.\n\nA `message` must follows this principle:\n* never use a `message` in a [redux] `container`\n\n\n## Usage\n\n### PropTypes\n`Signal` and `Selector` are composed of `PropTypes` (thanks to [stampit]).\n\nThus you can enforce props (as React propTypes) you receive and ensure you send good ones.\n\nAnd it offers readibility and some documentation for the same price.\n\n#### Available types\n* `number`\n* `string`\n* `object`\n* `func`\n* `array`\n* `mixed`\n\n````javascript\nimport { Signal, Types } from 'bdn-pocket'\nconst {\n  number,\n  string,\n  object,\n  func,\n  array,\n  mixed,\n} = Types\n\nconst sig = Signal\n  .propTypes({\n    a: string, // required string\n    b: string({ required: false }) // prop `b` not required but if present should not be null or undefined\n    c: string({ required: false, allowNull: true }) // prop `c` not required and can be null\n  })\n  .def('my signal')\n\nsig({ a: 'a', b: 'b', c: 'c' }) // => not throw\nsig({ a: 'a', c: null}) // => not throw\nsig({ b: 'b'}) // => throw an error\nsig({ a: 'a', b: 10, c: null}) // => throw an error\nsig({ a: 'a', b: null, c: null}) // => throw an error\n\n````\n\nYou can use short notation to define prop types with required string.\n````javascript\nimport { Signal, Types } from 'bdn-pocket'\nconst {\n  string,\n} = Types\n\nconst sig = Signal\n  .propTypes({\n    a: string, // required string\n    b: string, // required string\n  })\n  .def('my sig')\n\n// same as\nconst sig = Signal\n  .propTypes('a', 'b')\n  .def('my sig')\n\n````\n\n### Signal\nA `Signal` is an action creator that creates a `signal`.\n\nIn the concept of `bdn-pocket` a dispatched `signal` should not result as a [redux] `state` change.\n\nIt's goal is to be watched by `saga`.\n\nA [redux] `container` must dispatch a `signal`.\n\n#### Create\n````javascript\n// in /user/signal.js\nimport { Signal } from 'bdn-pocket'\n\nconst triggerLoadUser = Signal.def('trigger load user')\n\nconsole.log(triggerLoadUser({ userId: 'me' } ))\n// => { type: 'my-app/TRIGGER_LOAD_USER', payload: { userId: 'me' }}\n````\n\n#### Change prefix\n\n````javascript\n// in /user/signal.js\nimport { Signal } from 'bdn-pocket'\n\nconst triggerLoadUser = Signal.prefix('my-plugin').def('trigger load user')\n\nconsole.log(triggerLoadUser({ userId: 'me' } ))\n// => { type: 'my-plugin/TRIGGER_LOAD_USER', payload: { userId: 'me' }}\n````\n\nYou can use your own `Signal` definition inside a plugin.\n````javascript\n// in /lib/my_signal.js\nimport { Signal } from 'bdn-pocket'\n\nexport default Signal.prefix('my-plugin')\n\n// in /user/signal.js\nimport PluginSignal from '/lib/my-signal'\nconst triggerLoadUser = PluginSignal.def('trigger load user')\n\nconsole.log(triggerLoadUser({ userId: 'me' } ))\n// => { type: 'my-plugin/TRIGGER_LOAD_USER', payload: { userId: 'me' }}\n````\n\n#### PropTypes\nYou can enforce prop types of `signal` to ensure `userId` is present and has good type.\n\n````javascript\n// in /user/signal.js\nimport { Signal, Types } from 'bdn-pocket'\nconst { string } = Types\n\nconst triggerLoadUser = Signal\n  .propTypes({\n    userId: string\n  })\n  .def('trigger load user')\n\nconsole.log(triggerLoadUser({ userId: 'me' } ))\n// => { type: 'my-plugin/TRIGGER_LOAD_USER', payload: { userId: 'me' }}\n\n// call without userId throw an error\ntriggerLoadUser({NOUSERID: ''}) // => throw an error\n````\n\n\n\n#### Dispatch\n````javascript\n// in /module/user/signal.js\nimport { Signal } from 'bdn-pocket'\n\nexport const triggerLoadUser = Signal.def('trigger load user')\n\n// in /module/user/container\nimport { connect } from 'react-redux'\n\nimport * as userSig from '../signals' // <- signals are here\nimport MyComp from '../component/my_comp'\n\nexport default connect(\n  null,\n  function dispatchToProps(dispatch) {\n    return {\n      loadUser(userId) {\n        dispatch(userSig.triggerLoadUser({ userId }) // <- signal accept only one arg\n      }\n    }\n  }\n)(MyComp)\n\n````\n\n#### Watch (in saga)\n````javascript\n// in /module/user/signal.js\nimport { Signal } from 'bdn-pocket'\n\nexport const triggerLoadUser = Signal.def('trigger load user')\n\n// in /module/user/sagas\nimport { take, call } from 'redux-saga/effects'\nimport * as userSig from '../signals' // <- signals are here\n\nexport function* watchLoadUser() {\n  while(true) {\n    const { payload } = yield take(userSig.triggerLoadUser.CONST)\n    yield call(loadUser, payload)\n  }\n}\n\nfunction* loadUser({ userId }) {\n  // do some side effect here\n}\n\n````\n\n### Messenger\nA `Messenger` is a list of `message`s associated with [redux] `reducer`s.\n\nHelper functions help you to combine a `messenger` in the global [redux] `reducer`.\n\n\n#### Create\n\n````javascript\n/// in /module/user/messages.js\n\nimport {\n  Messenger,\n  Action,\n  Types,\n  } from 'bdn-pocket'\nimport R from 'ramda' // <- yes we use Ramda a lot\n\nconst { string } = Types\nconst state = {\n  users: {\n    'user1': {\n      id: 'a',\n      name: 'name',\n      email: 'email',\n    }\n  }\n}\nexport const user = Messenger\n  .add({\n    key: 'add',\n    action: Action\n      .propTypes('id', 'name', 'email')\n      .def('add user'),\n    reducer(state, { payload: { id, name, email } }) {\n      return R.assoc(\n        id,\n        { id, name, email },\n        state\n      )\n    }\n  })\n  .add({\n    key: 'del',\n    action: Action\n      .propTypes('id')\n      .def('del user'),\n    reducer(state, { payload: { id } }) {\n      return R.dissoc(\n        id,\n        state\n      )\n    }\n  })\n  .add({\n    key: 'update',\n    action: Action\n      .propTypes({\n        id: string,\n        name: string(required: false),\n        name: string(required: false),\n      })\n      .def('update user'),\n    reducer(state, { payload: data }) {\n      return R.mergeWith( // <- yes, it is a special ramda trick\n        R.merge,\n        state,\n        { [data.id]:  userData }\n      )\n    }\n  })\n  .create({ name: 'user entities' }) // <- DO NOT FORGET TO CREATE YOUR MESSENGER INSTANCE\n\n\n````\n\n#### Combine with [redux] `reducer` (`makeReducer`)\n\n````javascript\n/// in /module/user/messages.js\n\nimport {\n  Messenger,\n  Action,\n  makeReducer, // <- here we added makeReducer\n} from 'bdn-pocket'\nimport R from 'ramda' // <- yes we use Ramda a lot\n// ... same code as before\n\nexport default makeReducer(user) // <- now you can combine this reducer with global redux reducer\n````\n\nIf you define more than one `messenger` in a messenger file, you can use [redux] `combineReducer` helper to export a default reducer from your file\n\n````javascript\n/// in /module/user/messages.js\n\nimport {\n  Messenger,\n  Action,\n  makeReducer, // <- here we added makeReducer\n} from 'bdn-pocket'\nimport R from 'ramda' // <- yes we use Ramda a lot\nimport { combineRecuers } from 'redux'\n\nexport const user = Messenger\n  .add({\n    ...\n  })\n  .create({ name: 'user' })\n\nexport const account = Messenger\n  .add({\n    ...\n  })\n  .create({ name: 'account' })\n\nexport default combineReducer({\n  user: makeReducer(user),\n  account: makeReducer(account)\n}) // <- now you can combine this reducer with global redux reducer\n\n````\n\n#### Call message (from saga)\n\nA `message` has to be dispatch in order to call associated reducer.\n\nTo create a `message` you have to use the message create accessible with `key` on `messenger`\n\n````javascript\n\n// in /module/user/sagas\nimport { take, call, put } from 'redux-saga/effects'\nimport * as userSig from '../signals'\nimport * as userMsg from '../messages'\n\nexport function* watchLoadUser() {\n  while(true) {\n    const { payload } = yield take(userSig.triggerLoadUser.CONST)\n    yield call(loadUser, payload)\n  }\n}\n\nfunction* loadUser({ userId }) {\n  // do some side effect here ...\n  // here we assume we receive a user data from our server\n  const userData = {\n    id: 'me',\n    name: 'Arnaud',\n    email: 'amelon@b-flower.com',\n  }\n\n  //                add is the key of Message in messenger\n  yield put(userMsg.add(userData)) // <-- create message -> dispatch (put) -> call reducer -> new state\n}\n\n// other exemple\nfunction* delUser({ userId }) {\n  // do some server stuff ...\n  // now delete user in state\n\n  //                del is the key of Message creator in messenger\n  yield put(userMsg.del({ id: userId }))\n}\n\n````\n\n#### Path reducer (`makePathReducer`)\nSometimes (often) you want to use payload property as key of a substate.\n\nIn our previous exemple, we use `payload.id` to put a specific user data under this sub state.\n\n````javascript\n// example of our users state\nconst state = {\n  users: {\n    me: { // id is used as key in our users sub state\n      id: 'me',\n      name: 'Arnaud',\n      //...\n    }\n  }\n}\n````\n\nTo facilitate this common pattern, we use `makePathReducer`.\n\n````javascript\n/// in /module/user/messages.js\n\nimport {\n  Messenger,\n  Action,\n  Types,\n  makePathReducer,\n  } from 'bdn-pocket'\nimport R from 'ramda' // <- yes we use Ramda a lot\n\nconst { string } = Types\nconst { DELETE_KEY } = makePathReducer\n\n// here state manipulation is easier\n// state in reducer is directly `users.id` (in first exemple it was `users`)\nexport const user = Messenger\n  .add({\n    key: 'add',\n    action: Action\n      .propTypes('id', 'name', 'email')\n      .def('add user'),\n    reducer(state, { payload }) {\n      return payload\n    }\n  })\n  .add({\n    key: 'del',\n    action: Action\n      .propTypes('id')\n      .def('del user'),\n    reducer(state, { payload: { id } }) {\n      return DELETE_KEY // special trick => will remove key from state\n    }\n  })\n  .add({\n    key: 'update',\n    action: Action\n      .propTypes({\n        id: string,\n        name: string(required: false),\n        name: string(required: false),\n      })\n      .def('update user'),\n    reducer(state, { payload: data }) {\n      return R.merge(state, data)\n\n    }\n  })\n  .create({ name: 'user entities' }) // <- DO NOT FORGET TO CREATE YOUR MESSENGER INSTANCE\n\nexport default makePathReducer(\n  user,\n  (payload) => payload.id\n  // or even simpler\n  // ({ id }) => id\n)\n\n````\n\n\n### Selector & SliceSelector\n[reselect] is a wonderfull library but it misses `selector` with `arguments`.\n\nWith `Selector` you can send props to your `selector` to make some filtering or choices.\n\nYou can ensure your props as `Selector` is composed of `PropTypes`.\n\n`Selector` used [reselect] under the hood and implements it's own memoization to handle props.\n\nYou can compose a `Selector` with another `Selector` (see `getArticle` example)\n\nA composed `Selector` (see `userSel` in `getArticle`) return a partial function that is memoized once.\n\nIt is usefull for computation selection.\n\nDo not use `Selector` to get a slice of state.\nUse `SliceSelector` in this case.\n\nAs `Selector` memoizes the last `reducer` result, if you want to only get a portion of your state without any computation, it won't be performant to run memoization and props comparison check.\n\n\n````javascript\nimport { Selector, SliceSelector } from 'bdn-pocket'\nconst state = {\n  \"articles\": {\n    \"123\": {\n      id: \"123\",\n      author: \"1\",\n      title: \"My awesome blog post\",\n      comments: [ \"324\" ]\n    }\n  },\n  \"users\": {\n    \"1\": { \"id\": \"1\", \"name\": \"Paul\" },\n    \"2\": { \"id\": \"2\", \"name\": \"Nicole\" }\n  },\n  \"comments\": {\n    \"324\": { id: \"324\", \"commenter\": \"2\" }\n  }\n}\n\nconst getSlice = (name) => (state) => state[name]\n\nconst getUsers = getSlice('users')\n\nconst getUser = SliceSelector\n  .selectors({\n    users: getUsers\n  })\n  .propTypes('userId')\n  .create({\n    // first arg = list of sub states\n    // second arg = list of props send\n    reducer({ users },  { userId }) {\n      return users[userId]\n    }\n  })\n\n\nconst getComments = state => state.comments\n\nconst getComment = SliceSelector\n  .selectors({\n    comments: getComments,\n  })\n  .propTypes('commentId')\n  .create({\n    reducer({ comments }, { commentId }) {\n      return comments[commentId]\n    }\n  })\n\n\nconst getArticles = state => state.articles\n\nconst getArticle = Selector\n  .selectors({\n    userSel: getUser,\n    commentSel: getComment,\n    articles: getArticles,\n  })\n  .propTypes('articleId')\n  .create({\n    reducer({ userSel, commentSel, articles }, { articleId }) {\n      // userSel & commentSel are partial functions that wait for theirs props ({userId} for userSel, { commentId } for commentSel )\n      const article = articles[articleId]\n      const comments = article.comments.map(\n        commentId => {\n          const comment = commentSel({ commentId })\n          const user = userSel({ userId: comment.commenter })\n          return {\n            comment,\n            commenter: user\n          }\n        }\n      )\n      return {\n        article,\n        comments\n      }\n    }\n  })\n\n\n````\n\n\n\n[redux]: https://github.com/reactjs/redux\n[redux-saga]: https://github.com/redux-saga/redux-saga\n[reselect]: https://github.com/reactjs/reselect\n[b-eden]: http://beden.b-flower.com/\n[stampit]: https://github.com/stampit-org/stamp/tree/master/packages/it\n[Principles]: #principles","maintainers":[{"name":"kcoulibaly","email":"kcoulibaly@b-flower.com"},{"name":"amelon","email":"paztaga@gmail.com"},{"name":"gabchang","email":"dotgab@gmail.com"}],"time":{"modified":"2022-04-04T17:23:26.562Z","created":"2018-01-30T13:50:05.353Z","1.2.0":"2018-01-30T13:50:05.353Z"},"homepage":"https://bitbucket.org/bflower/bdn-pocket#readme","keywords":["redux","redux-saga","reselect","action","action-creator","stampit"],"repository":{"type":"git","url":"git+ssh://git@bitbucket.org/bflower/bdn-pocket.git"},"author":{"name":"Arnaud Mélon","url":"https://github.com/amelon"},"license":"Apache-2.0","readmeFilename":"README.md"}