├── .babelrc
├── CHANGELOG.md
├── .travis.yml
├── test
├── setup
│ ├── .globals.json
│ ├── browser.js
│ ├── node.js
│ └── setup.js
├── .eslintrc
├── runner.html
└── unit
│ └── extensible-duck.js
├── .eslintrc
├── .editorconfig
├── .gitignore
├── LICENSE
├── package.json
├── dist
├── extensible-duck.min.js
├── extensible-duck.js
├── extensible-duck.js.map
└── extensible-duck.min.js.map
├── src
└── extensible-duck.js
├── gulpfile.js
└── README.md
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"],
3 | "plugins": ["transform-object-rest-spread"]
4 | }
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### [1.0.0](https://github.com/investtools/extensible-duck/releases/tag/v1.0.0)
2 |
3 | - The first release
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | - "stable"
5 | sudo: false
6 | script: "gulp coverage"
7 | after_success:
8 | - npm install -g codeclimate-test-reporter
9 | - codeclimate-test-reporter < coverage/lcov.info
10 |
--------------------------------------------------------------------------------
/test/setup/.globals.json:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "expect": true,
4 | "mock": true,
5 | "sandbox": true,
6 | "spy": true,
7 | "stub": true,
8 | "useFakeServer": true,
9 | "useFakeTimers": true,
10 | "useFakeXMLHttpRequest": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 6,
4 | "sourceType": "module",
5 | "ecmaFeatures": {
6 | "experimentalObjectRestSpread": true
7 | }
8 | },
9 | "rules": {},
10 | "env": {
11 | "browser": true,
12 | "node": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/setup/browser.js:
--------------------------------------------------------------------------------
1 | var mochaGlobals = require('./.globals.json').globals
2 |
3 | window.mocha.setup('bdd')
4 | window.onload = function() {
5 | window.mocha.checkLeaks()
6 | window.mocha.globals(Object.keys(mochaGlobals))
7 | window.mocha.run()
8 | require('./setup')(window)
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | root = true;
4 |
5 | [*]
6 | # Ensure there's no lingering whitespace
7 | trim_trailing_whitespace = true
8 | # Ensure a newline at the end of each file
9 | insert_final_newline = true
10 |
11 | [*.js]
12 | # Unix-style newlines
13 | end_of_line = lf
14 | charset = utf-8
15 | indent_style = space
16 | indent_size = 2
17 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./setup/.globals.json",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "experimentalObjectRestSpread": true
8 | }
9 | },
10 | "rules": {
11 | "strict": 0,
12 | "quotes": [2, "single"],
13 | "no-unused-expressions": 0
14 | },
15 | "env": {
16 | "browser": true,
17 | "node": true,
18 | "mocha": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/setup/node.js:
--------------------------------------------------------------------------------
1 | global.chai = require('chai')
2 | global.sinon = require('sinon')
3 | global.chai.use(require('sinon-chai'))
4 |
5 | require('babel-core/register')
6 | require('./setup')()
7 |
8 | /*
9 | Uncomment the following if your library uses features of the DOM,
10 | for example if writing a jQuery extension, and
11 | add 'simple-jsdom' to the `devDependencies` of your package.json
12 |
13 | Note that JSDom doesn't implement the entire DOM API. If you're using
14 | more advanced or experimental features, you may need to switch to
15 | PhantomJS. Setting that up is currently outside of the scope of this
16 | boilerplate.
17 | */
18 | // import simpleJSDom from 'simple-jsdom';
19 | // simpleJSDom.install();
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 | bower_components
27 | coverage
28 | tmp
29 |
30 | # Users Environment Variables
31 | .lock-wscript
32 |
--------------------------------------------------------------------------------
/test/setup/setup.js:
--------------------------------------------------------------------------------
1 | module.exports = function(root) {
2 | root = root ? root : global
3 | root.expect = root.chai.expect
4 |
5 | beforeEach(() => {
6 | // Using these globally-available Sinon features is preferrable, as they're
7 | // automatically restored for you in the subsequent `afterEach`
8 | root.sandbox = root.sinon.sandbox.create()
9 | root.stub = root.sandbox.stub.bind(root.sandbox)
10 | root.spy = root.sandbox.spy.bind(root.sandbox)
11 | root.mock = root.sandbox.mock.bind(root.sandbox)
12 | root.useFakeTimers = root.sandbox.useFakeTimers.bind(root.sandbox)
13 | root.useFakeXMLHttpRequest = root.sandbox.useFakeXMLHttpRequest.bind(
14 | root.sandbox
15 | )
16 | root.useFakeServer = root.sandbox.useFakeServer.bind(root.sandbox)
17 | })
18 |
19 | afterEach(() => {
20 | delete root.stub
21 | delete root.spy
22 | root.sandbox.restore()
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/test/runner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tests
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Andre Aizim Kelmanosn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "extensible-duck",
3 | "version": "1.6.0",
4 | "description": "Modular and Extensible Redux Reducer Bundles (ducks-modular-redux)",
5 | "main": "dist/extensible-duck.js",
6 | "scripts": {
7 | "test": "gulp",
8 | "lint": "gulp lint",
9 | "test-browser": "gulp test-browser",
10 | "watch": "gulp watch",
11 | "build": "gulp build",
12 | "precommit": "lint-staged",
13 | "prettier": "prettier --no-semi --single-quote --trailing-comma es5 --write '{src,test}/**/*.js'",
14 | "coverage": "gulp coverage"
15 | },
16 | "lint-staged": {
17 | "**/{src,test}/**/*.js": [
18 | "prettier --no-semi --single-quote --trailing-comma es5 --write",
19 | "git add"
20 | ]
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/investtools/extensible-duck.git"
25 | },
26 | "keywords": [],
27 | "author": "Andre Aizim Kelmanosn ",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/investtools/extensible-duck/issues"
31 | },
32 | "homepage": "https://github.com/investtools/extensible-duck",
33 | "devDependencies": {
34 | "babel-cli": "6.24.1",
35 | "babel-core": "6.25.0",
36 | "babel-eslint": "7.2.3",
37 | "babel-loader": "7.1.1",
38 | "babel-plugin-transform-object-rest-spread": "6.23.0",
39 | "babel-polyfill": "6.23.0",
40 | "babel-preset-es2015": "6.24.1",
41 | "babel-register": "6.24.1",
42 | "chai": "3.5.0",
43 | "del": "2.2.2",
44 | "glob": "7.0.6",
45 | "gulp": "3.9.1",
46 | "gulp-eslint": "3.0.1",
47 | "gulp-filter": "4.0.0",
48 | "gulp-istanbul": "1.1.1",
49 | "gulp-livereload": "3.8.1",
50 | "gulp-load-plugins": "1.2.4",
51 | "gulp-mocha": "3.0.1",
52 | "gulp-plumber": "1.1.0",
53 | "gulp-rename": "1.2.2",
54 | "gulp-sourcemaps": "2.6.0",
55 | "gulp-uglify": "3.0.0",
56 | "isparta": "4.0.0",
57 | "json-loader": "0.5.4",
58 | "lint-staged": "4.0.2",
59 | "mocha": "3.0.2",
60 | "prettier": "1.5.3",
61 | "reselect": "^3.0.1",
62 | "sinon": "1.17.5",
63 | "sinon-chai": "2.8.0",
64 | "webpack": "3.2.0",
65 | "webpack-stream": "3.2.0"
66 | },
67 | "babelBoilerplateOptions": {
68 | "entryFileName": "extensible-duck.js",
69 | "mainVarName": "Duck"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/dist/extensible-duck.min.js:
--------------------------------------------------------------------------------
1 | !function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define([],r):"object"==typeof exports?exports.Duck=r():t.Duck=r()}(this,function(){return function(e){var n={};function o(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,o),r.l=!0,r.exports}return o.m=e,o.c=n,o.d=function(t,r,e){o.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:e})},o.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(r,"a",r),r},o.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},o.p="",o(o.s=0)}([function(t,r,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.constructLocalized=u,e.d(r,"constructLocalised",function(){return u}),e.d(r,"Selector",function(){return s});var n=function(){function n(t,r){for(var e=0;e typeValue(namespace, store, type)))
23 | }
24 |
25 | function isObject(obj) {
26 | return obj !== null && typeof obj === 'object'
27 | }
28 |
29 | function isFunction(func) {
30 | return func !== null && typeof func === 'function'
31 | }
32 |
33 | function isUndefined(value) {
34 | return typeof value === 'undefined' || value === undefined
35 | }
36 |
37 | function isPlainObject(obj) {
38 | return (
39 | isObject(obj) &&
40 | (obj.constructor === Object || // obj = {}
41 | obj.constructor === undefined) // obj = Object.create(null)
42 | )
43 | }
44 |
45 | function mergeDeep(target, ...sources) {
46 | if (!sources.length) return target
47 | const source = sources.shift()
48 |
49 | if (Array.isArray(target)) {
50 | if (Array.isArray(source)) {
51 | target.push(...source)
52 | } else {
53 | target.push(source)
54 | }
55 | } else if (isPlainObject(target)) {
56 | if (isPlainObject(source)) {
57 | for (let key of Object.keys(source)) {
58 | if (!target[key]) {
59 | target[key] = source[key]
60 | } else {
61 | mergeDeep(target[key], source[key])
62 | }
63 | }
64 | } else {
65 | throw new Error(`Cannot merge object with non-object`)
66 | }
67 | } else {
68 | target = source
69 | }
70 |
71 | return mergeDeep(target, ...sources)
72 | }
73 |
74 | function assignDefaults(options) {
75 | return {
76 | ...options,
77 | consts: options.consts || {},
78 | sagas: options.sagas || (() => ({})),
79 | takes: options.takes || (() => []),
80 | creators: options.creators || (() => ({})),
81 | selectors: options.selectors || {},
82 | types: options.types || [],
83 | }
84 | }
85 |
86 | function injectDuck(input, duck) {
87 | if (input instanceof Function) {
88 | return input(duck)
89 | } else {
90 | return input
91 | }
92 | }
93 |
94 | function getLocalizedState(globalState, duck) {
95 | let localizedState
96 |
97 | if (duck.storePath) {
98 | const segments = [].concat(duck.storePath.split('.'), duck.store)
99 | localizedState = segments.reduce(function getSegment(acc, segment) {
100 | if (!acc[segment]) {
101 | throw Error(
102 | `state does not contain reducer at storePath ${segments.join('.')}`
103 | )
104 | }
105 | return acc[segment]
106 | }, globalState)
107 | } else {
108 | localizedState = globalState[duck.store]
109 | }
110 |
111 | return localizedState
112 | }
113 |
114 | export function constructLocalized(selectors) {
115 | const derivedSelectors = deriveSelectors(selectors)
116 | return duck => {
117 | const localizedSelectors = {}
118 | Object.keys(derivedSelectors).forEach(key => {
119 | const selector = derivedSelectors[key]
120 | localizedSelectors[key] = globalState =>
121 | selector(getLocalizedState(globalState, duck), globalState)
122 | })
123 | return localizedSelectors
124 | }
125 | }
126 |
127 | // An alias for those who do not use the above spelling.
128 | export { constructLocalized as constructLocalised }
129 |
130 | /**
131 | * Helper utility to assist in composing the selectors.
132 | * Previously defined selectors can be used to derive future selectors.
133 | *
134 | * @param {object} selectors
135 | * @returns
136 | */
137 | function deriveSelectors(selectors) {
138 | const composedSelectors = {}
139 | Object.keys(selectors).forEach(key => {
140 | const selector = selectors[key]
141 | if (selector instanceof Selector) {
142 | composedSelectors[key] = (...args) =>
143 | (composedSelectors[key] = selector.extractFunction(composedSelectors))(
144 | ...args
145 | )
146 | } else {
147 | composedSelectors[key] = selector
148 | }
149 | })
150 | return composedSelectors
151 | }
152 |
153 | export default class Duck {
154 | constructor(options) {
155 | options = assignDefaults(options)
156 | const {
157 | namespace,
158 | store,
159 | storePath,
160 | types,
161 | consts,
162 | initialState,
163 | creators,
164 | selectors,
165 | sagas,
166 | takes,
167 | } = options
168 | this.options = options
169 | Object.keys(consts).forEach(name => {
170 | this[name] = zipObject(consts[name], consts[name])
171 | })
172 |
173 | this.store = store
174 | this.storePath = storePath
175 | this.types = buildTypes(namespace, store, types)
176 | this.initialState = isFunction(initialState)
177 | ? initialState(this)
178 | : initialState
179 | this.reducer = this.reducer.bind(this)
180 | this.selectors = deriveSelectors(injectDuck(selectors, this))
181 | this.creators = creators(this)
182 | this.sagas = sagas(this)
183 | this.takes = takes(this)
184 | }
185 | reducer(state, action) {
186 | if (isUndefined(state)) {
187 | state = this.initialState
188 | }
189 | return this.options.reducer(state, action, this)
190 | }
191 | extend(options) {
192 | if (isFunction(options)) {
193 | options = options(this)
194 | }
195 | options = assignDefaults(options)
196 | const parent = this.options
197 | let initialState
198 | if (isFunction(options.initialState)) {
199 | initialState = duck => options.initialState(duck, this.initialState)
200 | } else if (isUndefined(options.initialState)) {
201 | initialState = parent.initialState
202 | } else {
203 | initialState = options.initialState
204 | }
205 | return new Duck({
206 | ...parent,
207 | ...options,
208 | initialState,
209 | consts: mergeDeep({}, parent.consts, options.consts),
210 | sagas: duck => {
211 | const parentSagas = parent.sagas(duck)
212 | return { ...parentSagas, ...options.sagas(duck, parentSagas) }
213 | },
214 | takes: duck => {
215 | const parentTakes = parent.takes(duck)
216 | return [...parentTakes, ...options.takes(duck, parentTakes)]
217 | },
218 | creators: duck => {
219 | const parentCreators = parent.creators(duck)
220 | return { ...parentCreators, ...options.creators(duck, parentCreators) }
221 | },
222 | selectors: duck => ({
223 | ...injectDuck(parent.selectors, duck),
224 | ...injectDuck(options.selectors, duck),
225 | }),
226 | types: [...parent.types, ...options.types],
227 | reducer: (state, action, duck) => {
228 | state = parent.reducer(state, action, duck)
229 | if (isUndefined(options.reducer)) {
230 | return state
231 | } else {
232 | return options.reducer(state, action, duck)
233 | }
234 | },
235 | })
236 | }
237 | }
238 |
239 | export class Selector {
240 | constructor(func) {
241 | this.func = func
242 | }
243 |
244 | extractFunction(selectors) {
245 | return this.func(selectors)
246 | }
247 | }
248 |
249 | Duck.Selector = Selector
250 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp')
2 | const loadPlugins = require('gulp-load-plugins')
3 | const del = require('del')
4 | const glob = require('glob')
5 | const path = require('path')
6 | const isparta = require('isparta')
7 | const webpack = require('webpack')
8 | const webpackStream = require('webpack-stream')
9 |
10 | const Instrumenter = isparta.Instrumenter
11 | const mochaGlobals = require('./test/setup/.globals')
12 | const manifest = require('./package.json')
13 |
14 | // Load all of our Gulp plugins
15 | const $ = loadPlugins()
16 |
17 | // Gather the library data from `package.json`
18 | const config = manifest.babelBoilerplateOptions
19 | const mainFile = manifest.main
20 | const destinationFolder = path.dirname(mainFile)
21 | const exportFileName = path.basename(mainFile, path.extname(mainFile))
22 |
23 | function cleanDist(done) {
24 | del([destinationFolder]).then(() => done())
25 | }
26 |
27 | function cleanTmp(done) {
28 | del(['tmp']).then(() => done())
29 | }
30 |
31 | // Lint a set of files
32 | function lint(files) {
33 | return gulp
34 | .src(files)
35 | .pipe($.eslint())
36 | .pipe($.eslint.format())
37 | .pipe($.eslint.failAfterError())
38 | }
39 |
40 | function lintSrc() {
41 | return lint('src/**/*.js')
42 | }
43 |
44 | function lintTest() {
45 | return lint('test/**/*.js')
46 | }
47 |
48 | function lintGulpfile() {
49 | return lint('gulpfile.js')
50 | }
51 |
52 | function build() {
53 | return gulp
54 | .src(path.join('src', config.entryFileName))
55 | .pipe(
56 | webpackStream(
57 | {
58 | output: {
59 | filename: `${exportFileName}.js`,
60 | libraryTarget: 'umd',
61 | library: config.mainVarName,
62 | },
63 | // Add your own externals here. For instance,
64 | // {
65 | // jquery: true
66 | // }
67 | // would externalize the `jquery` module.
68 | externals: {},
69 | module: {
70 | rules: [
71 | {
72 | test: /\.js$/,
73 | exclude: /node_modules/,
74 | use: [
75 | {
76 | loader: 'babel-loader',
77 | query: {
78 | babelrc: false,
79 | presets: [
80 | [
81 | 'es2015',
82 | {
83 | // Enable tree-shaking by disabling commonJS transformation
84 | modules: false,
85 | },
86 | ],
87 | ],
88 | plugins: ['transform-object-rest-spread'],
89 | },
90 | },
91 | ],
92 | },
93 | ],
94 | },
95 | devtool: 'source-map',
96 | },
97 | webpack
98 | )
99 | )
100 | .pipe(gulp.dest(destinationFolder))
101 | .pipe($.filter(['**', '!**/*.js.map']))
102 | .pipe($.rename(`${exportFileName}.min.js`))
103 | .pipe($.sourcemaps.init({ loadMaps: true }))
104 | .pipe($.uglify())
105 | .pipe($.sourcemaps.write('./'))
106 | .pipe(gulp.dest(destinationFolder))
107 | }
108 |
109 | function _mocha() {
110 | return gulp
111 | .src(['test/setup/node.js', 'test/unit/**/*.js'], { read: false })
112 | .pipe(
113 | $.mocha({
114 | reporter: 'dot',
115 | globals: Object.keys(mochaGlobals.globals),
116 | ignoreLeaks: false,
117 | })
118 | )
119 | }
120 |
121 | function _registerBabel() {
122 | require('babel-register')
123 | require('babel-polyfill')
124 | }
125 |
126 | function test() {
127 | _registerBabel()
128 | return _mocha()
129 | }
130 |
131 | function coverage(done) {
132 | _registerBabel()
133 | gulp
134 | .src(['src/**/*.js'])
135 | .pipe(
136 | $.istanbul({
137 | instrumenter: Instrumenter,
138 | includeUntested: true,
139 | })
140 | )
141 | .pipe($.istanbul.hookRequire())
142 | .on('finish', () => {
143 | return test().pipe($.istanbul.writeReports()).on('end', done)
144 | })
145 | }
146 |
147 | const watchFiles = ['src/**/*', 'test/**/*', 'package.json', '**/.eslintrc']
148 |
149 | // Run the headless unit tests as you make changes.
150 | function watch() {
151 | gulp.watch(watchFiles, ['test'])
152 | }
153 |
154 | function testBrowser() {
155 | // Our testing bundle is made up of our unit tests, which
156 | // should individually load up pieces of our application.
157 | // We also include the browser setup file.
158 | const testFiles = glob.sync('./test/unit/**/*.js')
159 | const allFiles = ['./test/setup/browser.js'].concat(testFiles)
160 |
161 | // Lets us differentiate between the first build and subsequent builds
162 | var firstBuild = true
163 |
164 | // This empty stream might seem like a hack, but we need to specify all of our files through
165 | // the `entry` option of webpack. Otherwise, it ignores whatever file(s) are placed in here.
166 | return gulp
167 | .src('')
168 | .pipe($.plumber())
169 | .pipe(
170 | webpackStream(
171 | {
172 | watch: true,
173 | entry: allFiles,
174 | output: {
175 | filename: '__spec-build.js',
176 | },
177 | // Externals isn't necessary here since these are for tests.
178 | module: {
179 | // This is what allows us to author in future JavaScript
180 | rules: [
181 | {
182 | test: /\.js$/,
183 | exclude: /node_modules/,
184 | use: [
185 | {
186 | loader: 'babel-loader',
187 | query: {
188 | babelrc: false,
189 | presets: [
190 | [
191 | 'es2015',
192 | {
193 | modules: false,
194 | },
195 | ],
196 | ],
197 | plugins: ['transform-object-rest-spread'],
198 | },
199 | },
200 | ],
201 | },
202 | {
203 | test: /\.json$/,
204 | use: [
205 | {
206 | loader: 'json-loader',
207 | },
208 | ],
209 | },
210 | ],
211 | },
212 | plugins: [
213 | // By default, webpack does `n=>n` compilation with entry files. This concatenates
214 | // them into a single chunk.
215 | new webpack.optimize.LimitChunkCountPlugin({
216 | maxChunks: 1,
217 | }),
218 | ],
219 | devtool: 'inline-source-map',
220 | },
221 | webpack,
222 | () => {
223 | if (firstBuild) {
224 | $.livereload.listen({
225 | port: 35729,
226 | host: 'localhost',
227 | start: true,
228 | })
229 | gulp.watch(watchFiles, ['lint'])
230 | } else {
231 | $.livereload.reload('./tmp/__spec-build.js')
232 | }
233 | firstBuild = false
234 | }
235 | )
236 | )
237 | .pipe(gulp.dest('./tmp'))
238 | }
239 |
240 | // Remove the built files
241 | gulp.task('clean', cleanDist)
242 |
243 | // Remove our temporary files
244 | gulp.task('clean-tmp', cleanTmp)
245 |
246 | // Lint our source code
247 | gulp.task('lint-src', lintSrc)
248 |
249 | // Lint our test code
250 | gulp.task('lint-test', lintTest)
251 |
252 | // Lint this file
253 | gulp.task('lint-gulpfile', lintGulpfile)
254 |
255 | // Lint everything
256 | gulp.task('lint', ['lint-src', 'lint-test', 'lint-gulpfile'])
257 |
258 | // Build two versions of the library
259 | gulp.task('build', ['lint', 'clean'], build)
260 |
261 | // Lint and run our tests
262 | gulp.task('test', ['lint'], test)
263 |
264 | // Set up coverage and run tests
265 | gulp.task('coverage', ['lint'], coverage)
266 |
267 | // Set up a livereload environment for our spec runner `test/runner.html`
268 | gulp.task('test-browser', ['lint', 'clean-tmp'], testBrowser)
269 |
270 | // Run the headless unit tests as you make changes.
271 | gulp.task('watch', watch)
272 |
273 | // An alias of test
274 | gulp.task('default', ['test'])
275 |
--------------------------------------------------------------------------------
/test/unit/extensible-duck.js:
--------------------------------------------------------------------------------
1 | import Duck, { constructLocalized } from '../../src/extensible-duck'
2 | import _ from 'lodash'
3 | import { createSelector } from 'reselect'
4 |
5 | describe('Duck', () => {
6 | describe('constructor', () => {
7 | it('transforms types in object with prefix', () => {
8 | expect(
9 | new Duck({
10 | namespace: 'app',
11 | store: 'users',
12 | types: ['FETCH'],
13 | }).types
14 | ).to.eql({ FETCH: 'app/users/FETCH' })
15 | })
16 | it('lets the creators reference the duck instance', () => {
17 | const duck = new Duck({
18 | types: ['FETCH'],
19 | creators: ({ types }) => ({
20 | get: id => ({ type: types.FETCH, id }),
21 | }),
22 | })
23 | expect(duck.creators.get(15)).to.eql({
24 | type: duck.types.FETCH,
25 | id: 15,
26 | })
27 | })
28 | it('lets the selectors compose themselves and reference the duck instance', () => {
29 | const duck = new Duck({
30 | initialState: {
31 | items: [
32 | { name: 'apple', value: 1.2 },
33 | { name: 'orange', value: 0.95 },
34 | ],
35 | },
36 | selectors: {
37 | items: state => state.items, // gets the items from complete state
38 | subTotal: new Duck.Selector(selectors => state =>
39 | // Get another derived state reusing previous items selector.
40 | // Can be composed multiple such states if using library like reselect.
41 | selectors
42 | .items(state)
43 | .reduce((computedTotal, item) => computedTotal + item.value, 0)
44 | ),
45 | },
46 | })
47 | expect(duck.selectors.items(duck.initialState)).to.eql([
48 | { name: 'apple', value: 1.2 },
49 | { name: 'orange', value: 0.95 },
50 | ])
51 | expect(duck.selectors.subTotal(duck.initialState)).to.eql(2.15)
52 | })
53 | it('generates the selector function once per selector', () => {
54 | let passes = 0
55 | const duck = new Duck({
56 | selectors: {
57 | myFunc: new Duck.Selector(selectors => {
58 | passes++
59 | return () => {}
60 | }),
61 | },
62 | })
63 | duck.selectors.myFunc()
64 | duck.selectors.myFunc()
65 | expect(passes).to.eql(1)
66 | })
67 | it('lets the selectors access the duck instance', () => {
68 | const planetsState = {
69 | planets: ['mercury', 'wenus', 'earth', 'mars'],
70 | }
71 | const duck = new Duck({
72 | store: 'box',
73 | initialState: {
74 | items: ['chocolate', 'muffin', 'candy'],
75 | },
76 | selectors: constructLocalized({
77 | countSweets: localState => localState.items.length,
78 | countPlanets: (localState, globalState) => globalState.planets.length,
79 | countObjects: new Duck.Selector(selectors =>
80 | createSelector(
81 | selectors.countSweets,
82 | selectors.countPlanets,
83 | (countSweets, countPlanets) => countSweets + countPlanets
84 | )
85 | ),
86 | }),
87 | })
88 | const store = {
89 | ...planetsState,
90 | [duck.store]: duck.initialState,
91 | }
92 | expect(duck.selectors.countSweets(store)).to.eql(3)
93 | expect(duck.selectors.countPlanets(store)).to.eql(4)
94 | expect(duck.selectors.countObjects(store)).to.eql(7)
95 | })
96 | it('can construct localized state of deep nested duck reference', () => {
97 | const planetsState = {
98 | planets: ['mercury', 'wenus', 'earth', 'mars'],
99 | }
100 | const duck = new Duck({
101 | store: 'box',
102 | storePath: 'foo.bar',
103 | initialState: {
104 | items: ['chocolate', 'muffin', 'candy'],
105 | },
106 | selectors: constructLocalized({
107 | countSweets: localState => localState.items.length,
108 | countPlanets: (localState, globalState) => globalState.planets.length,
109 | }),
110 | })
111 | const store = {
112 | ...planetsState,
113 | foo: {
114 | bar: {
115 | [duck.store]: duck.initialState,
116 | },
117 | },
118 | }
119 | expect(duck.selectors.countSweets(store)).to.eql(3)
120 | expect(duck.selectors.countPlanets(store)).to.eql(4)
121 | })
122 | it('works with reselect', () => {
123 | const duck = new Duck({
124 | selectors: {
125 | test1: state => state.test1,
126 | test2: new Duck.Selector(selectors =>
127 | createSelector(selectors.test1, test1 => test1)
128 | ),
129 | test3: new Duck.Selector(selectors =>
130 | createSelector(selectors.test2, test2 => test2)
131 | ),
132 | },
133 | })
134 | expect(duck.selectors.test3({ test1: 'it works' })).to.eql('it works')
135 | })
136 | it('lets the initialState reference the duck instance', () => {
137 | const duck = new Duck({
138 | consts: { statuses: ['NEW'] },
139 | initialState: ({ statuses }) => ({ status: statuses.NEW }),
140 | })
141 | expect(duck.initialState).to.eql({ status: 'NEW' })
142 | })
143 | it('accepts the initialState as an object', () => {
144 | const duck = new Duck({
145 | initialState: { obj: {} },
146 | })
147 | expect(duck.initialState).to.eql({ obj: {} })
148 | })
149 | it('creates the constant objects', () => {
150 | const duck = new Duck({
151 | consts: { statuses: ['READY', 'ERROR'] },
152 | })
153 | expect(duck.statuses).to.eql({ READY: 'READY', ERROR: 'ERROR' })
154 | })
155 | it('lets the creators access the selectors', () => {
156 | const duck = new Duck({
157 | selectors: {
158 | sum: numbers => numbers.reduce((sum, n) => sum + n, 0),
159 | },
160 | creators: ({ selectors }) => ({
161 | calculate: () => dispatch => {
162 | dispatch({ type: 'CALCULATE', payload: selectors.sum([1, 2, 3]) })
163 | },
164 | }),
165 | })
166 | const dispatch = sinon.spy()
167 | duck.creators.calculate()(dispatch)
168 | expect(dispatch).to.have.been.calledWith({
169 | type: 'CALCULATE',
170 | payload: 6,
171 | })
172 | })
173 | })
174 | describe('reducer', () => {
175 | it('lets the original reducer reference the duck instance', () => {
176 | const duck = new Duck({
177 | types: ['FETCH'],
178 | reducer: (state, action, { types }) => {
179 | switch (action.type) {
180 | case types.FETCH:
181 | return { worked: true }
182 | default:
183 | return state
184 | }
185 | },
186 | })
187 | expect(duck.reducer({}, { type: duck.types.FETCH })).to.eql({
188 | worked: true,
189 | })
190 | })
191 | it('passes the initialState to the original reducer when state is undefined', () => {
192 | const duck = new Duck({
193 | initialState: { obj: {} },
194 | reducer: (state, action) => {
195 | return state
196 | },
197 | })
198 | expect(duck.reducer(undefined, { type: duck.types.FETCH })).to.eql({
199 | obj: {},
200 | })
201 | })
202 | })
203 | describe('extend', () => {
204 | it('creates a new Duck', () => {
205 | expect(new Duck({}).extend({}).constructor.name).to.eql('Duck')
206 | })
207 | it('copies the attributes to the new Duck', () => {
208 | const duck = new Duck({ initialState: { obj: null } })
209 | expect(duck.extend({}).initialState).to.eql({ obj: null })
210 | })
211 | it('copies the original consts', () => {
212 | const duck = new Duck({ consts: { statuses: ['NEW'] } })
213 | expect(duck.extend({}).statuses).to.eql({ NEW: 'NEW' })
214 | })
215 | it('overrides the types', () => {
216 | const duck = new Duck({
217 | namespace: 'ns',
218 | store: 'x',
219 | types: ['FETCH'],
220 | })
221 | expect(duck.extend({ namespace: 'ns2', store: 'y' }).types).to.eql({
222 | FETCH: 'ns2/y/FETCH',
223 | })
224 | })
225 | it('merges the consts', () => {
226 | const duck = new Duck({ consts: { statuses: ['READY'] } })
227 | expect(
228 | duck.extend({ consts: { statuses: ['FAILED'] } }).statuses
229 | ).to.eql({
230 | READY: 'READY',
231 | FAILED: 'FAILED',
232 | })
233 | })
234 | it('merges the takes', () => {
235 | const duck = new Duck({ takes: () => ['first'] })
236 | expect(duck.extend({ takes: () => ['second'] }).takes).to.eql([
237 | 'first',
238 | 'second',
239 | ])
240 | })
241 | it('merges the sagas', () => {
242 | const duck = new Duck({
243 | sagas: () => ({
244 | first: function*() {
245 | yield 1
246 | },
247 | }),
248 | })
249 | const childDuck = duck.extend({
250 | sagas: () => ({
251 | second: function*() {
252 | yield 2
253 | },
254 | }),
255 | })
256 | expect(_.keys(childDuck.sagas)).to.eql(['first', 'second'])
257 | })
258 | it('appends new types', () => {
259 | expect(
260 | new Duck({}).extend({
261 | namespace: 'ns2',
262 | store: 'y',
263 | types: ['RESET'],
264 | }).types
265 | ).to.eql({ RESET: 'ns2/y/RESET' })
266 | })
267 | it('appends the new reducers', () => {
268 | const duck = new Duck({
269 | creators: () => ({
270 | get: () => ({ type: 'GET' }),
271 | }),
272 | })
273 | const childDuck = duck.extend({
274 | creators: () => ({
275 | delete: () => ({ type: 'DELETE' }),
276 | }),
277 | })
278 | expect(_.keys(childDuck.creators)).to.eql(['get', 'delete'])
279 | })
280 | it('lets the reducers access the parents', () => {
281 | const d1 = new Duck({
282 | creators: () => ({
283 | get: () => ({ d1: true }),
284 | }),
285 | })
286 | const d2 = d1.extend({
287 | creators: (duck, parent) => ({
288 | get: () => ({ ...parent.get(duck), d2: true }),
289 | }),
290 | })
291 | const d3 = d2.extend({
292 | creators: (duck, parent) => ({
293 | get: () => ({ ...parent.get(duck), d3: true }),
294 | }),
295 | })
296 | expect(d3.creators.get()).to.eql({ d1: true, d2: true, d3: true })
297 | })
298 | context('when a function is passed', () => {
299 | it('passes the duck instance as argument', () => {
300 | const duck = new Duck({ foo: 2 })
301 | const childDuck = duck.extend(parent => ({
302 | bar: parent.options.foo * 2,
303 | }))
304 | expect(childDuck.options.bar).to.eql(4)
305 | })
306 | })
307 | it('updates the old creators with the new properties', () => {
308 | const duck = new Duck({
309 | namespace: 'a',
310 | store: 'x',
311 | types: ['GET'],
312 | creators: ({ types }) => ({
313 | get: () => ({ type: types.GET }),
314 | }),
315 | })
316 | const childDuck = duck.extend({ namespace: 'b', store: 'y' })
317 | expect(childDuck.creators.get()).to.eql({ type: 'b/y/GET' })
318 | })
319 | it('updates the old selectors with the new properties', () => {
320 | const duck = new Duck({
321 | namespace: 'a',
322 | store: 'x',
323 | initialState: {
324 | items: [
325 | { name: 'apple', value: 1.2 },
326 | { name: 'orange', value: 0.95 },
327 | ],
328 | },
329 | selectors: {
330 | items: state => state.items, // gets the items from complete state
331 | },
332 | })
333 | const childDuck = duck.extend({
334 | namespace: 'b',
335 | store: 'y',
336 | selectors: {
337 | subTotal: new Duck.Selector(selectors => state =>
338 | // Get another derived state reusing previous items selector.
339 | // Can be composed multiple such states if using library like reselect.
340 | selectors
341 | .items(state)
342 | .reduce((computedTotal, item) => computedTotal + item.value, 0)
343 | ),
344 | },
345 | })
346 | expect(childDuck.selectors.items(duck.initialState)).to.eql([
347 | { name: 'apple', value: 1.2 },
348 | { name: 'orange', value: 0.95 },
349 | ])
350 | expect(childDuck.selectors.subTotal(duck.initialState)).to.eql(2.15)
351 | })
352 | it('adds the new reducer keeping the old ones', () => {
353 | const parentDuck = new Duck({
354 | reducer: (state, action) => {
355 | switch (action.type) {
356 | case 'FETCH':
357 | return { ...state, parentDuck: true }
358 | default:
359 | return state
360 | }
361 | },
362 | })
363 | const duck = parentDuck.extend({
364 | reducer: (state, action) => {
365 | switch (action.type) {
366 | case 'FETCH':
367 | return { ...state, duck: true }
368 | default:
369 | return state
370 | }
371 | },
372 | })
373 | expect(duck.reducer({}, { type: 'FETCH' })).to.eql({
374 | parentDuck: true,
375 | duck: true,
376 | })
377 | })
378 | it('does not affect the original duck', () => {
379 | const parentDuck = new Duck({
380 | reducer: (state, action) => {
381 | switch (action.type) {
382 | case 'FETCH':
383 | return { ...state, parentDuck: true }
384 | default:
385 | return state
386 | }
387 | },
388 | })
389 | const duck = parentDuck.extend({
390 | reducer: (state, action) => {
391 | switch (action.type) {
392 | case 'FETCH':
393 | return { ...state, duck: true }
394 | default:
395 | return state
396 | }
397 | },
398 | })
399 | expect(parentDuck.reducer({}, { type: 'FETCH' })).to.eql({
400 | parentDuck: true,
401 | })
402 | })
403 | it('passes the parent initialState to the child', () => {
404 | const parentDuck = new Duck({ initialState: { parent: true } })
405 | const duck = parentDuck.extend({
406 | initialState: (duck, parentState) => {
407 | return { ...parentState, child: true }
408 | },
409 | })
410 | expect(duck.initialState).to.eql({ parent: true, child: true })
411 | })
412 | })
413 | })
414 |
--------------------------------------------------------------------------------
/dist/extensible-duck.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory();
4 | else if(typeof define === 'function' && define.amd)
5 | define([], factory);
6 | else if(typeof exports === 'object')
7 | exports["Duck"] = factory();
8 | else
9 | root["Duck"] = factory();
10 | })(this, function() {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 | /******/
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 | /******/
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId]) {
20 | /******/ return installedModules[moduleId].exports;
21 | /******/ }
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ i: moduleId,
25 | /******/ l: false,
26 | /******/ exports: {}
27 | /******/ };
28 | /******/
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 | /******/
32 | /******/ // Flag the module as loaded
33 | /******/ module.l = true;
34 | /******/
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 | /******/
39 | /******/
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 | /******/
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 | /******/
46 | /******/ // define getter function for harmony exports
47 | /******/ __webpack_require__.d = function(exports, name, getter) {
48 | /******/ if(!__webpack_require__.o(exports, name)) {
49 | /******/ Object.defineProperty(exports, name, {
50 | /******/ configurable: false,
51 | /******/ enumerable: true,
52 | /******/ get: getter
53 | /******/ });
54 | /******/ }
55 | /******/ };
56 | /******/
57 | /******/ // getDefaultExport function for compatibility with non-harmony modules
58 | /******/ __webpack_require__.n = function(module) {
59 | /******/ var getter = module && module.__esModule ?
60 | /******/ function getDefault() { return module['default']; } :
61 | /******/ function getModuleExports() { return module; };
62 | /******/ __webpack_require__.d(getter, 'a', getter);
63 | /******/ return getter;
64 | /******/ };
65 | /******/
66 | /******/ // Object.prototype.hasOwnProperty.call
67 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
68 | /******/
69 | /******/ // __webpack_public_path__
70 | /******/ __webpack_require__.p = "";
71 | /******/
72 | /******/ // Load entry module and return exports
73 | /******/ return __webpack_require__(__webpack_require__.s = 0);
74 | /******/ })
75 | /************************************************************************/
76 | /******/ ([
77 | /* 0 */
78 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
79 |
80 | "use strict";
81 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
82 | /* harmony export (immutable) */ __webpack_exports__["constructLocalized"] = constructLocalized;
83 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "constructLocalised", function() { return constructLocalized; });
84 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Selector", function() { return Selector; });
85 | 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; }; }();
86 |
87 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
88 |
89 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
90 |
91 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
92 |
93 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
94 |
95 | function typeValue(namespace, store, type) {
96 | return namespace + '/' + store + '/' + type;
97 | }
98 |
99 | function zipObject(keys, values) {
100 | if (arguments.length == 1) {
101 | values = keys[1];
102 | keys = keys[0];
103 | }
104 |
105 | var result = {};
106 | var i = 0;
107 |
108 | for (i; i < keys.length; i += 1) {
109 | result[keys[i]] = values[i];
110 | }
111 |
112 | return result;
113 | }
114 |
115 | function buildTypes(namespace, store, types) {
116 | return zipObject(types, types.map(function (type) {
117 | return typeValue(namespace, store, type);
118 | }));
119 | }
120 |
121 | function isObject(obj) {
122 | return obj !== null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object';
123 | }
124 |
125 | function isFunction(func) {
126 | return func !== null && typeof func === 'function';
127 | }
128 |
129 | function isUndefined(value) {
130 | return typeof value === 'undefined' || value === undefined;
131 | }
132 |
133 | function isPlainObject(obj) {
134 | return isObject(obj) && (obj.constructor === Object || // obj = {}
135 | obj.constructor === undefined) // obj = Object.create(null)
136 | ;
137 | }
138 |
139 | function mergeDeep(target) {
140 | for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
141 | sources[_key - 1] = arguments[_key];
142 | }
143 |
144 | if (!sources.length) return target;
145 | var source = sources.shift();
146 |
147 | if (Array.isArray(target)) {
148 | if (Array.isArray(source)) {
149 | var _target;
150 |
151 | (_target = target).push.apply(_target, _toConsumableArray(source));
152 | } else {
153 | target.push(source);
154 | }
155 | } else if (isPlainObject(target)) {
156 | if (isPlainObject(source)) {
157 | var _iteratorNormalCompletion = true;
158 | var _didIteratorError = false;
159 | var _iteratorError = undefined;
160 |
161 | try {
162 | for (var _iterator = Object.keys(source)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
163 | var key = _step.value;
164 |
165 | if (!target[key]) {
166 | target[key] = source[key];
167 | } else {
168 | mergeDeep(target[key], source[key]);
169 | }
170 | }
171 | } catch (err) {
172 | _didIteratorError = true;
173 | _iteratorError = err;
174 | } finally {
175 | try {
176 | if (!_iteratorNormalCompletion && _iterator.return) {
177 | _iterator.return();
178 | }
179 | } finally {
180 | if (_didIteratorError) {
181 | throw _iteratorError;
182 | }
183 | }
184 | }
185 | } else {
186 | throw new Error('Cannot merge object with non-object');
187 | }
188 | } else {
189 | target = source;
190 | }
191 |
192 | return mergeDeep.apply(undefined, [target].concat(sources));
193 | }
194 |
195 | function assignDefaults(options) {
196 | return _extends({}, options, {
197 | consts: options.consts || {},
198 | sagas: options.sagas || function () {
199 | return {};
200 | },
201 | takes: options.takes || function () {
202 | return [];
203 | },
204 | creators: options.creators || function () {
205 | return {};
206 | },
207 | selectors: options.selectors || {},
208 | types: options.types || []
209 | });
210 | }
211 |
212 | function injectDuck(input, duck) {
213 | if (input instanceof Function) {
214 | return input(duck);
215 | } else {
216 | return input;
217 | }
218 | }
219 |
220 | function getLocalizedState(globalState, duck) {
221 | var localizedState = void 0;
222 |
223 | if (duck.storePath) {
224 | var segments = [].concat(duck.storePath.split('.'), duck.store);
225 | localizedState = segments.reduce(function getSegment(acc, segment) {
226 | if (!acc[segment]) {
227 | throw Error('state does not contain reducer at storePath ' + segments.join('.'));
228 | }
229 | return acc[segment];
230 | }, globalState);
231 | } else {
232 | localizedState = globalState[duck.store];
233 | }
234 |
235 | return localizedState;
236 | }
237 |
238 | function constructLocalized(selectors) {
239 | var derivedSelectors = deriveSelectors(selectors);
240 | return function (duck) {
241 | var localizedSelectors = {};
242 | Object.keys(derivedSelectors).forEach(function (key) {
243 | var selector = derivedSelectors[key];
244 | localizedSelectors[key] = function (globalState) {
245 | return selector(getLocalizedState(globalState, duck), globalState);
246 | };
247 | });
248 | return localizedSelectors;
249 | };
250 | }
251 |
252 | // An alias for those who do not use the above spelling.
253 |
254 |
255 | /**
256 | * Helper utility to assist in composing the selectors.
257 | * Previously defined selectors can be used to derive future selectors.
258 | *
259 | * @param {object} selectors
260 | * @returns
261 | */
262 | function deriveSelectors(selectors) {
263 | var composedSelectors = {};
264 | Object.keys(selectors).forEach(function (key) {
265 | var selector = selectors[key];
266 | if (selector instanceof Selector) {
267 | composedSelectors[key] = function () {
268 | return (composedSelectors[key] = selector.extractFunction(composedSelectors)).apply(undefined, arguments);
269 | };
270 | } else {
271 | composedSelectors[key] = selector;
272 | }
273 | });
274 | return composedSelectors;
275 | }
276 |
277 | var Duck = function () {
278 | function Duck(options) {
279 | var _this = this;
280 |
281 | _classCallCheck(this, Duck);
282 |
283 | options = assignDefaults(options);
284 | var _options = options,
285 | namespace = _options.namespace,
286 | store = _options.store,
287 | storePath = _options.storePath,
288 | types = _options.types,
289 | consts = _options.consts,
290 | initialState = _options.initialState,
291 | creators = _options.creators,
292 | selectors = _options.selectors,
293 | sagas = _options.sagas,
294 | takes = _options.takes;
295 |
296 | this.options = options;
297 | Object.keys(consts).forEach(function (name) {
298 | _this[name] = zipObject(consts[name], consts[name]);
299 | });
300 |
301 | this.store = store;
302 | this.storePath = storePath;
303 | this.types = buildTypes(namespace, store, types);
304 | this.initialState = isFunction(initialState) ? initialState(this) : initialState;
305 | this.reducer = this.reducer.bind(this);
306 | this.selectors = deriveSelectors(injectDuck(selectors, this));
307 | this.creators = creators(this);
308 | this.sagas = sagas(this);
309 | this.takes = takes(this);
310 | }
311 |
312 | _createClass(Duck, [{
313 | key: 'reducer',
314 | value: function reducer(state, action) {
315 | if (isUndefined(state)) {
316 | state = this.initialState;
317 | }
318 | return this.options.reducer(state, action, this);
319 | }
320 | }, {
321 | key: 'extend',
322 | value: function extend(options) {
323 | var _this2 = this;
324 |
325 | if (isFunction(options)) {
326 | options = options(this);
327 | }
328 | options = assignDefaults(options);
329 | var parent = this.options;
330 | var initialState = void 0;
331 | if (isFunction(options.initialState)) {
332 | initialState = function initialState(duck) {
333 | return options.initialState(duck, _this2.initialState);
334 | };
335 | } else if (isUndefined(options.initialState)) {
336 | initialState = parent.initialState;
337 | } else {
338 | initialState = options.initialState;
339 | }
340 | return new Duck(_extends({}, parent, options, {
341 | initialState: initialState,
342 | consts: mergeDeep({}, parent.consts, options.consts),
343 | sagas: function sagas(duck) {
344 | var parentSagas = parent.sagas(duck);
345 | return _extends({}, parentSagas, options.sagas(duck, parentSagas));
346 | },
347 | takes: function takes(duck) {
348 | var parentTakes = parent.takes(duck);
349 | return [].concat(_toConsumableArray(parentTakes), _toConsumableArray(options.takes(duck, parentTakes)));
350 | },
351 | creators: function creators(duck) {
352 | var parentCreators = parent.creators(duck);
353 | return _extends({}, parentCreators, options.creators(duck, parentCreators));
354 | },
355 | selectors: function selectors(duck) {
356 | return _extends({}, injectDuck(parent.selectors, duck), injectDuck(options.selectors, duck));
357 | },
358 | types: [].concat(_toConsumableArray(parent.types), _toConsumableArray(options.types)),
359 | reducer: function reducer(state, action, duck) {
360 | state = parent.reducer(state, action, duck);
361 | if (isUndefined(options.reducer)) {
362 | return state;
363 | } else {
364 | return options.reducer(state, action, duck);
365 | }
366 | }
367 | }));
368 | }
369 | }]);
370 |
371 | return Duck;
372 | }();
373 |
374 | /* harmony default export */ __webpack_exports__["default"] = (Duck);
375 |
376 |
377 | var Selector = function () {
378 | function Selector(func) {
379 | _classCallCheck(this, Selector);
380 |
381 | this.func = func;
382 | }
383 |
384 | _createClass(Selector, [{
385 | key: 'extractFunction',
386 | value: function extractFunction(selectors) {
387 | return this.func(selectors);
388 | }
389 | }]);
390 |
391 | return Selector;
392 | }();
393 |
394 | Duck.Selector = Selector;
395 |
396 | /***/ })
397 | /******/ ]);
398 | });
399 | //# sourceMappingURL=extensible-duck.js.map
--------------------------------------------------------------------------------
/dist/extensible-duck.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///webpack/bootstrap 880a1df6fb2495bb3587","webpack:///./src/extensible-duck.js"],"names":["typeValue","namespace","store","type","zipObject","keys","values","arguments","length","result","i","buildTypes","types","map","isObject","obj","isFunction","func","isUndefined","value","undefined","isPlainObject","constructor","Object","mergeDeep","target","sources","source","shift","Array","isArray","push","key","Error","assignDefaults","options","consts","sagas","takes","creators","selectors","injectDuck","input","duck","Function","getLocalizedState","globalState","localizedState","storePath","segments","concat","split","reduce","getSegment","acc","segment","join","constructLocalized","derivedSelectors","deriveSelectors","localizedSelectors","forEach","selector","composedSelectors","Selector","extractFunction","Duck","initialState","name","reducer","bind","state","action","parent","parentSagas","parentTakes","parentCreators"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;ACVA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;;;;;;;;;;;;;;;AC7DA,SAASA,SAAT,CAAmBC,SAAnB,EAA8BC,KAA9B,EAAqCC,IAArC,EAA2C;AACzC,SAAUF,SAAV,SAAuBC,KAAvB,SAAgCC,IAAhC;AACD;;AAED,SAASC,SAAT,CAAmBC,IAAnB,EAAyBC,MAAzB,EAAiC;AAC/B,MAAIC,UAAUC,MAAV,IAAoB,CAAxB,EAA2B;AACzBF,aAASD,KAAK,CAAL,CAAT;AACAA,WAAOA,KAAK,CAAL,CAAP;AACD;;AAED,MAAII,SAAS,EAAb;AACA,MAAIC,IAAI,CAAR;;AAEA,OAAKA,CAAL,EAAQA,IAAIL,KAAKG,MAAjB,EAAyBE,KAAK,CAA9B,EAAiC;AAC/BD,WAAOJ,KAAKK,CAAL,CAAP,IAAkBJ,OAAOI,CAAP,CAAlB;AACD;;AAED,SAAOD,MAAP;AACD;;AAED,SAASE,UAAT,CAAoBV,SAApB,EAA+BC,KAA/B,EAAsCU,KAAtC,EAA6C;AAC3C,SAAOR,UAAUQ,KAAV,EAAiBA,MAAMC,GAAN,CAAU;AAAA,WAAQb,UAAUC,SAAV,EAAqBC,KAArB,EAA4BC,IAA5B,CAAR;AAAA,GAAV,CAAjB,CAAP;AACD;;AAED,SAASW,QAAT,CAAkBC,GAAlB,EAAuB;AACrB,SAAOA,QAAQ,IAAR,IAAgB,QAAOA,GAAP,yCAAOA,GAAP,OAAe,QAAtC;AACD;;AAED,SAASC,UAAT,CAAoBC,IAApB,EAA0B;AACxB,SAAOA,SAAS,IAAT,IAAiB,OAAOA,IAAP,KAAgB,UAAxC;AACD;;AAED,SAASC,WAAT,CAAqBC,KAArB,EAA4B;AAC1B,SAAO,OAAOA,KAAP,KAAiB,WAAjB,IAAgCA,UAAUC,SAAjD;AACD;;AAED,SAASC,aAAT,CAAuBN,GAAvB,EAA4B;AAC1B,SACED,SAASC,GAAT,MACCA,IAAIO,WAAJ,KAAoBC,MAApB,IAA8B;AAC7BR,MAAIO,WAAJ,KAAoBF,SAFtB,CADF,CAGmC;AAHnC;AAKD;;AAED,SAASI,SAAT,CAAmBC,MAAnB,EAAuC;AAAA,oCAATC,OAAS;AAATA,WAAS;AAAA;;AACrC,MAAI,CAACA,QAAQlB,MAAb,EAAqB,OAAOiB,MAAP;AACrB,MAAME,SAASD,QAAQE,KAAR,EAAf;;AAEA,MAAIC,MAAMC,OAAN,CAAcL,MAAd,CAAJ,EAA2B;AACzB,QAAII,MAAMC,OAAN,CAAcH,MAAd,CAAJ,EAA2B;AAAA;;AACzB,yBAAOI,IAAP,mCAAeJ,MAAf;AACD,KAFD,MAEO;AACLF,aAAOM,IAAP,CAAYJ,MAAZ;AACD;AACF,GAND,MAMO,IAAIN,cAAcI,MAAd,CAAJ,EAA2B;AAChC,QAAIJ,cAAcM,MAAd,CAAJ,EAA2B;AAAA;AAAA;AAAA;;AAAA;AACzB,6BAAgBJ,OAAOlB,IAAP,CAAYsB,MAAZ,CAAhB,8HAAqC;AAAA,cAA5BK,GAA4B;;AACnC,cAAI,CAACP,OAAOO,GAAP,CAAL,EAAkB;AAChBP,mBAAOO,GAAP,IAAcL,OAAOK,GAAP,CAAd;AACD,WAFD,MAEO;AACLR,sBAAUC,OAAOO,GAAP,CAAV,EAAuBL,OAAOK,GAAP,CAAvB;AACD;AACF;AAPwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ1B,KARD,MAQO;AACL,YAAM,IAAIC,KAAJ,uCAAN;AACD;AACF,GAZM,MAYA;AACLR,aAASE,MAAT;AACD;;AAED,SAAOH,4BAAUC,MAAV,SAAqBC,OAArB,EAAP;AACD;;AAED,SAASQ,cAAT,CAAwBC,OAAxB,EAAiC;AAC/B,sBACKA,OADL;AAEEC,YAAQD,QAAQC,MAAR,IAAkB,EAF5B;AAGEC,WAAOF,QAAQE,KAAR,IAAkB;AAAA,aAAO,EAAP;AAAA,KAH3B;AAIEC,WAAOH,QAAQG,KAAR,IAAkB;AAAA,aAAM,EAAN;AAAA,KAJ3B;AAKEC,cAAUJ,QAAQI,QAAR,IAAqB;AAAA,aAAO,EAAP;AAAA,KALjC;AAMEC,eAAWL,QAAQK,SAAR,IAAqB,EANlC;AAOE5B,WAAOuB,QAAQvB,KAAR,IAAiB;AAP1B;AASD;;AAED,SAAS6B,UAAT,CAAoBC,KAApB,EAA2BC,IAA3B,EAAiC;AAC/B,MAAID,iBAAiBE,QAArB,EAA+B;AAC7B,WAAOF,MAAMC,IAAN,CAAP;AACD,GAFD,MAEO;AACL,WAAOD,KAAP;AACD;AACF;;AAED,SAASG,iBAAT,CAA2BC,WAA3B,EAAwCH,IAAxC,EAA8C;AAC5C,MAAII,uBAAJ;;AAEA,MAAIJ,KAAKK,SAAT,EAAoB;AAClB,QAAMC,WAAW,GAAGC,MAAH,CAAUP,KAAKK,SAAL,CAAeG,KAAf,CAAqB,GAArB,CAAV,EAAqCR,KAAKzC,KAA1C,CAAjB;AACA6C,qBAAiBE,SAASG,MAAT,CAAgB,SAASC,UAAT,CAAoBC,GAApB,EAAyBC,OAAzB,EAAkC;AACjE,UAAI,CAACD,IAAIC,OAAJ,CAAL,EAAmB;AACjB,cAAMtB,uDAC2CgB,SAASO,IAAT,CAAc,GAAd,CAD3C,CAAN;AAGD;AACD,aAAOF,IAAIC,OAAJ,CAAP;AACD,KAPgB,EAOdT,WAPc,CAAjB;AAQD,GAVD,MAUO;AACLC,qBAAiBD,YAAYH,KAAKzC,KAAjB,CAAjB;AACD;;AAED,SAAO6C,cAAP;AACD;;AAEM,SAASU,kBAAT,CAA4BjB,SAA5B,EAAuC;AAC5C,MAAMkB,mBAAmBC,gBAAgBnB,SAAhB,CAAzB;AACA,SAAO,gBAAQ;AACb,QAAMoB,qBAAqB,EAA3B;AACArC,WAAOlB,IAAP,CAAYqD,gBAAZ,EAA8BG,OAA9B,CAAsC,eAAO;AAC3C,UAAMC,WAAWJ,iBAAiB1B,GAAjB,CAAjB;AACA4B,yBAAmB5B,GAAnB,IAA0B;AAAA,eACxB8B,SAASjB,kBAAkBC,WAAlB,EAA+BH,IAA/B,CAAT,EAA+CG,WAA/C,CADwB;AAAA,OAA1B;AAED,KAJD;AAKA,WAAOc,kBAAP;AACD,GARD;AASD;;AAED;AACA;;AAEA;;;;;;;AAOA,SAASD,eAAT,CAAyBnB,SAAzB,EAAoC;AAClC,MAAMuB,oBAAoB,EAA1B;AACAxC,SAAOlB,IAAP,CAAYmC,SAAZ,EAAuBqB,OAAvB,CAA+B,eAAO;AACpC,QAAMC,WAAWtB,UAAUR,GAAV,CAAjB;AACA,QAAI8B,oBAAoBE,QAAxB,EAAkC;AAChCD,wBAAkB/B,GAAlB,IAAyB;AAAA,eACvB,CAAC+B,kBAAkB/B,GAAlB,IAAyB8B,SAASG,eAAT,CAAyBF,iBAAzB,CAA1B,6BADuB;AAAA,OAAzB;AAID,KALD,MAKO;AACLA,wBAAkB/B,GAAlB,IAAyB8B,QAAzB;AACD;AACF,GAVD;AAWA,SAAOC,iBAAP;AACD;;IAEoBG,I;AACnB,gBAAY/B,OAAZ,EAAqB;AAAA;;AAAA;;AACnBA,cAAUD,eAAeC,OAAf,CAAV;AADmB,mBAafA,OAbe;AAAA,QAGjBlC,SAHiB,YAGjBA,SAHiB;AAAA,QAIjBC,KAJiB,YAIjBA,KAJiB;AAAA,QAKjB8C,SALiB,YAKjBA,SALiB;AAAA,QAMjBpC,KANiB,YAMjBA,KANiB;AAAA,QAOjBwB,MAPiB,YAOjBA,MAPiB;AAAA,QAQjB+B,YARiB,YAQjBA,YARiB;AAAA,QASjB5B,QATiB,YASjBA,QATiB;AAAA,QAUjBC,SAViB,YAUjBA,SAViB;AAAA,QAWjBH,KAXiB,YAWjBA,KAXiB;AAAA,QAYjBC,KAZiB,YAYjBA,KAZiB;;AAcnB,SAAKH,OAAL,GAAeA,OAAf;AACAZ,WAAOlB,IAAP,CAAY+B,MAAZ,EAAoByB,OAApB,CAA4B,gBAAQ;AAClC,YAAKO,IAAL,IAAahE,UAAUgC,OAAOgC,IAAP,CAAV,EAAwBhC,OAAOgC,IAAP,CAAxB,CAAb;AACD,KAFD;;AAIA,SAAKlE,KAAL,GAAaA,KAAb;AACA,SAAK8C,SAAL,GAAiBA,SAAjB;AACA,SAAKpC,KAAL,GAAaD,WAAWV,SAAX,EAAsBC,KAAtB,EAA6BU,KAA7B,CAAb;AACA,SAAKuD,YAAL,GAAoBnD,WAAWmD,YAAX,IAChBA,aAAa,IAAb,CADgB,GAEhBA,YAFJ;AAGA,SAAKE,OAAL,GAAe,KAAKA,OAAL,CAAaC,IAAb,CAAkB,IAAlB,CAAf;AACA,SAAK9B,SAAL,GAAiBmB,gBAAgBlB,WAAWD,SAAX,EAAsB,IAAtB,CAAhB,CAAjB;AACA,SAAKD,QAAL,GAAgBA,SAAS,IAAT,CAAhB;AACA,SAAKF,KAAL,GAAaA,MAAM,IAAN,CAAb;AACA,SAAKC,KAAL,GAAaA,MAAM,IAAN,CAAb;AACD;;;;4BACOiC,K,EAAOC,M,EAAQ;AACrB,UAAItD,YAAYqD,KAAZ,CAAJ,EAAwB;AACtBA,gBAAQ,KAAKJ,YAAb;AACD;AACD,aAAO,KAAKhC,OAAL,CAAakC,OAAb,CAAqBE,KAArB,EAA4BC,MAA5B,EAAoC,IAApC,CAAP;AACD;;;2BACMrC,O,EAAS;AAAA;;AACd,UAAInB,WAAWmB,OAAX,CAAJ,EAAyB;AACvBA,kBAAUA,QAAQ,IAAR,CAAV;AACD;AACDA,gBAAUD,eAAeC,OAAf,CAAV;AACA,UAAMsC,SAAS,KAAKtC,OAApB;AACA,UAAIgC,qBAAJ;AACA,UAAInD,WAAWmB,QAAQgC,YAAnB,CAAJ,EAAsC;AACpCA,uBAAe;AAAA,iBAAQhC,QAAQgC,YAAR,CAAqBxB,IAArB,EAA2B,OAAKwB,YAAhC,CAAR;AAAA,SAAf;AACD,OAFD,MAEO,IAAIjD,YAAYiB,QAAQgC,YAApB,CAAJ,EAAuC;AAC5CA,uBAAeM,OAAON,YAAtB;AACD,OAFM,MAEA;AACLA,uBAAehC,QAAQgC,YAAvB;AACD;AACD,aAAO,IAAID,IAAJ,cACFO,MADE,EAEFtC,OAFE;AAGLgC,kCAHK;AAIL/B,gBAAQZ,UAAU,EAAV,EAAciD,OAAOrC,MAArB,EAA6BD,QAAQC,MAArC,CAJH;AAKLC,eAAO,qBAAQ;AACb,cAAMqC,cAAcD,OAAOpC,KAAP,CAAaM,IAAb,CAApB;AACA,8BAAY+B,WAAZ,EAA4BvC,QAAQE,KAAR,CAAcM,IAAd,EAAoB+B,WAApB,CAA5B;AACD,SARI;AASLpC,eAAO,qBAAQ;AACb,cAAMqC,cAAcF,OAAOnC,KAAP,CAAaK,IAAb,CAApB;AACA,8CAAWgC,WAAX,sBAA2BxC,QAAQG,KAAR,CAAcK,IAAd,EAAoBgC,WAApB,CAA3B;AACD,SAZI;AAaLpC,kBAAU,wBAAQ;AAChB,cAAMqC,iBAAiBH,OAAOlC,QAAP,CAAgBI,IAAhB,CAAvB;AACA,8BAAYiC,cAAZ,EAA+BzC,QAAQI,QAAR,CAAiBI,IAAjB,EAAuBiC,cAAvB,CAA/B;AACD,SAhBI;AAiBLpC,mBAAW;AAAA,8BACNC,WAAWgC,OAAOjC,SAAlB,EAA6BG,IAA7B,CADM,EAENF,WAAWN,QAAQK,SAAnB,EAA8BG,IAA9B,CAFM;AAAA,SAjBN;AAqBL/B,4CAAW6D,OAAO7D,KAAlB,sBAA4BuB,QAAQvB,KAApC,EArBK;AAsBLyD,iBAAS,iBAACE,KAAD,EAAQC,MAAR,EAAgB7B,IAAhB,EAAyB;AAChC4B,kBAAQE,OAAOJ,OAAP,CAAeE,KAAf,EAAsBC,MAAtB,EAA8B7B,IAA9B,CAAR;AACA,cAAIzB,YAAYiB,QAAQkC,OAApB,CAAJ,EAAkC;AAChC,mBAAOE,KAAP;AACD,WAFD,MAEO;AACL,mBAAOpC,QAAQkC,OAAR,CAAgBE,KAAhB,EAAuBC,MAAvB,EAA+B7B,IAA/B,CAAP;AACD;AACF;AA7BI,SAAP;AA+BD;;;;;;AAnFkBuB,mE;;;AAsFd,IAAMF,QAAb;AACE,oBAAY/C,IAAZ,EAAkB;AAAA;;AAChB,SAAKA,IAAL,GAAYA,IAAZ;AACD;;AAHH;AAAA;AAAA,oCAKkBuB,SALlB,EAK6B;AACzB,aAAO,KAAKvB,IAAL,CAAUuB,SAAV,CAAP;AACD;AAPH;;AAAA;AAAA;;AAUA0B,KAAKF,QAAL,GAAgBA,QAAhB,C","file":"extensible-duck.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"Duck\"] = factory();\n\telse\n\t\troot[\"Duck\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 880a1df6fb2495bb3587","function typeValue(namespace, store, type) {\n return `${namespace}/${store}/${type}`\n}\n\nfunction zipObject(keys, values) {\n if (arguments.length == 1) {\n values = keys[1]\n keys = keys[0]\n }\n\n var result = {}\n var i = 0\n\n for (i; i < keys.length; i += 1) {\n result[keys[i]] = values[i]\n }\n\n return result\n}\n\nfunction buildTypes(namespace, store, types) {\n return zipObject(types, types.map(type => typeValue(namespace, store, type)))\n}\n\nfunction isObject(obj) {\n return obj !== null && typeof obj === 'object'\n}\n\nfunction isFunction(func) {\n return func !== null && typeof func === 'function'\n}\n\nfunction isUndefined(value) {\n return typeof value === 'undefined' || value === undefined\n}\n\nfunction isPlainObject(obj) {\n return (\n isObject(obj) &&\n (obj.constructor === Object || // obj = {}\n obj.constructor === undefined) // obj = Object.create(null)\n )\n}\n\nfunction mergeDeep(target, ...sources) {\n if (!sources.length) return target\n const source = sources.shift()\n\n if (Array.isArray(target)) {\n if (Array.isArray(source)) {\n target.push(...source)\n } else {\n target.push(source)\n }\n } else if (isPlainObject(target)) {\n if (isPlainObject(source)) {\n for (let key of Object.keys(source)) {\n if (!target[key]) {\n target[key] = source[key]\n } else {\n mergeDeep(target[key], source[key])\n }\n }\n } else {\n throw new Error(`Cannot merge object with non-object`)\n }\n } else {\n target = source\n }\n\n return mergeDeep(target, ...sources)\n}\n\nfunction assignDefaults(options) {\n return {\n ...options,\n consts: options.consts || {},\n sagas: options.sagas || (() => ({})),\n takes: options.takes || (() => []),\n creators: options.creators || (() => ({})),\n selectors: options.selectors || {},\n types: options.types || [],\n }\n}\n\nfunction injectDuck(input, duck) {\n if (input instanceof Function) {\n return input(duck)\n } else {\n return input\n }\n}\n\nfunction getLocalizedState(globalState, duck) {\n let localizedState\n\n if (duck.storePath) {\n const segments = [].concat(duck.storePath.split('.'), duck.store)\n localizedState = segments.reduce(function getSegment(acc, segment) {\n if (!acc[segment]) {\n throw Error(\n `state does not contain reducer at storePath ${segments.join('.')}`\n )\n }\n return acc[segment]\n }, globalState)\n } else {\n localizedState = globalState[duck.store]\n }\n\n return localizedState\n}\n\nexport function constructLocalized(selectors) {\n const derivedSelectors = deriveSelectors(selectors)\n return duck => {\n const localizedSelectors = {}\n Object.keys(derivedSelectors).forEach(key => {\n const selector = derivedSelectors[key]\n localizedSelectors[key] = globalState =>\n selector(getLocalizedState(globalState, duck), globalState)\n })\n return localizedSelectors\n }\n}\n\n// An alias for those who do not use the above spelling.\nexport { constructLocalized as constructLocalised }\n\n/**\n * Helper utility to assist in composing the selectors.\n * Previously defined selectors can be used to derive future selectors.\n *\n * @param {object} selectors\n * @returns\n */\nfunction deriveSelectors(selectors) {\n const composedSelectors = {}\n Object.keys(selectors).forEach(key => {\n const selector = selectors[key]\n if (selector instanceof Selector) {\n composedSelectors[key] = (...args) =>\n (composedSelectors[key] = selector.extractFunction(composedSelectors))(\n ...args\n )\n } else {\n composedSelectors[key] = selector\n }\n })\n return composedSelectors\n}\n\nexport default class Duck {\n constructor(options) {\n options = assignDefaults(options)\n const {\n namespace,\n store,\n storePath,\n types,\n consts,\n initialState,\n creators,\n selectors,\n sagas,\n takes,\n } = options\n this.options = options\n Object.keys(consts).forEach(name => {\n this[name] = zipObject(consts[name], consts[name])\n })\n\n this.store = store\n this.storePath = storePath\n this.types = buildTypes(namespace, store, types)\n this.initialState = isFunction(initialState)\n ? initialState(this)\n : initialState\n this.reducer = this.reducer.bind(this)\n this.selectors = deriveSelectors(injectDuck(selectors, this))\n this.creators = creators(this)\n this.sagas = sagas(this)\n this.takes = takes(this)\n }\n reducer(state, action) {\n if (isUndefined(state)) {\n state = this.initialState\n }\n return this.options.reducer(state, action, this)\n }\n extend(options) {\n if (isFunction(options)) {\n options = options(this)\n }\n options = assignDefaults(options)\n const parent = this.options\n let initialState\n if (isFunction(options.initialState)) {\n initialState = duck => options.initialState(duck, this.initialState)\n } else if (isUndefined(options.initialState)) {\n initialState = parent.initialState\n } else {\n initialState = options.initialState\n }\n return new Duck({\n ...parent,\n ...options,\n initialState,\n consts: mergeDeep({}, parent.consts, options.consts),\n sagas: duck => {\n const parentSagas = parent.sagas(duck)\n return { ...parentSagas, ...options.sagas(duck, parentSagas) }\n },\n takes: duck => {\n const parentTakes = parent.takes(duck)\n return [...parentTakes, ...options.takes(duck, parentTakes)]\n },\n creators: duck => {\n const parentCreators = parent.creators(duck)\n return { ...parentCreators, ...options.creators(duck, parentCreators) }\n },\n selectors: duck => ({\n ...injectDuck(parent.selectors, duck),\n ...injectDuck(options.selectors, duck),\n }),\n types: [...parent.types, ...options.types],\n reducer: (state, action, duck) => {\n state = parent.reducer(state, action, duck)\n if (isUndefined(options.reducer)) {\n return state\n } else {\n return options.reducer(state, action, duck)\n }\n },\n })\n }\n}\n\nexport class Selector {\n constructor(func) {\n this.func = func\n }\n\n extractFunction(selectors) {\n return this.func(selectors)\n }\n}\n\nDuck.Selector = Selector\n\n\n\n// WEBPACK FOOTER //\n// ./src/extensible-duck.js"],"sourceRoot":""}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # extensible-duck
2 |
3 | extensible-duck is an implementation of the [Ducks proposal](https://github.com/erikras/ducks-modular-redux). With this library you can create reusable and extensible ducks.
4 |
5 | [](https://travis-ci.org/investtools/extensible-duck)
6 | [](https://codeclimate.com/github/investtools/extensible-duck)
7 | [](https://codeclimate.com/github/investtools/extensible-duck)
8 | [](https://david-dm.org/investtools/extensible-duck)
9 | [](https://david-dm.org/investtools/extensible-duck#info=devDependencies)
10 | 
11 |
12 |
13 |
14 | - [Basic Usage](#basic-usage)
15 | - [Constructor Arguments](#constructor-arguments)
16 | - [Duck Accessors](#duck-accessors)
17 | - [Defining the Reducer](#defining-the-reducer)
18 | - [Defining the Creators](#defining-the-creators)
19 | - [Defining the sagas](#defining-the-sagas)
20 | - [Defining the Initial State](#defining-the-initial-state)
21 | - [Defining the Selectors](#defining-the-selectors)
22 | - [Defining the Types](#defining-the-types)
23 | - [Defining the Constants](#defining-the-constants)
24 | - [Creating Reusable Ducks](#creating-reusable-ducks)
25 | - [Extending Ducks](#extending-ducks)
26 | - [Creating Reusable Duck Extensions](#creating-reusable-duck-extensions)
27 | - [Creating Ducks with selectors](#creating-ducks-with-selectors)
28 |
29 |
30 |
31 | ## Basic Usage
32 |
33 | ```js
34 | // widgetsDuck.js
35 |
36 | import Duck from 'extensible-duck'
37 |
38 | export default new Duck({
39 | namespace: 'my-app', store: 'widgets',
40 | types: ['LOAD', 'CREATE', 'UPDATE', 'REMOVE'],
41 | initialState: {},
42 | reducer: (state, action, duck) => {
43 | switch(action.type) {
44 | // do reducer stuff
45 | default: return state
46 | }
47 | },
48 | selectors: {
49 | root: state => state
50 | },
51 | creators: (duck) => ({
52 | loadWidgets: () => ({ type: duck.types.LOAD }),
53 | createWidget: widget => ({ type: duck.types.CREATE, widget }),
54 | updateWidget: widget => ({ type: duck.types.UPDATE, widget }),
55 | removeWidget: widget => ({ type: duck.types.REMOVE, widget })
56 | })
57 | })
58 | ```
59 |
60 | ```js
61 | // reducers.js
62 |
63 | import { combineReducers } from 'redux'
64 | import widgetDuck from './widgetDuck'
65 |
66 | export default combineReducers({ [widgetDuck.store]: widgetDuck.reducer })
67 | ```
68 |
69 | ### Constructor Arguments
70 |
71 | const { namespace, store, types, consts, initialState, creators } = options
72 |
73 | | Name | Description | Type | Example |
74 | |--------------|---------------------------------------------------------|--------------------------------|---------------------------------------------|
75 | | namespace | Used as a prefix for the types | String | `'my-app'` |
76 | | store | Used as a prefix for the types and as a redux state key | String | `'widgets'` |
77 | | storePath | Object path of the store from root infinal redux state. Defaults to the [duck.store] value. Can be used to define duck store location in nested state | String | `'foo.bar'` |
78 | | types | List of action types | Array | `[ 'CREATE', 'UPDATE' ]` |
79 | | consts | Constants you may need to declare | Object of Arrays | `{ statuses: [ 'LOADING', 'LOADED' ] }` |
80 | | initialState | State passed to the reducer when the state is undefined | Anything | `{}` |
81 | | reducer | Action reducer | function(state, action, duck) | `(state, action, duck) => { return state }` |
82 | | creators | Action creators | function(duck) | `duck => ({ type: types.CREATE })` |
83 | | sagas | Action sagas | function(duck) | `duck => ({ fetchData: function* { yield ... }` |
84 | | takes | Action takes | function(duck) | `duck => ([ takeEvery(types.FETCH, sagas.fetchData) ])` |
85 | | selectors | state selectors | Object of functions
or
function(duck) | `{ root: state => state}`
or
`duck => ({ root: state => state })` |
86 |
87 | ### Duck Accessors
88 |
89 | * duck.store
90 | * duck.storePath
91 | * duck.reducer
92 | * duck.creators
93 | * duck.sagas
94 | * duck.takes
95 | * duck.selectors
96 | * duck.types
97 | * for each const, duck.\
98 |
99 | ### Helper functions
100 |
101 | * **constructLocalized(selectors)**: maps selectors syntax from `(globalStore) => selectorBody` into `(localStore, globalStore) => selectorBody`. `localStore` is derived from `globalStore` on every selector execution using `duck.storage` key. Use to simplify selectors syntax when used in tandem with reduxes' `combineReducers` to bind the duck to a dedicated state part ([example](#creating-ducks-with-selectors)). If defined will use the duck.storePath value to determine the localized state in deeply nested redux state trees.
102 |
103 | ### Defining the Reducer
104 |
105 | While a plain vanilla reducer would be defined by something like this:
106 |
107 | ```js
108 | function reducer(state={}, action) {
109 | switch (action.type) {
110 | // ...
111 | default:
112 | return state
113 | }
114 | }
115 | ```
116 |
117 | Here the reducer has two slight differences:
118 |
119 | * It receives the duck itself as the third argument
120 | * It doesn't define the initial state (see [Defining the Initial State](#defining-the-initial-state))
121 |
122 | ```js
123 | new Duck({
124 | // ...
125 | reducer: (state, action, duck) => {
126 | switch (action.type) {
127 | // ...
128 | default:
129 | return state
130 | }
131 | }
132 | })
133 | ```
134 |
135 | With the `duck` argument you can access the types, the constants, etc (see [Duck Accessors](#duck-accessors)).
136 |
137 | ### Defining the Creators
138 |
139 | While plain vanilla creators would be defined by something like this:
140 |
141 | ```js
142 | export function createWidget(widget) {
143 | return { type: CREATE, widget }
144 | }
145 |
146 | // Using thunk
147 | export function updateWidget(widget) {
148 | return dispatch => {
149 | dispatch({ type: UPDATE, widget })
150 | }
151 | }
152 | ```
153 |
154 | With extensible-duck you define it as an Object of functions:
155 |
156 | ```js
157 | export default new Duck({
158 | // ...
159 | creators: {
160 | createWidget: widget => ({ type: 'CREATE', widget })
161 |
162 | // Using thunk
163 | updateWidget: widget => dispatch => {
164 | dispatch({ type: 'UPDATE', widget })
165 | }
166 | }
167 | })
168 | ```
169 |
170 | If you need to access any duck attribute, you can define a function that returns the Object of functions:
171 |
172 | ```js
173 | export default new Duck({
174 | // ...
175 | types: [ 'CREATE' ],
176 | creators: (duck) => ({
177 | createWidget: widget => ({ type: duck.types.CREATE, widget })
178 | })
179 | })
180 | ```
181 |
182 | ### Defining the Sagas
183 |
184 | While plain vanilla creators would be defined by something like this:
185 |
186 | ```js
187 | function* fetchData() {
188 | try{
189 | yield put({ type: reducerDuck.types.FETCH_PENDING })
190 | const payload = yield call(Get, 'data')
191 | yield put({
192 | type: reducerDuck.types.FETCH_FULFILLED,
193 | payload
194 | })
195 | } catch(err) {
196 | yield put({
197 | type: reducerDuck.types.FETCH_FAILURE,
198 | err
199 | })
200 | }
201 | }
202 |
203 | // Defining observer
204 | export default [ takeEvery(reducerDuck.types.FETCH, fetchData) ]
205 | ```
206 |
207 | With extensible-duck you define it as an Object of functions accessing any duck attribute:
208 |
209 | ```js
210 | export default new Duck({
211 | // ...
212 | sagas: {
213 | fetchData: function* (duck) {
214 | try{
215 | yield put({ type: duck.types.FETCH_PENDING })
216 | const payload = yield call(Get, 'data')
217 | yield put({
218 | type: duck.types.FETCH_FULFILLED,
219 | payload
220 | })
221 | } catch(err) {
222 | yield put({
223 | type: duck.types.FETCH_FAILURE,
224 | err
225 | })
226 | }
227 | }
228 | },
229 | // Defining observer
230 | takes: (duck) => ([
231 | takeEvery(duck.types.FETCH, duck.sagas.fetchData)
232 | ])
233 | })
234 | ```
235 |
236 | ### Defining the Initial State
237 |
238 | Usually the initial state is declared within the the reducer declaration, just like bellow:
239 |
240 | ```js
241 | function myReducer(state = {someDefaultValue}, action) {
242 | // ...
243 | }
244 | ```
245 |
246 | With extensible-duck you define it separately:
247 |
248 | ```js
249 | export default new Duck({
250 | // ...
251 | initialState: {someDefaultValue}
252 | })
253 | ```
254 |
255 | If you need to access the [types](#defining-the-types) or [constants](#defining-the-constants), you can define this way:
256 |
257 | ```js
258 | export default new Duck({
259 | // ...
260 | consts: { statuses: ['NEW'] },
261 | initialState: ({ statuses }) => ({ status: statuses.NEW })
262 | })
263 | ```
264 |
265 | ### Defining the Selectors
266 |
267 | Simple selectors:
268 |
269 | ```js
270 | export default new Duck({
271 | // ...
272 | selectors: {
273 | shopItems: state => state.shop.items
274 | }
275 | })
276 |
277 | ```
278 |
279 | Composed selectors:
280 |
281 | ```js
282 | export default new Duck({
283 | // ...
284 | selectors: {
285 | shopItems: state => state.shop.items,
286 | subtotal: new Duck.Selector(selectors => state =>
287 | selectors.shopItems(state).reduce((acc, item) => acc + item.value, 0)
288 | )
289 | }
290 | })
291 | ```
292 |
293 | Using with [Reselect](https://github.com/reactjs/reselect):
294 |
295 | ```js
296 | export default new Duck({
297 | // ...
298 | selectors: {
299 | shopItems: state => state.shop.items,
300 | subtotal: new Duck.Selector(selectors =>
301 | createSelector(
302 | selectors.shopItems,
303 | items => items.reduce((acc, item) => acc + item.value, 0)
304 | )
305 | )
306 | }
307 | })
308 | ```
309 |
310 | Selectors with duck reference:
311 |
312 | ```js
313 | export default new Duck({
314 | // ...
315 | selectors: (duck) => ({
316 | shopItems: state => state.shop.items,
317 | addedItems: new Duck.Selector(selectors =>
318 | createSelector(
319 | selectors.shopItems,
320 | items => {
321 | const out = [];
322 | items.forEach(item => {
323 | if (-1 === duck.initialState.shop.items.indexOf(item)) {
324 | out.push(item);
325 | }
326 | });
327 | return out;
328 | }
329 | )
330 | )
331 | })
332 | })
333 | ```
334 |
335 | ### Defining the Types
336 |
337 | ```js
338 | export default new Duck({
339 | namespace: 'my-app', store: 'widgets',
340 | // ...
341 | types: [
342 | 'CREATE', // myDuck.types.CREATE = "my-app/widgets/CREATE"
343 | 'RETREIVE', // myDuck.types.RETREIVE = "my-app/widgets/RETREIVE"
344 | 'UPDATE', // myDuck.types.UPDATE = "my-app/widgets/UPDATE"
345 | 'DELETE', // myDuck.types.DELETE = "my-app/widgets/DELETE"
346 | ]
347 | }
348 | ```
349 |
350 | ### Defining the Constants
351 |
352 | ```js
353 | export default new Duck({
354 | // ...
355 | consts: {
356 | statuses: ['NEW'], // myDuck.statuses = { NEW: "NEW" }
357 | fooBar: [
358 | 'FOO', // myDuck.fooBar.FOO = "FOO"
359 | 'BAR' // myDuck.fooBar.BAR = "BAR"
360 | ]
361 | }
362 | }
363 | ```
364 |
365 | ## Creating Reusable Ducks
366 |
367 | This example uses [redux-promise-middleware](https://github.com/pburtchaell/redux-promise-middleware)
368 | and [axios](https://github.com/mzabriskie/axios).
369 |
370 | ```js
371 | // remoteObjDuck.js
372 |
373 | import Duck from 'extensible-duck'
374 | import axios from 'axios'
375 |
376 | export default function createDuck({ namespace, store, path, initialState={} }) {
377 | return new Duck({
378 | namespace, store,
379 |
380 | consts: { statuses: [ 'NEW', 'LOADING', 'READY', 'SAVING', 'SAVED' ] },
381 |
382 | types: [
383 | 'UPDATE',
384 | 'FETCH', 'FETCH_PENDING', 'FETCH_FULFILLED',
385 | 'POST', 'POST_PENDING', 'POST_FULFILLED',
386 | ],
387 |
388 | reducer: (state, action, { types, statuses, initialState }) => {
389 | switch(action.type) {
390 | case types.UPDATE:
391 | return { ...state, obj: { ...state.obj, ...action.payload } }
392 | case types.FETCH_PENDING:
393 | return { ...state, status: statuses.LOADING }
394 | case types.FETCH_FULFILLED:
395 | return { ...state, obj: action.payload.data, status: statuses.READY }
396 | case types.POST_PENDING:
397 | case types.PATCH_PENDING:
398 | return { ...state, status: statuses.SAVING }
399 | case types.POST_FULFILLED:
400 | case types.PATCH_FULFILLED:
401 | return { ...state, status: statuses.SAVED }
402 | default:
403 | return state
404 | }
405 | },
406 |
407 | creators: ({ types }) => ({
408 | update: (fields) => ({ type: types.UPDATE, payload: fields }),
409 | get: (id) => ({ type: types.FETCH, payload: axios.get(`${path}/${id}`),
410 | post: () => ({ type: types.POST, payload: axios.post(path, obj) }),
411 | patch: () => ({ type: types.PATCH, payload: axios.patch(`${path}/${id}`, obj) })
412 | }),
413 |
414 | initialState: ({ statuses }) => ({ obj: initialState || {}, status: statuses.NEW, entities: [] })
415 | })
416 | }
417 | ```
418 |
419 | ```js
420 | // usersDuck.js
421 |
422 | import createDuck from './remoteObjDuck'
423 |
424 | export default createDuck({ namespace: 'my-app', store: 'user', path: '/users' })
425 | ```
426 |
427 | ```js
428 | // reducers.js
429 |
430 | import { combineReducers } from 'redux'
431 | import userDuck from './userDuck'
432 |
433 | export default combineReducers({ [userDuck.store]: userDuck.reducer })
434 | ```
435 |
436 | ## Extending Ducks
437 |
438 | This example is based on the previous one.
439 |
440 | ```js
441 | // usersDuck.js
442 |
443 | import createDuck from './remoteObjDuck.js'
444 |
445 | export default createDuck({ namespace: 'my-app',store: 'user', path: '/users' }).extend({
446 | types: [ 'RESET' ],
447 | reducer: (state, action, { types, statuses, initialState }) => {
448 | switch(action.type) {
449 | case types.RESET:
450 | return { ...initialState, obj: { ...initialState.obj, ...action.payload } }
451 | default:
452 | return state
453 | },
454 | creators: ({ types }) => ({
455 | reset: (fields) => ({ type: types.RESET, payload: fields }),
456 | })
457 | })
458 | ```
459 |
460 | ## Creating Reusable Duck Extensions
461 |
462 | This example is a refactor of the previous one.
463 |
464 | ```js
465 | // resetDuckExtension.js
466 |
467 | export default {
468 | types: [ 'RESET' ],
469 | reducer: (state, action, { types, statuses, initialState }) => {
470 | switch(action.type) {
471 | case types.RESET:
472 | return { ...initialState, obj: { ...initialState.obj, ...action.payload } }
473 | default:
474 | return state
475 | },
476 | creators: ({ types }) => ({
477 | reset: (fields) => ({ type: types.RESET, payload: fields }),
478 | })
479 | }
480 | ```
481 |
482 | ```js
483 | // userDuck.js
484 |
485 | import createDuck from './remoteObjDuck'
486 | import reset from './resetDuckExtension'
487 |
488 | export default createDuck({ namespace: 'my-app',store: 'user', path: '/users' }).extend(reset)
489 | ```
490 |
491 |
492 | ## Creating Ducks with selectors
493 |
494 | Selectors help in providing performance optimisations when used with libraries such as React-Redux, Preact-Redux etc.
495 |
496 | ```js
497 | // Duck.js
498 |
499 | import Duck, { constructLocalized } from 'extensible-duck'
500 |
501 | export default new Duck({
502 | store: 'fruits',
503 | initialState: {
504 | items: [
505 | { name: 'apple', value: 1.2 },
506 | { name: 'orange', value: 0.95 }
507 | ]
508 | },
509 | reducer: (state, action, duck) => {
510 | switch(action.type) {
511 | // do reducer stuff
512 | default: return state
513 | }
514 | },
515 | selectors: constructLocalized({
516 | items: state => state.items, // gets the items from state
517 | subTotal: new Duck.Selector(selectors => state =>
518 | // Get another derived state reusing previous selector. In this case items selector
519 | // Can compose multiple such selectors if using library like reselect. Recommended!
520 | // Note: The order of the selectors definitions matters
521 | selectors
522 | .items(state)
523 | .reduce((computedTotal, item) => computedTotal + item.value, 0)
524 | )
525 | })
526 | })
527 | ```
528 |
529 | ```js
530 | // reducers.js
531 |
532 | import { combineReducers } from 'redux'
533 | import Duck from './Duck'
534 |
535 | export default combineReducers({ [Duck.store]: Duck.reducer })
536 | ```
537 |
538 | ```js
539 | // HomeView.js
540 | import React from 'react'
541 | import Duck from './Duck'
542 |
543 | @connect(state => ({
544 | items: Duck.selectors.items(state),
545 | subTotal: Duck.selectors.subTotal(state)
546 | }))
547 | export default class HomeView extends React.Component {
548 | render(){
549 | // make use of sliced state here in props
550 | ...
551 | }
552 | }
553 | ```
554 |
--------------------------------------------------------------------------------
/dist/extensible-duck.min.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["extensible-duck.min.js"],"names":["root","factory","exports","module","define","amd","this","modules","installedModules","__webpack_require__","moduleId","i","l","call","m","c","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","n","__esModule","object","property","prototype","hasOwnProperty","p","s","__webpack_exports__","value","constructLocalized","Selector","_createClass","defineProperties","target","props","length","descriptor","writable","key","Constructor","protoProps","staticProps","_extends","assign","arguments","source","_typeof","Symbol","iterator","obj","constructor","_classCallCheck","instance","TypeError","_toConsumableArray","arr","Array","isArray","arr2","from","zipObject","keys","values","result","isFunction","func","isUndefined","undefined","isPlainObject","assignDefaults","options","consts","sagas","takes","creators","selectors","types","injectDuck","input","duck","Function","derivedSelectors","deriveSelectors","localizedSelectors","forEach","selector","globalState","localizedState","storePath","segments","concat","split","store","reduce","acc","segment","Error","join","getLocalizedState","composedSelectors","extractFunction","apply","Duck","_this","namespace","_options","initialState","map","type","reducer","bind","state","action","_this2","parent","mergeDeep","_len","sources","_key","_target","shift","push","_iteratorNormalCompletion","_didIteratorError","_iteratorError","_step","_iterator","next","done","err","return","parentSagas","parentTakes","parentCreators"],"mappings":"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","file":"extensible-duck.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"Duck\"] = factory();\n\telse\n\t\troot[\"Duck\"] = factory();\n})(this, function() {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, {\n/******/ \t\t\t\tconfigurable: false,\n/******/ \t\t\t\tenumerable: true,\n/******/ \t\t\t\tget: getter\n/******/ \t\t\t});\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = 0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\nObject.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony export (immutable) */ __webpack_exports__[\"constructLocalized\"] = constructLocalized;\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"constructLocalised\", function() { return constructLocalized; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Selector\", function() { return Selector; });\nvar _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; }; }();\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\nfunction typeValue(namespace, store, type) {\n return namespace + '/' + store + '/' + type;\n}\n\nfunction zipObject(keys, values) {\n if (arguments.length == 1) {\n values = keys[1];\n keys = keys[0];\n }\n\n var result = {};\n var i = 0;\n\n for (i; i < keys.length; i += 1) {\n result[keys[i]] = values[i];\n }\n\n return result;\n}\n\nfunction buildTypes(namespace, store, types) {\n return zipObject(types, types.map(function (type) {\n return typeValue(namespace, store, type);\n }));\n}\n\nfunction isObject(obj) {\n return obj !== null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object';\n}\n\nfunction isFunction(func) {\n return func !== null && typeof func === 'function';\n}\n\nfunction isUndefined(value) {\n return typeof value === 'undefined' || value === undefined;\n}\n\nfunction isPlainObject(obj) {\n return isObject(obj) && (obj.constructor === Object || // obj = {}\n obj.constructor === undefined) // obj = Object.create(null)\n ;\n}\n\nfunction mergeDeep(target) {\n for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n sources[_key - 1] = arguments[_key];\n }\n\n if (!sources.length) return target;\n var source = sources.shift();\n\n if (Array.isArray(target)) {\n if (Array.isArray(source)) {\n var _target;\n\n (_target = target).push.apply(_target, _toConsumableArray(source));\n } else {\n target.push(source);\n }\n } else if (isPlainObject(target)) {\n if (isPlainObject(source)) {\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = Object.keys(source)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var key = _step.value;\n\n if (!target[key]) {\n target[key] = source[key];\n } else {\n mergeDeep(target[key], source[key]);\n }\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n } else {\n throw new Error('Cannot merge object with non-object');\n }\n } else {\n target = source;\n }\n\n return mergeDeep.apply(undefined, [target].concat(sources));\n}\n\nfunction assignDefaults(options) {\n return _extends({}, options, {\n consts: options.consts || {},\n sagas: options.sagas || function () {\n return {};\n },\n takes: options.takes || function () {\n return [];\n },\n creators: options.creators || function () {\n return {};\n },\n selectors: options.selectors || {},\n types: options.types || []\n });\n}\n\nfunction injectDuck(input, duck) {\n if (input instanceof Function) {\n return input(duck);\n } else {\n return input;\n }\n}\n\nfunction getLocalizedState(globalState, duck) {\n var localizedState = void 0;\n\n if (duck.storePath) {\n var segments = [].concat(duck.storePath.split('.'), duck.store);\n localizedState = segments.reduce(function getSegment(acc, segment) {\n if (!acc[segment]) {\n throw Error('state does not contain reducer at storePath ' + segments.join('.'));\n }\n return acc[segment];\n }, globalState);\n } else {\n localizedState = globalState[duck.store];\n }\n\n return localizedState;\n}\n\nfunction constructLocalized(selectors) {\n var derivedSelectors = deriveSelectors(selectors);\n return function (duck) {\n var localizedSelectors = {};\n Object.keys(derivedSelectors).forEach(function (key) {\n var selector = derivedSelectors[key];\n localizedSelectors[key] = function (globalState) {\n return selector(getLocalizedState(globalState, duck), globalState);\n };\n });\n return localizedSelectors;\n };\n}\n\n// An alias for those who do not use the above spelling.\n\n\n/**\n * Helper utility to assist in composing the selectors.\n * Previously defined selectors can be used to derive future selectors.\n *\n * @param {object} selectors\n * @returns\n */\nfunction deriveSelectors(selectors) {\n var composedSelectors = {};\n Object.keys(selectors).forEach(function (key) {\n var selector = selectors[key];\n if (selector instanceof Selector) {\n composedSelectors[key] = function () {\n return (composedSelectors[key] = selector.extractFunction(composedSelectors)).apply(undefined, arguments);\n };\n } else {\n composedSelectors[key] = selector;\n }\n });\n return composedSelectors;\n}\n\nvar Duck = function () {\n function Duck(options) {\n var _this = this;\n\n _classCallCheck(this, Duck);\n\n options = assignDefaults(options);\n var _options = options,\n namespace = _options.namespace,\n store = _options.store,\n storePath = _options.storePath,\n types = _options.types,\n consts = _options.consts,\n initialState = _options.initialState,\n creators = _options.creators,\n selectors = _options.selectors,\n sagas = _options.sagas,\n takes = _options.takes;\n\n this.options = options;\n Object.keys(consts).forEach(function (name) {\n _this[name] = zipObject(consts[name], consts[name]);\n });\n\n this.store = store;\n this.storePath = storePath;\n this.types = buildTypes(namespace, store, types);\n this.initialState = isFunction(initialState) ? initialState(this) : initialState;\n this.reducer = this.reducer.bind(this);\n this.selectors = deriveSelectors(injectDuck(selectors, this));\n this.creators = creators(this);\n this.sagas = sagas(this);\n this.takes = takes(this);\n }\n\n _createClass(Duck, [{\n key: 'reducer',\n value: function reducer(state, action) {\n if (isUndefined(state)) {\n state = this.initialState;\n }\n return this.options.reducer(state, action, this);\n }\n }, {\n key: 'extend',\n value: function extend(options) {\n var _this2 = this;\n\n if (isFunction(options)) {\n options = options(this);\n }\n options = assignDefaults(options);\n var parent = this.options;\n var initialState = void 0;\n if (isFunction(options.initialState)) {\n initialState = function initialState(duck) {\n return options.initialState(duck, _this2.initialState);\n };\n } else if (isUndefined(options.initialState)) {\n initialState = parent.initialState;\n } else {\n initialState = options.initialState;\n }\n return new Duck(_extends({}, parent, options, {\n initialState: initialState,\n consts: mergeDeep({}, parent.consts, options.consts),\n sagas: function sagas(duck) {\n var parentSagas = parent.sagas(duck);\n return _extends({}, parentSagas, options.sagas(duck, parentSagas));\n },\n takes: function takes(duck) {\n var parentTakes = parent.takes(duck);\n return [].concat(_toConsumableArray(parentTakes), _toConsumableArray(options.takes(duck, parentTakes)));\n },\n creators: function creators(duck) {\n var parentCreators = parent.creators(duck);\n return _extends({}, parentCreators, options.creators(duck, parentCreators));\n },\n selectors: function selectors(duck) {\n return _extends({}, injectDuck(parent.selectors, duck), injectDuck(options.selectors, duck));\n },\n types: [].concat(_toConsumableArray(parent.types), _toConsumableArray(options.types)),\n reducer: function reducer(state, action, duck) {\n state = parent.reducer(state, action, duck);\n if (isUndefined(options.reducer)) {\n return state;\n } else {\n return options.reducer(state, action, duck);\n }\n }\n }));\n }\n }]);\n\n return Duck;\n}();\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Duck);\n\n\nvar Selector = function () {\n function Selector(func) {\n _classCallCheck(this, Selector);\n\n this.func = func;\n }\n\n _createClass(Selector, [{\n key: 'extractFunction',\n value: function extractFunction(selectors) {\n return this.func(selectors);\n }\n }]);\n\n return Selector;\n}();\n\nDuck.Selector = Selector;\n\n/***/ })\n/******/ ]);\n});\n//# sourceMappingURL=extensible-duck.js.map"]}
--------------------------------------------------------------------------------