# cat-stack
** work in progress **
modular framework for data-driven real-time apps
![cat stack](http://i.imgur.com/v5zw1z3.jpg)
made by [Enspiral Craftworks](http://craftworks.enspiral.com)
## resources
- [thinking in react](https://facebook.github.io/react/docs/thinking-in-react.html)
- [simplest redux example](https://github.com/jackielii/simplest-redux-example/blob/master/index.js)
- [smart and dumb components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
- [css modules](http://glenmaddern.com/articles/css-modules)
- [mixins are dead, long live higher-order components](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750)
- [react on es6+](http://babeljs.io/blog/2015/06/07/react-on-es6-plus/)
- [how to use classes and sleep at night](https://medium.com/@dan_abramov/how-to-use-classes-and-sleep-at-night-9af8de78ccb4)
- [common react mistakes: unneeded state](http://reactkungfu.com/2015/09/common-react-dot-js-mistakes-unneeded-state/)
- [promise cookbook](https://github.com/mattdesl/promise-cookbook)
- [approaches to testing react components](http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/)
- [unit testing react components without a dom](http://simonsmith.io/unit-testing-react-components-without-a-dom/)
## stack
- node version manager: [`nvm`](https://github.com/creationix/nvm)
- when in this directory run `nvm use` which will use the version of `node` specified in [our .nvmrc](./.nvmrc).
- package manager: [`npm@3`](https://www.npmjs.com/)
- install with `npm install -g npm@3`
- to install a package, run `npm install --save my-favorite-package`
- task runner: [npm scripts](http://substack.net/task_automation_with_npm_run)
- browser bundler: [browserify](https://github.com/substack/browserify-handbook)
- es6/jsx transpiler: [babel](babeljs.io/) ([babel-require-hook](https://www.npmjs.com/package/babel-require-hook) and [babelify](https://www.npmjs.com/package/babelify))
- modular css: [css modules](http://glenmaddern.com/articles/css-modules) ([css-modules-require-hook](https://www.npmjs.com/package/css-modules-require-hook) and [cssify](https://www.npmjs.com/package/cssify))
- bulk require: [bulk-require](https://www.npmjs.com/package/bulk-require) and [bulkify](https://www.npmjs.com/package/bulkify)
- configuration: [simple-rc](https://www.npmjs.com/package/simple-rc) and [evalify](https://www.npmjs.com/package/evalify)
- utility functions: [lodash](https://lodash.com/docs/)
- data model: [tcomb](https://github.com/gcanti/tcomb)
- database: [knex](https://www.npmjs.com/package/knex)
- api service: [feathers-knex](https://www.npmjs.com/package/feathers-knex)
- api validator: [feathers-tcomb](https://www.npmjs.com/package/feathers-tcomb)
- TODO api authentication: [feathers-authentication](https://www.npmjs.org/package/feathers-authentication)
- api transport: [feathers-rest](https://www.npmjs.com/package/feathers-rest)
- client transport: [isomorphic-fetch](https://www.npmjs.com/package/isomorphic-fetch)
- client: [feathers-client](https://www.npmjs.com/package/feathers-client)
- render action { creators, reducers }: [feathers-action](https://www.npmjs.com/package/feathers-action)
- render action store: [redux](https://www.npmjs.com/package/redux)
- render getters: [reselect](https://www.npmjs.com/package/reselect)
- render router: [redux-router](https://www.npmjs.com/package/redux-router)
- render views: [react](https://www.npmjs.com/package/react)
- render forms: [tcomb-form](https://github.com/gcanti/tcomb-form)
- test specs: [ava](https://www.npmjs.com/package/ava)
- test features: [cucumber](https://www.npmjs.com/package/cucumber)
- test browser: [zombie](http://zombie.js.org/)
- TODO generators: [plop](https://github.com/amwmedia/plop)
## scripts
- [install](#install)
- [start](#start)
- [test](#test)
- [lint](#lint)
- [format](#format)
- [database](#database)
- [pg](#pg)
- [knex](#knex)
### install
```shell
npm install
```
### start
#### dev
starts development environment
```shell
npm run dev
```
#### prod
starts production environment
```shell
npm run prod
```
### test
runs tests
```shell
npm test
```
#### test:spec
runs [ava](https://www.npmjs.com/package/ava) tests
can optionally take a [glob](https://www.npmjs.com/package/glob)
```shell
npm run test:spec -- './app/todos/**/*.spec.js'
```
default glob is `./app/**/*.spec.js`
#### test:feature
runs [cucumber](https://www.npmjs.com/package/cucumber) tests
can optionally take a [glob](https://www.npmjs.com/package/glob)
```shell
npm run test:feature -- './features/todo.feature`
```
default glob is `./features/**/*.feature`
### lint
checks for [standard style](http://standardjs.com)
can optionally take a [glob](https://www.npmjs.com/package/glob)
```shell
npm run lint -- './app/todos/**/*.js'
```
default glob is `./**/*.js` ignoring `node_modules`
### format
converts to [standard](http://standardjs.com) if possible
can optionally take a [glob](https://www.npmjs.com/package/glob)
```shell
npm run format -- './app/**/*.js'
```
default glob is `./**/*.js` ignoring `node_modules`
### database
#### pg
to run a local postgres db with [`docker`](https://docs.docker.com/) installed,
- to install db, run `npm run pg:pull`
- to create db, run `npm run pg:run`
- to start db, run `npm run pg:start`
- to stop db, run `npm run pg:stop`
- to remove db, run `npm run pg:rm`
- to show db logs, run `npm run pg:logs`
#### knex
run [any `knex` command](http://knexjs.org/#Migrations-CLI) with `npm run knex -- [command] [args]`
for example, run latest sql migrations with
```shell
npm run knex -- migrate:latest
```
and generate new sql migration with
```shell
npm run knex -- migrate:make add_some_columns_and_stuff
```
## directory structure
- `/config/`
- `/config/index.js`
- `/config/${ NODE_ENV }.js`
- `/app/${ module }/`
- symlink `/app` to `/node_modules/app`
- only do relative requires (`require('./models')`) if within module
- otherwise, always require top-down (`require('app/things/models')`).
- `/feature/`
- `/feature/support/world.js`
- `/feature/support/hooks.js`
- `/feature/step_definitions/steps.js`
- `/feature/smoke.feature`
- spec tests are any files that end in `.spec.js`
### app entry points
our server code is run as separate processes, namely:
- `static`: static file server using [`http`](https://nodejs.org/api/http.html) and [`ecstatic`](https://www.npmjs.com/package/ecstatic)
- `api`: RESTful HTTP interface to data using [`feathers`](http://feathersjs.com) and [`connect` middleware](https://github.com/senchalabs/connect#readme)
- `render`: server-side rendering using [`http`](https://nodejs.org/api/http.html), [`react-dom/server`](https://www.npmjs.com/package/react-dom), [`feathers-client`](https://www.npmjs.com/package/feathers-client), and more...
our client code for `render` is bundled using [`browserify`](http://browserify.org), which similar to the server `render` process also uses [`react-dom`](https://www.npmjs.com/package/react-dom), [`feathers-client`](https://www.npmjs.com/package/feathers-client), and more...
each server process has a separate url available to other entry points via the [./config](./config)
### app modules
in contrast to frameworks like Rails which split our `app` into directories for each "type" of file (models, views, controllers), our `app` is split into directories for each module, where each module contains the various types of files *within* that module.
each module directory may contain any of:
- `index.js`: exports all the below exports from the module
- `models.js`: exports [`tcomb`](https://www.npmjs.com/package/tcomb) models
- `constants.js`: exports constants (such as [redux action types](https://www.npmjs.com/package/create-action-types))
- `actions.js`: exports [redux actions](https://www.npmjs.com/package/redux-actions) (recommended to use [`feathers-action`](https://npmjs.com/package/feathers-action))
- `reducer.js`: exports [redux reducer](http://redux.js.org/docs/basics/Reducers.html) (recommended to use [`feathers-action`](https://npmjs.com/package/feathers-action))
- `routes.js`: exports [`function (store) { return }`](https://www.npmjs.com/package/react-router)
- `getters.js`: exports [`reselect`](https://www.npmjs.com/package/reselect) getters for use in containers' `connect`
- `containers/*.js`: exports [smart component](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) [`connect`](https://github.com/rackt/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options)ed with [`react-redux`](https://www.npmjs.com/package/react-redux)
- `components/*.js`: exports [dumb component](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
- `components/*.css`: exports [css modules](http://glenmaddern.com/articles/css-modules) for respective component
- `service.js`: exports [`feathers`](http://feathersjs.com) service (recommended to use [`feathers-knex`](https://github.com/feathersjs/feathers-knex) and [`feathers-tcomb`](https://github.com/ahdinosaur/feathers-tcomb))
## FAQ
### how do i do relations between models?
implement them in your `getters.js` file as selectors.
```js
// app/groups/getter.js
import { createSelector } from 'reselect'
import { getMemberships } from 'app/memberships/getters'
export const getGroups = (state) => state.groups
export const getMembersByGroupId = createSelector (
getGroups,
getMemberships,
(groups, memberships) => {
const isInGroup = (group) => (membership) => {
return group.id === membership.groupId
}
return Object.values(groups).map((group) => {
return Object.values(memberships)
.filter(isInGroup(group))
.map((membership) => membership.memberId)
})
}
)
```
in the future, we should extract common relations into helper creators.
### how to set default props
```jsx
import React from 'react'
export default class Thing extends React.Component {
static displayName = 'Thing'
static defaultProps = {
isAwesome: true
}
...
}
```
### how to set prop types
```jsx
import React, { PropTypes } from 'react'
export default class Thing extends React.Component {
static displayName = 'Thing'
static propTypes = {
increment: PropTypes.function
}
...
}
```
### how to bind functions to parent component when passing them down?
```jsx
import React, { PropTypes } from 'react'
export class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { a: 0 }
}
render () {
return
}
increment = () => {
this.setState({ a: this.state.a + 1 })
}
}
export class Child extends React.Component {
static propTypes = {
increment: PropTypes.function
}
render () {
return
}
}
```
## known issues
- adding a new file won't always be noticed by `node-dev` or `watchify` due to usage of `bulk-require`). potential fix is to use `chokidar-cli` and some transform to watch for new files and re-run the script command