├── .npmignore ├── immuto.png ├── docs ├── immuto.png ├── react.png ├── redux.png ├── typescript.png ├── gitbook │ ├── images │ │ ├── favicon.ico │ │ └── apple-touch-icon-precomposed-152.png │ ├── fonts │ │ └── fontawesome │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-search │ │ ├── search.css │ │ ├── search-engine.js │ │ ├── search.js │ │ └── lunr.min.js │ ├── gitbook-plugin-theme-api │ │ ├── theme-api.js │ │ └── theme-api.css │ ├── gitbook-plugin-lunr │ │ ├── search-lunr.js │ │ └── lunr.min.js │ ├── gitbook-plugin-sharing │ │ └── buttons.js │ ├── gitbook-plugin-highlight │ │ └── ebook.css │ └── gitbook-plugin-fontsettings │ │ ├── fontsettings.js │ │ └── website.css ├── how_it_works │ ├── how_properties_reference_actions_related.md │ ├── index.html │ ├── cursors.html │ ├── amend.html │ └── the_piping_operator.html └── getting_started │ ├── index.html │ └── 04_what_just_happened.html ├── docs-src ├── immuto.png ├── react.png ├── redux.png ├── typescript.png ├── getting_started │ ├── README.md │ ├── 02_our_first_data_model.md │ ├── 01_creating_the_project.md │ ├── 04_what_just_happened.md │ ├── 05_a_collection_of_people.md │ ├── 03_making_the_ui.md │ └── 06_dispatching_some_people.md ├── how_it_works │ ├── README.md │ ├── amend.md │ ├── cursors.md │ ├── the_piping_operator.md │ ├── how_textinput_works.md │ ├── references.md │ └── how_properties_reference_actions_related.md ├── book.json ├── SUMMARY.md └── README.md ├── .gitignore ├── .travis.yml ├── debugTests.js ├── spec ├── support │ └── jasmine.json ├── founder.ts ├── shop.ts ├── shelf.ts ├── book.ts └── immutoSpec.ts ├── tsconfig.json ├── package.json ├── LICENSE └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /immuto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/immuto.png -------------------------------------------------------------------------------- /docs/immuto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/immuto.png -------------------------------------------------------------------------------- /docs/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/react.png -------------------------------------------------------------------------------- /docs/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/redux.png -------------------------------------------------------------------------------- /docs-src/immuto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs-src/immuto.png -------------------------------------------------------------------------------- /docs-src/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs-src/react.png -------------------------------------------------------------------------------- /docs-src/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs-src/redux.png -------------------------------------------------------------------------------- /docs/typescript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/typescript.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | built 2 | node_modules 3 | .DS_Store 4 | .vscode 5 | .alm/ 6 | .editorconfig 7 | npm-debug.log -------------------------------------------------------------------------------- /docs-src/typescript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs-src/typescript.png -------------------------------------------------------------------------------- /docs/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | - "4.1" 5 | before_install: 6 | - npm install -g typescript@rc 7 | - npm install -g jasmine 8 | -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielearwicker/immuto/HEAD/docs/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /debugTests.js: -------------------------------------------------------------------------------- 1 | var Jasmine = require("jasmine"); 2 | 3 | var jasmine = new Jasmine(); 4 | 5 | jasmine.loadConfigFile('spec/support/jasmine.json'); 6 | 7 | jasmine.execute(); 8 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "built/spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "spec/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false 11 | } 12 | -------------------------------------------------------------------------------- /docs-src/getting_started/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | Let's start by building a very simple but complete working application. It will display a person's details, and let you edit them. 3 | 4 | Not very impressive, but we'll build from there. 5 | 6 | [Let's get started!](01_creating_the_project.md) -------------------------------------------------------------------------------- /docs-src/how_it_works/README.md: -------------------------------------------------------------------------------- 1 | # How It Works 2 | Here we'll lift the covers and discover how the pieces of Immuto fit together. 3 | 4 | You don't necessarily need to know all this detail to use Immuto, but it will help you become a "power user" and become able to figure out solutions to problems more easily. 5 | -------------------------------------------------------------------------------- /docs-src/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ "theme-api" ], 3 | "pluginsConfig": { 4 | "theme-api": { 5 | "languages": [ 6 | { 7 | "lang": "js", 8 | "name": "JavaScript", 9 | "default": true 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spec/founder.ts: -------------------------------------------------------------------------------- 1 | import * as I from "../index"; 2 | import { action, reducer, reference, amend } from "../index"; 3 | 4 | import { Shop } from "./shop"; 5 | 6 | export interface Founder { 7 | readonly name: string; 8 | readonly shop: Shop; 9 | } 10 | 11 | export namespace Founder { 12 | 13 | export const setName = action("SET_NAME", 14 | (shop: Founder, name: string) => amend(shop, { name })); 15 | 16 | export const shop = reference("SHELVES", Shop, 17 | (founder: Founder) => founder.shop); 18 | 19 | export const empty: Founder = { 20 | name: "", 21 | shop: Shop.empty 22 | }; 23 | 24 | export const reduce = reducer(empty) 25 | .action(setName) 26 | .action(shop); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /docs-src/how_it_works/amend.md: -------------------------------------------------------------------------------- 1 | # How the amend function works 2 | 3 | The `amend` function is defined as: 4 | 5 | ```ts 6 | function amend(o1: O1, o2: O2) { 7 | return assign({}, o1, o2); 8 | } 9 | ``` 10 | 11 | And `assign` is just a substitute for the gradually appearing standard function `Object.assign`. 12 | 13 | The purpose of amend is to fill in for the object spread operator that is not yet in TypeScript. It would be cool if we could say: 14 | 15 | ```ts 16 | { ...book, title } 17 | ``` 18 | 19 | which would mean: make a new object with all the properties of `book`, and add (or replace) the `title` property with the value of the `title` variable. But in the meantime this isn't so bad: 20 | 21 | ```ts 22 | amend(book, { title }) 23 | ``` 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "isolatedModules": false, 7 | "jsx": "react", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": true, 11 | "noImplicitAny": true, 12 | "noImplicitThis": true, 13 | "removeComments": false, 14 | "noLib": false, 15 | "preserveConstEnums": true, 16 | "outDir": "built", 17 | "strictNullChecks": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "suppressImplicitAnyIndexErrors": false, 21 | "sourceMap": true 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "built" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /docs-src/how_it_works/cursors.md: -------------------------------------------------------------------------------- 1 | # How Cursors work 2 | 3 | A cursor is a way to read the state at some location in a data structure, and also to dispatch actions to that same location. 4 | 5 | This is a simplified version, leaving out the [piping operator](the_piping_operator.md): 6 | 7 | ```ts 8 | interface Cursor { 9 | readonly state: S; 10 | (action: A): Cursor; 11 | } 12 | ``` 13 | 14 | Two things to note: 15 | 16 | 1. A cursor doesn't *have* a dispatch function; it *is* a dispatch function. 17 | 2. It returns another cursor of the same type, so you can access the new state after your action has been dispatched. The value of `state` never changes on an existing cursor. 18 | 19 | You can call `snapshot`, passing it your store` to get a cursor to the whole store, and then using the piping operator to get to the objects inside. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "immuto", 3 | "version": "0.9.34", 4 | "description": "Very strongly typed Redux adaptation for TypeScript", 5 | "main": "built/index.js", 6 | "typings": "built/index.d.ts", 7 | "scripts": { 8 | "test": "jasmine", 9 | "prepublish": "tsc", 10 | "docs": "gitbook build docs-src docs" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/danielearwicker/immuto.git" 15 | }, 16 | "keywords": [ 17 | "redux", 18 | "react", 19 | "typescript" 20 | ], 21 | "author": "Daniel Earwicker ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/danielearwicker/immuto/issues" 25 | }, 26 | "homepage": "https://github.com/danielearwicker/immuto#readme", 27 | "dependencies": { 28 | "redux": "^3.5.2" 29 | }, 30 | "devDependencies": { 31 | "jasmine": "^2.5.2", 32 | "typescript": "^2.0.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs-src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [Getting Started](getting_started/README.md) 5 | * [1. Creating the project](getting_started/01_creating_the_project.md) 6 | * [2. Our first data model](getting_started/02_our_first_data_model.md) 7 | * [3. Making the UI](getting_started/03_making_the_ui.md) 8 | * [4. What just happened?](getting_started/04_what_just_happened.md) 9 | * [5. A collection of people](getting_started/05_a_collection_of_people.md) 10 | * [6. Dispatching some people](getting_started/06_dispatching_some_people.md) 11 | * [How It All Works](how_it_works/README.md) 12 | * [How TextInput works](how_it_works/how_textinput_works.md) 13 | * [How the Piping Operator works](how_it_works/the_piping_operator.md) 14 | * [How Cursors work](how_it_works/cursors.md) 15 | * [How References work](how_it_works/references.md) 16 | * [How properties, references and actions are related](how_properties_reference_actions_related.md) 17 | * [How the amend function works](how_it_works/amend.md) 18 | -------------------------------------------------------------------------------- /spec/shop.ts: -------------------------------------------------------------------------------- 1 | import * as I from "../index"; 2 | import { action, reducer, objectByString, reference, amend } from "../index"; 3 | 4 | import { Book } from "./book"; 5 | import { Shelf } from "./shelf"; 6 | 7 | export type Shelves = { [id: string]: Shelf } 8 | 9 | export namespace Shelves { 10 | 11 | export const empty: Shelves = {}; 12 | export const at = objectByString(Shelf); 13 | export const reduce = reducer(empty).action(at); 14 | } 15 | 16 | export interface Shop { 17 | readonly name: string; 18 | readonly shelves: { [id: string]: Shelf }; 19 | } 20 | 21 | export namespace Shop { 22 | 23 | export const setName = action("SET_NAME", 24 | (shop: Shop, name: string) => amend(shop, { name })); 25 | 26 | export const shelves = reference("SHELVES", Shelves, 27 | (shop: Shop) => shop.shelves); 28 | 29 | export const empty: Shop = { 30 | name: "", 31 | shelves: Shelves.empty 32 | }; 33 | 34 | export const reduce = reducer(empty) 35 | .action(setName) 36 | .action(shelves); 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Daniel Earwicker 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 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-search/search.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS only styled the search results section, not the search input 3 | It defines the basic interraction to hide content when displaying results, etc 4 | */ 5 | #book-search-results .search-results { 6 | display: none; 7 | } 8 | #book-search-results .search-results ul.search-results-list { 9 | list-style-type: none; 10 | padding-left: 0; 11 | } 12 | #book-search-results .search-results ul.search-results-list li { 13 | margin-bottom: 1.5rem; 14 | padding-bottom: 0.5rem; 15 | /* Highlight results */ 16 | } 17 | #book-search-results .search-results ul.search-results-list li p em { 18 | background-color: rgba(255, 220, 0, 0.4); 19 | font-style: normal; 20 | } 21 | #book-search-results .search-results .no-results { 22 | display: none; 23 | } 24 | #book-search-results.open .search-results { 25 | display: block; 26 | } 27 | #book-search-results.open .search-noresults { 28 | display: none; 29 | } 30 | #book-search-results.no-results .search-results .has-results { 31 | display: none; 32 | } 33 | #book-search-results.no-results .search-results .no-results { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /spec/shelf.ts: -------------------------------------------------------------------------------- 1 | import * as I from "../index"; 2 | import { action, reducer, objectByNumber, removeFromObjectByNumber, reference, amend } from "../index"; 3 | 4 | import { Book } from "./book"; 5 | 6 | export type Books = { [id: number]: Book } 7 | 8 | export namespace Books { 9 | 10 | export const empty: Books = {}; 11 | export const at = objectByNumber(Book); 12 | export const remove = removeFromObjectByNumber(); 13 | 14 | export const reduce = reducer(empty) 15 | .action(at) 16 | .action(remove); 17 | } 18 | 19 | export interface Shelf { 20 | readonly description: string; 21 | readonly books: Books; 22 | } 23 | 24 | export namespace Shelf { 25 | 26 | export const setDescription = action("SET_DESCRIPTION", 27 | (shelf: Shelf, description: string) => amend(shelf, { description })); 28 | 29 | export const books = reference("BOOKS", Books, (shelf: Shelf) => shelf.books); 30 | 31 | export const empty: Shelf = { 32 | description: "", 33 | books: Books.empty 34 | }; 35 | 36 | export const reduce = reducer(empty) 37 | .action(setDescription) 38 | .action(books); 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/danielearwicker/immuto.svg?branch=master)](https://travis-ci.org/danielearwicker/immuto) 2 | 3 | # immuto 4 | Very strongly typed Redux adaptation for TypeScript 5 | 6 | 7 | 8 | # Install 9 | 10 | npm install immuto 11 | 12 | And then 13 | 14 | import * as I from "immuto" 15 | 16 | # Docs 17 | 18 | * [Proper documentation](https://danielearwicker.github.io/immuto/) is gradually appearing... 19 | * [Deep background walkthrough](http://danielearwicker.github.io/Immuto_Strongly_Typed_Redux_Composition.html) 20 | * [Example of React binding](http://danielearwicker.github.io/Immuto_Working_with_React_An_Example_.html) 21 | 22 | Or just install and read the highly documented tests : https://github.com/danielearwicker/immuto/tree/master/spec ! 23 | 24 | # Related 25 | * [immuto-react](https://github.com/danielearwicker/immuto-react) 26 | * [immuto-example](https://github.com/danielearwicker/immuto-example) (with live demo!) 27 | 28 | # Contributing 29 | 30 | * `npm install` 31 | * Open `index.ts` in VSCode (or your favorite TypeScript supporting IDE) and start cracking. 32 | * `prepublish` is wired to compile the code using TypeScript latest (see our devDependency) 33 | * `npm run test` makes sure nothing awful happens. 34 | 35 | > Originally developed in http://alm.tools. :rose: 36 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-search/search-engine.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Global search objects 6 | var engine = null; 7 | var initialized = false; 8 | 9 | // Set a new search engine 10 | function setEngine(Engine, config) { 11 | initialized = false; 12 | engine = new Engine(config); 13 | 14 | init(config); 15 | } 16 | 17 | // Initialize search engine with config 18 | function init(config) { 19 | if (!engine) throw new Error('No engine set for research. Set an engine using gitbook.research.setEngine(Engine).'); 20 | 21 | return engine.init(config) 22 | .then(function() { 23 | initialized = true; 24 | gitbook.events.trigger('search.ready'); 25 | }); 26 | } 27 | 28 | // Launch search for query q 29 | function query(q, offset, length) { 30 | if (!initialized) throw new Error('Search has not been initialized'); 31 | return engine.search(q, offset, length); 32 | } 33 | 34 | // Get stats about search 35 | function getEngine() { 36 | return engine? engine.name : null; 37 | } 38 | 39 | function isInitialized() { 40 | return initialized; 41 | } 42 | 43 | // Initialize gitbook.search 44 | gitbook.search = { 45 | setEngine: setEngine, 46 | getEngine: getEngine, 47 | query: query, 48 | isInitialized: isInitialized 49 | }; 50 | }); -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-theme-api/theme-api.js: -------------------------------------------------------------------------------- 1 | require(["gitbook","jquery"],function(t,e){function n(e){r=t.storage.get("themeApi",{split:e.split,currentLang:null})}function a(){t.storage.set("themeApi",r),i()}function i(){e(".book").toggleClass("two-columns",r.split),s=e(".api-method-sample"),s.each(function(){var t=!(e(this).data("lang")==r.currentLang);e(this).toggleClass("hidden",t)})}function o(){t.toolbar.removeButtons(c),c=[],s=e(".api-method-sample");var n=[],i=!1;s.each(function(){var t,a,o=!1,s=e(this).data("lang"),c=e(this).data("name");s==r.currentLang&&(i=!0,o=!0),t=e.grep(n,function(t){return t.name==c}),a=!!t.length,a||n.push({name:c,lang:s,"default":o})}),n.reverse(),e.each(n,function(o,s){var l,g=s["default"]||!i&&o==n.length-1;l=t.toolbar.createButton({text:s.name,position:"right",className:"lang-switcher"+(g?" active":""),onClick:function(t){r.currentLang=s.lang,a(),e(".btn.lang-switcher.active").removeClass("active"),e(t.currentTarget).addClass("active")}}),c.push(l),g&&(r.currentLang=s.lang)})}var s,r,c=[],l=[{config:"light",text:"Light",id:0},{config:"dark",text:"Dark",id:3}];t.events.bind("start",function(e,i){var o=i["theme-api"];t.toolbar.createButton({icon:"fa fa-columns",label:"Change Layout",onClick:function(){r.split=!r.split,a()}}),t.fontsettings.setThemes(l),t.fontsettings.setTheme(o.theme),n(o)}),t.events.on("page.change",function(){o(),i()}),t.events.on("comment.toggled",function(e,n,a){if(n.parents(".api-method-definition").length){var i=t.state.$book.find(".page-wrapper");i.toggleClass("comments-open-from-definition",a&&r.split)}})}); 2 | -------------------------------------------------------------------------------- /docs-src/getting_started/02_our_first_data_model.md: -------------------------------------------------------------------------------- 1 | # Our first data model 2 | 3 | We're using Immuto because we want all our data to be immutable. For a really simple example we'll go with: 4 | 5 | ```ts 6 | export interface Person { 7 | readonly firstName: string; 8 | readonly lastName: string; 9 | readonly trusted: boolean; 10 | } 11 | ``` 12 | 13 | Looks like we have trust issues. But anyway. Save the above into your project directory as `Person.ts`. 14 | 15 | Now let's describe this structure to Immuto. At the top of the file add: 16 | 17 | ```ts 18 | import { property, reducer } from "immuto"; 19 | ``` 20 | 21 | Then at the bottom continue with: 22 | 23 | ```ts 24 | export namespace Person { 25 | 26 | export const empty: Person = { 27 | firstName: "", 28 | lastName: "", 29 | trusted: false 30 | }; 31 | 32 | export const firstName = property("FIRST_NAME", (p: Person) => p.firstName); 33 | export const lastName = property("LAST_NAME", (p: Person) => p.lastName); 34 | export const trusted = property("TRUSTED", (p: Person) => p.trusted); 35 | 36 | export const reduce = reducer(empty) 37 | .action(firstName) 38 | .action(lastName) 39 | .action(trusted); 40 | 41 | export type Cursor = typeof reduce.cursorType; 42 | } 43 | ``` 44 | 45 | We've declared an `empty` Person (not saying they're emotionally empty.) Then we've declared `property` helpers for each of the properties we want user to be able to independently edit, giving each one a Redux-style all-caps name. 46 | 47 | Then we create the `reduce` function. This is the crux of the Redux approach, but here Immuto writes the function for us. 48 | 49 | The last part, where we declare a type alias called `Cursor`, is hard to explain at this point, but we'll get to it soon. 50 | -------------------------------------------------------------------------------- /docs-src/getting_started/01_creating_the_project.md: -------------------------------------------------------------------------------- 1 | # Creating the project 2 | 3 | This is the same type of stuff as any modern web app project, and in fact is similar to [the TypeScript guidelines for React and webpack](https://www.typescriptlang.org/docs/handbook/react-&-webpack.html). 4 | 5 | First install [nodejs](https://nodejs.org/en/), then make sure you have TypeScript and webpack installed: 6 | 7 | ```bash 8 | $ npm install -g typescript@rc webpack 9 | ``` 10 | 11 | Note we're using the release candidate, because Immuto requires TypeScript 2.0 which hasn't gone mainstream just yet. 12 | 13 | Now create your project directory: 14 | 15 | ```bash 16 | $ mkdir myapp 17 | $ cd myapp 18 | $ npm init 19 | ``` 20 | 21 | And accept all the defaults. Then add the packages that we need for the app to run: 22 | 23 | ```bash 24 | $ npm install --save react react-dom immuto immuto-react 25 | ``` 26 | 27 | And then the packages we'll need to develop the app: 28 | 29 | ```bash 30 | $ npm install --save-dev @types/react @types/react-dom typescript@rc webpack ts-loader source-map-loader 31 | ``` 32 | 33 | Run this command to get a minimal React project in place: 34 | 35 | ```bash 36 | $ node node_modules/immuto-react/templates/get 37 | ``` 38 | 39 | This creates: 40 | 41 | * `tsconfig.json` - strictest TypeScript rules 42 | * `index.tsx` - entry point for app 43 | * `webpack.config.js` - builds `index.tsx` to `built/bundle.js` with source maps 44 | * `index.html` - minimal container page, loads `built/bundle.js` 45 | 46 | You can now build the app with: 47 | 48 | ```bash 49 | $ webpack 50 | ``` 51 | 52 | You can keep webpack running all the time by adding the `-w` switch, so it rebuilds every time you save a modification. 53 | 54 | If you open `index.html` in your browser you should see the message: 55 | 56 | ``` 57 | Hello, world! 58 | ``` 59 | 60 | Now our aim is to improve on that... -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-lunr/search-lunr.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /docs-src/how_it_works/the_piping_operator.md: -------------------------------------------------------------------------------- 1 | # How the Piping Operator works 2 | 3 | The piping operator is the `$` method available on cursors. It is ridiculously simple. You pass it a function, and it calls that function, *passing itself* as the only argument, then returns whatever the function returns. That's it. The implementation, sans type declarations, is equivalent to: 4 | 5 | ```ts 6 | $(ref) { 7 | return ref(this); 8 | } 9 | ``` 10 | 11 | The type declarations are important, of course: the things being passed around must be cursors, though the input and output cursors can refer to different types: 12 | 13 | ```ts 14 | $(ref: (outer: Cursor) => Cursor): Cursor; 15 | ``` 16 | 17 | The parameter is called `ref` because [reference](references.md) definitions are ideal for passing in. 18 | 19 | Why bother with such a simple thing? So we can write easy-to-read expressions that navigate down a hierarchy. Suppose a `Shop` has a collection `Shelves` of `Shelf` objects, each having a collection `Books` of `Book` objects, each having a `title`. 20 | 21 | Without piping, we have to write inside-out expressions. Here, `shop` is the starting point, yet appears in the middle! 22 | 23 | ```ts 24 | const book = Book.title( 25 | Books.at( 26 | Shelf.books( 27 | Shelves.at( 28 | Shop.shelves(shop), 29 | 13 30 | ) 31 | ), 32 | 832 33 | ) 34 | ); 35 | ``` 36 | 37 | With piping, we "send" the input through a pipeline of operators, and it's self-explanatory: 38 | 39 | ```ts 40 | const title = shop.$(Shop.shelves) 41 | .$(Shelves.at(13)) 42 | .$(Shelf.books) 43 | .$(Books.at(832)) 44 | .$(Book.title); 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /docs-src/getting_started/04_what_just_happened.md: -------------------------------------------------------------------------------- 1 | # What just happened? 2 | 3 | We just achieved declarative two-way binding with Redux. No imperative code was required, just pure declarations. How is this possible? 4 | 5 | Immutable data is intentionally hard to modify. We are forced to clone the whole data structure just to change one tiny piece. What we need is that one weird trick *(doctors hate her!)* known as a cursor. 6 | 7 | A **cursor** represents a single location in the data structure, and when you pass it a new value for that location, it takes care of cloning the rest of the structure around it. 8 | 9 | Immuto generalises this idea and says that cursors must be sent actions (because Immuto *is* Redux, and in Redux actions are the only way to change anything). 10 | 11 | So because our `PersonEditor` has a `Person.Cursor`, it can both access the current state of that `Person` and also send actions to it. 12 | 13 | A **property** is a really simple example of a cursor, supporting a single action type, `"REPLACE"`, that lets you assign a new value. So it mimics a traditional cursor. 14 | 15 | The `binding` prop of `TextInput` needs a cursor to a string. We have a cursor to a whole `Person`. We can "pipe" it through `Person.firstName` to get a cursor to the `firstName` string of our `Person`. 16 | 17 | How did we get a cursor to a person? Well, in this case we used `bindToStore`, because our whole store is a `Person`. But a cursor can refer to any `Person` embedded within a complex structure, so `PersonEditor` doesn't necessarily have to be at the root of our UI, as we'll see. 18 | 19 | It's good idea to only use properties for really simple individual values: strings, numbers, booleans, that can vary independently. 20 | 21 | If you find yourself storing your whole life story in a single property, that might need a rethink. You'll probably prefer to enforce rules about how its various parts can be modified, to keep it consistent. Immuto is all about structuring stuff, so we'll get on to some ways of doing that. 22 | -------------------------------------------------------------------------------- /spec/book.ts: -------------------------------------------------------------------------------- 1 | import * as I from "../index"; 2 | import { action, property, reducer, amend, snapshot, Cursor, Reducer, array, replace, assign, 3 | ReducerBuilder, ActionDefinition, Action, Store, ReducerOrProvider, getReducer } from "../index"; 4 | import { createStore } from "redux" 5 | 6 | export interface Product { 7 | readonly price: number; 8 | } 9 | 10 | export namespace Product { 11 | 12 | export const empty: Product = { price: 0 }; 13 | 14 | export const setPrice = action("SET_PRICE", 15 | (product: Product, price: number) => amend(product, { price })); 16 | 17 | export const reduce = reducer(empty).action(setPrice); 18 | } 19 | 20 | export namespace Product { 21 | export type Cursor = typeof Product.reduce.cursorType; 22 | } 23 | 24 | export interface Book extends Product { 25 | readonly title: string; 26 | readonly authors: string[]; 27 | } 28 | 29 | export namespace Book { 30 | 31 | export const title = property("TITLE", (book: Book) => book.title); 32 | 33 | export const setTitle = action("SET_TITLE", 34 | (book: Book, title: string) => amend(book, { title })); 35 | 36 | export const addAuthor = action("ADD_AUTHOR", 37 | (book: Book, author: string) => amend(book, { authors: book.authors.concat(author) })); 38 | 39 | export const empty: Book = { 40 | price: 0, 41 | title: "", 42 | authors: [] 43 | }; 44 | 45 | export const reduce = Product.reduce.mixin( 46 | reducer(empty) 47 | .action(title) 48 | .action(setTitle) 49 | .action(addAuthor) 50 | ); 51 | 52 | export type Cursor = typeof Book.reduce.cursorType; 53 | } 54 | 55 | export type Flavour = "sweet"|"savoury"|"none"; 56 | 57 | export interface Food extends Product { 58 | readonly flavour: Flavour; 59 | } 60 | 61 | export namespace Food { 62 | 63 | export const flavour = property("FLAVOUR", (food: Food) => food.flavour); 64 | 65 | export const empty: Food = { 66 | flavour: "none", 67 | price: 0 68 | }; 69 | 70 | export const reduce = Product.reduce.mixin( 71 | reducer(empty).action(flavour) 72 | ); 73 | 74 | export type Cursor = typeof Food.reduce.cursorType; 75 | } 76 | -------------------------------------------------------------------------------- /docs-src/how_it_works/how_textinput_works.md: -------------------------------------------------------------------------------- 1 | # How TextInput works 2 | 3 | The source for `TextInput` is so short, leaving aside some interface declarations, it's worth taking a look to see how little magic is involved. 4 | 5 | Basically it's Redux being played out on a microscopic scale. 6 | 7 | First, you need to know that actions in Immuto always conform to this interface: 8 | 9 | ```ts 10 | export interface Action { 11 | readonly type: T; 12 | readonly payload: P; 13 | } 14 | ``` 15 | 16 | Second, those things we previously talked about called cursors are represented by this interface: 17 | 18 | ```ts 19 | export interface Cursor { 20 | readonly state: S; 21 | (action: A): Cursor; 22 | } 23 | ``` 24 | 25 | So a cursor is a function that (a) accepts an action (and returns another cursor) and (b) has a `state` property so you can access the current value. It's like a terse, immutable version of a Redux store. 26 | 27 | Third, there's a built-in action creator in Immuto called `replace`, which builds an action such as: 28 | 29 | ```ts 30 | { type: "REPLACE", payload: "Homer" } 31 | ``` 32 | 33 | The intention of a `"REPLACE"` action is to tell its target to replace the existing value with the one in the `payload`. 34 | 35 | And last, there's a type called `Property`, defined as: 36 | 37 | ```ts 38 | export type Property = Cursor>; 39 | ``` 40 | 41 | So it's just a cursor that only supports `"REPLACE"` actions, and (as you'd expect) its state is the same type that it receives in the replacement payload. 42 | 43 | So with this knowledge, you may be able to predict how `TextInput` works. No cheating! Okay, you can look now: 44 | 45 | ```ts 46 | export interface TextInputProps extends StandardTextInputProps { 47 | binding: Property; 48 | } 49 | 50 | export const TextInput = (props: TextInputProps) => ( 51 | props.binding(replace(e.currentTarget.value))} /> 54 | ); 55 | ``` 56 | 57 | It's just an `` element. Any properties besides `binding` are passed straight through, so you can use it just as you would a plain ``. 58 | 59 | The key to understanding it is to see how it reads and writes values through the `binding` prop. To read, it accesses `state`, which contains the current value of the string. To write, it calls `binding` as a function, passing it an action built with the `replace` action builder. 60 | 61 | In Immuto, exactly the same Redux concepts (immutable data updated by actions) are applied at the very small scale of individual values, all the way out to the scale of the entire application. 62 | -------------------------------------------------------------------------------- /docs-src/README.md: -------------------------------------------------------------------------------- 1 | # Immuto 2 | 3 | Welcome to Immuto, the TypeScripty way to Redux your React! 4 | 5 | Let's justify these buzzwords in reverse order of appearance. 6 | 7 | ## Why React? 8 | 9 | 10 | If used carefully, React allows you describe your UI as pure functions with no side-effects. This is such a simple, powerful idea, it has made React fabulously popular. One day all UIs will work this way. 11 | 12 | (You don't *have* to use React; the core `immuto` package has no dependencies on it.) 13 | 14 | ## Why Redux? 15 | 16 | 17 | Redux is like React for your data. Instead of tweaking values, you write a pure function called a *reducer* to describe how to make new data from old. The instruction for how to make the new data different from the old is called an *action*. 18 | 19 | You make a single immutable data structure to contain all the data that powers your app. You make one giant reducer function to operate on it. 20 | 21 | Then you hide the current app state inside an object called the *store*, and you send it actions to make it update the state. Only actions can change things and they are all channeled through one entry point. This makes it really hard for things to go wrong, and really easy to see what's happened when they do. 22 | 23 | ## Why TypeScript? 24 | 25 | 26 | TypeScript adds static typing to JavaScript. Your source editor stops being a dumb electric typewriter and becomes an intelligent assistant. The more you tell it about what you're actually trying to do, the more intelligent and helpful it becomes. As you write code, it pops up suggestions, and it tells you when you're about to break your own rules. This all happens at the speed of thought, making your productivity sky-rocket. 27 | 28 | There are other type checking systems for JavaScript, and you may be able to use Immuto with them. TypeScript is recommended partly because it already has such widespread adoption. It's integrated into all the popular source editors, and many major frameworks are written in it. 29 | 30 | (You don't *have* to use TypeScript; Immuto is also useable as a plain JavaScript library.) 31 | 32 | ## Why Immuto? 33 | 34 | 35 | The challenge with Redux is composition. In a large application, how do you break it down into small composable pieces? And how do you fit them back together again? 36 | 37 | Immuto solves this problem, and does so with absolute static type-safety. [Read on to find out how...](getting_started/README.md) 38 | 39 | The name *Immuto* sounds like a magic spell to make your app immutable. But really it's the Latin verb *to change*. Which is what this package is really about: changing things without changing them. 40 | -------------------------------------------------------------------------------- /docs-src/how_it_works/references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | Redux says that to modify your data, you send it actions. Immuto is really some tools to help you structure your data in a hierarchy, and then target actions at the right place. A "parent" object can refer to a "child" object, and sometimes you'll need a way to send actions to the child, and you don't want to have to think about how to channel it through the parent. You need a [cursor](cursors.md) to the child. A reference is a way of obtaining that cursor. 4 | 5 | To set up a reference, we need to know the type of the child, and where to find an instance of the child given a parent. The resulting reference definition is function that, when given a cursor to the parent, returns a cursor to the child. This makes it ideal for passing to the [piping operator](the_piping_operator.md). 6 | 7 | ```ts 8 | interface Human { ... } 9 | 10 | namespace Human { 11 | export const reduce = ...; 12 | } 13 | 14 | interface Pet { 15 | owner: Human; 16 | } 17 | 18 | namespace Pet { 19 | export const owner = reference("OWNER", Human, 20 | (p: Pet) => p.owner); 21 | } 22 | ``` 23 | 24 | Hey presto. We can call `owner` as a function, passing it a cursor to a `Pet`, and we'll get a cursor to that pet's `Human` owner. 25 | 26 | We can then send an action to the `Human` cursor. Behind the scenes, it will go on a boomarang-style there-and-back-again journey. It is automatically wrapped in a forwarding action and submitted to the `Pet` cursor. This wrapping/forwarding process continues back through the chain of cursors until it reaches the store. Then the resulting combined action is dispatched to the store, and that means the action passed back down the hierarchy of reducers, being unwrapped along the way, until it reaches the `Pet`'s reducer where the change is applied. 27 | 28 | As for the declaration itself, there are a few sneaky tricks going on. First, the above `owner` definition is exactly equivalent to: 29 | 30 | ```ts 31 | export const owner = reference("OWNER", Human, 32 | (p: Pet) => p.owner, 33 | (p: Pet, owner: Human) => { amend(p, { owner })}); 34 | ``` 35 | 36 | The additional function is the "setter", responsible for storing a new `Human` in the pet's `owner` field. The first function is the "getter". For simple cases, Immuto can parse the source of the getter and so auto-generate the getter. For more complex ways of finding the referred-to object, you'll have to write your own setter. 37 | 38 | The `Human` argument is actually shorthand for `Human.reduce`, so we could have said: 39 | 40 | ```ts 41 | export const owner = reference("OWNER", Human.reduce, (p: Pet) => p.owner, 42 | (p: Pet, owner: Human) => { amend(p, { owner })}); 43 | ``` 44 | 45 | To use the shorter version, make sure you use the name `reduce` in the namespaces for your types. -------------------------------------------------------------------------------- /docs-src/getting_started/05_a_collection_of_people.md: -------------------------------------------------------------------------------- 1 | # A collection of people 2 | 3 | Now we're going to deal with a whole room of people at once. Intimidating. 4 | 5 | Start a new file `People.ts` and fill it with: 6 | 7 | ```ts 8 | import { reducer, array } from "immuto"; 9 | import { Person } from "./Person"; 10 | 11 | export type People = Person[]; 12 | 13 | export namespace People { 14 | 15 | export const empty: People = []; 16 | export const at = array(Person); 17 | export const reduce = reducer(empty).action(at); 18 | 19 | export type Cursor = typeof reduce.cursorType; 20 | } 21 | ``` 22 | 23 | It's really the same pattern as before. We've given a name to a data structure, only this time it's an array of `Person` instead of some custom interface we've cooked up. 24 | 25 | Then we populate the corresponding namespace with the same useful bits and pieces. Doing it this way means we get to control exactly what operations are available on our collection. 26 | 27 | The one apparent oddity is that instead of declaring properties, we just declare a single thing called `at`, but go with it for now. All will become clear. 28 | 29 | Let's jump straight into the UI. Save this as `PeopleEditor.tsx`: 30 | 31 | ```tsx 32 | import * as React from "react"; 33 | import { People } from "./People"; 34 | import { PersonEditor } from "./PersonEditor"; 35 | 36 | export interface PeopleEditorProps { 37 | people: People.Cursor; 38 | } 39 | 40 | export function PeopleEditor({people}: PeopleEditorProps) { 41 | return ( 42 |
43 | { 44 | people.state.map((person, index) => ( 45 |
46 |

{person.firstName} {person.lastName}

47 | 48 |
49 | )) 50 | } 51 |
52 | ); 53 | } 54 | ``` 55 | 56 | See how we use `People.at`? Again, we're using `people.$(...)` to follow the cursor on to something else. Inside the parentheses we describe the thing we want. Only this time its not a named property, but position in the array, so we pass the position `index`: 57 | 58 | ```ts 59 | people.$(People.at(index)) 60 | ``` 61 | 62 | To see it working, let's modify `index.tsx`, basically replacing `Person` with `People` throughout: 63 | 64 | ```tsx 65 | import * as React from "react"; 66 | import * as ReactDOM from "react-dom"; 67 | import { bindToStore } from "immuto-react"; 68 | import { People } from "./People"; 69 | import { PeopleEditor } from "./PeopleEditor"; 70 | 71 | const store = People.reduce.store(); 72 | 73 | const App = bindToStore(store, p => ); 74 | 75 | ReactDOM.render(, document.querySelector("#root")); 76 | ``` 77 | 78 | There's one other thing we'll need to do, because we don't (yet) have a way to add or remove items from our list. In the next step, we'll dispatch some actions into our store. -------------------------------------------------------------------------------- /docs/how_it_works/how_properties_reference_actions_related.md: -------------------------------------------------------------------------------- 1 | # How properties, references and actions are related 2 | 3 | The fundamental thing all these three have in common is that you can add them to your reducer. 4 | 5 | ```ts 6 | const reduce = reducer(empty) 7 | .action(someProperty1) 8 | .action(someReference2) 9 | .action(someAction3) 10 | ``` 11 | 12 | This is because they each define a single action. As far as `Reducer#action` is concerned, it just requires that you pass it an object with two properties: 13 | 14 | ```ts 15 | interface ActionDefinition { 16 | readonly type: T; 17 | reduce(state: S, payload: P): S; 18 | } 19 | ``` 20 | 21 | Here `reduce` is simpler than a full reducer function. It's a *partial* reducer: it only gets the payload, because the `type` is already guaranteed to be whatever the definition says it accepts. The combined `reducer` just forwards actions to the correct partial reducer. 22 | 23 | ## Actions 24 | 25 | When you declare a plain `action`: 26 | 27 | ```ts 28 | export const initialize = action("INITIALIZE", 29 | (state: People, payload: People) => payload); 30 | ``` 31 | 32 | this is made completely explicit, because you have to supply the `type` and `reduce` functions yourself. Internally an action creator function is built (this is pretty trivial: accept a `payload` and wrap it in an action alongside `type`), and then the `type` and `reduce` properties are added directly onto the action creator. So in the above example, `initialize` is an action creator function. 33 | 34 | ## References 35 | 36 | References are all about forwarding to another object. So you have to tell them what type they forward to, and where to find an instance of it. In return, a reference gives you a way to wrap action 37 | 38 | ```ts 39 | interface Human { ... } 40 | 41 | namespace Human { ... } 42 | 43 | interface Pet { 44 | owner: Human; 45 | } 46 | 47 | namespace Pet { 48 | export const owner = reference("OWNER", Human, (p: Pet) => p.owner); 49 | } 50 | ``` 51 | 52 | There's a few tricks going on here. First, it's exactly equivalent to: 53 | 54 | ```ts 55 | export const owner = reference("OWNER", Human, (p: Pet) => p.owner, 56 | (p: Pet, owner: Human) => { amend(p, { owner })}); 57 | ``` 58 | 59 | The additional function is the "setter", responsible for storing a new `Human` in the pet's `owner` field. The first function is the "getter". For simple cases, Immuto can parse the source of the getter and so auto-generate the getter. For more complex ways of finding the referred-to object, you'll have to write your own setter. 60 | 61 | The `Human` argument is actually shorthand for `Human.reduce`, so we could have said: 62 | 63 | ```ts 64 | export const owner = reference("OWNER", Human.reduce, (p: Pet) => p.owner, 65 | (p: Pet, owner: Human) => { amend(p, { owner })}); 66 | ``` 67 | 68 | Therefore, to use the shorter version, make sure you use the name `reduce` in the namespaces for your types. 69 | 70 | -------------------------------------------------------------------------------- /docs-src/how_it_works/how_properties_reference_actions_related.md: -------------------------------------------------------------------------------- 1 | # How properties, references and actions are related 2 | 3 | The fundamental thing all these three have in common is that you can add them to your reducer. 4 | 5 | ```ts 6 | const reduce = reducer(empty) 7 | .action(someProperty1) 8 | .action(someReference2) 9 | .action(someAction3) 10 | ``` 11 | 12 | This is because they each define a single action. As far as `Reducer#action` is concerned, it just requires that you pass it an object with two properties: 13 | 14 | ```ts 15 | interface ActionDefinition { 16 | readonly type: T; 17 | reduce(state: S, payload: P): S; 18 | } 19 | ``` 20 | 21 | Here `reduce` is simpler than a full reducer function. It's a *partial* reducer: it only gets the payload, because the `type` is already guaranteed to be whatever the definition says it accepts. The combined `reducer` just forwards actions to the correct partial reducer. 22 | 23 | ## Actions 24 | 25 | When you declare a plain `action`: 26 | 27 | ```ts 28 | export const initialize = action("INITIALIZE", 29 | (state: People, payload: People) => payload); 30 | ``` 31 | 32 | this is made completely explicit, because you have to supply the `type` and `reduce` functions yourself. Internally an action creator function is built (this is pretty trivial: accept a `payload` and wrap it in an action alongside `type`), and then the `type` and `reduce` properties are added directly onto the action creator. So in the above example, `initialize` is an action creator function. 33 | 34 | ## References 35 | 36 | References are all about forwarding to another object. So you have to tell them what type they forward to, and where to find an instance of it. In return, a reference gives you a way to wrap action 37 | 38 | ```ts 39 | interface Human { ... } 40 | 41 | namespace Human { ... } 42 | 43 | interface Pet { 44 | owner: Human; 45 | } 46 | 47 | namespace Pet { 48 | export const owner = reference("OWNER", Human, (p: Pet) => p.owner); 49 | } 50 | ``` 51 | 52 | There's a few tricks going on here. First, it's exactly equivalent to: 53 | 54 | ```ts 55 | export const owner = reference("OWNER", Human, (p: Pet) => p.owner, 56 | (p: Pet, owner: Human) => { amend(p, { owner })}); 57 | ``` 58 | 59 | The additional function is the "setter", responsible for storing a new `Human` in the pet's `owner` field. The first function is the "getter". For simple cases, Immuto can parse the source of the getter and so auto-generate the getter. For more complex ways of finding the referred-to object, you'll have to write your own setter. 60 | 61 | The `Human` argument is actually shorthand for `Human.reduce`, so we could have said: 62 | 63 | ```ts 64 | export const owner = reference("OWNER", Human.reduce, (p: Pet) => p.owner, 65 | (p: Pet, owner: Human) => { amend(p, { owner })}); 66 | ``` 67 | 68 | Therefore, to use the shorter version, make sure you use the name `reduce` in the namespaces for your types. 69 | 70 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-highlight/ebook.css: -------------------------------------------------------------------------------- 1 | pre, 2 | code { 3 | /* http://jmblog.github.io/color-themes-for-highlightjs */ 4 | /* Tomorrow Comment */ 5 | /* Tomorrow Red */ 6 | /* Tomorrow Orange */ 7 | /* Tomorrow Yellow */ 8 | /* Tomorrow Green */ 9 | /* Tomorrow Aqua */ 10 | /* Tomorrow Blue */ 11 | /* Tomorrow Purple */ 12 | } 13 | pre .hljs-comment, 14 | code .hljs-comment, 15 | pre .hljs-title, 16 | code .hljs-title { 17 | color: #8e908c; 18 | } 19 | pre .hljs-variable, 20 | code .hljs-variable, 21 | pre .hljs-attribute, 22 | code .hljs-attribute, 23 | pre .hljs-tag, 24 | code .hljs-tag, 25 | pre .hljs-regexp, 26 | code .hljs-regexp, 27 | pre .hljs-deletion, 28 | code .hljs-deletion, 29 | pre .ruby .hljs-constant, 30 | code .ruby .hljs-constant, 31 | pre .xml .hljs-tag .hljs-title, 32 | code .xml .hljs-tag .hljs-title, 33 | pre .xml .hljs-pi, 34 | code .xml .hljs-pi, 35 | pre .xml .hljs-doctype, 36 | code .xml .hljs-doctype, 37 | pre .html .hljs-doctype, 38 | code .html .hljs-doctype, 39 | pre .css .hljs-id, 40 | code .css .hljs-id, 41 | pre .css .hljs-class, 42 | code .css .hljs-class, 43 | pre .css .hljs-pseudo, 44 | code .css .hljs-pseudo { 45 | color: #c82829; 46 | } 47 | pre .hljs-number, 48 | code .hljs-number, 49 | pre .hljs-preprocessor, 50 | code .hljs-preprocessor, 51 | pre .hljs-pragma, 52 | code .hljs-pragma, 53 | pre .hljs-built_in, 54 | code .hljs-built_in, 55 | pre .hljs-literal, 56 | code .hljs-literal, 57 | pre .hljs-params, 58 | code .hljs-params, 59 | pre .hljs-constant, 60 | code .hljs-constant { 61 | color: #f5871f; 62 | } 63 | pre .ruby .hljs-class .hljs-title, 64 | code .ruby .hljs-class .hljs-title, 65 | pre .css .hljs-rules .hljs-attribute, 66 | code .css .hljs-rules .hljs-attribute { 67 | color: #eab700; 68 | } 69 | pre .hljs-string, 70 | code .hljs-string, 71 | pre .hljs-value, 72 | code .hljs-value, 73 | pre .hljs-inheritance, 74 | code .hljs-inheritance, 75 | pre .hljs-header, 76 | code .hljs-header, 77 | pre .hljs-addition, 78 | code .hljs-addition, 79 | pre .ruby .hljs-symbol, 80 | code .ruby .hljs-symbol, 81 | pre .xml .hljs-cdata, 82 | code .xml .hljs-cdata { 83 | color: #718c00; 84 | } 85 | pre .css .hljs-hexcolor, 86 | code .css .hljs-hexcolor { 87 | color: #3e999f; 88 | } 89 | pre .hljs-function, 90 | code .hljs-function, 91 | pre .python .hljs-decorator, 92 | code .python .hljs-decorator, 93 | pre .python .hljs-title, 94 | code .python .hljs-title, 95 | pre .ruby .hljs-function .hljs-title, 96 | code .ruby .hljs-function .hljs-title, 97 | pre .ruby .hljs-title .hljs-keyword, 98 | code .ruby .hljs-title .hljs-keyword, 99 | pre .perl .hljs-sub, 100 | code .perl .hljs-sub, 101 | pre .javascript .hljs-title, 102 | code .javascript .hljs-title, 103 | pre .coffeescript .hljs-title, 104 | code .coffeescript .hljs-title { 105 | color: #4271ae; 106 | } 107 | pre .hljs-keyword, 108 | code .hljs-keyword, 109 | pre .javascript .hljs-function, 110 | code .javascript .hljs-function { 111 | color: #8959a8; 112 | } 113 | pre .hljs, 114 | code .hljs { 115 | display: block; 116 | background: white; 117 | color: #4d4d4c; 118 | padding: 0.5em; 119 | } 120 | pre .coffeescript .javascript, 121 | code .coffeescript .javascript, 122 | pre .javascript .xml, 123 | code .javascript .xml, 124 | pre .tex .hljs-formula, 125 | code .tex .hljs-formula, 126 | pre .xml .javascript, 127 | code .xml .javascript, 128 | pre .xml .vbscript, 129 | code .xml .vbscript, 130 | pre .xml .css, 131 | code .xml .css, 132 | pre .xml .hljs-cdata, 133 | code .xml .hljs-cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /docs-src/getting_started/03_making_the_ui.md: -------------------------------------------------------------------------------- 1 | # Making the UI 2 | 3 | Start a new file and call it `PersonEditor.tsx`, so we can use JSX in our TypeScript. We'll need to import React of course, and our `Person` model. We'll also use some components from `immuto-react`: 4 | 5 | ```ts 6 | import * as React from "react"; 7 | import { Person } from "./Person"; 8 | import { TextInput, CheckBox } from "immuto-react"; 9 | ``` 10 | 11 | In Immuto all the model data is passed in through props, so let's declare the type of our props in TypeScript. There's that mysterious `Cursor` type we declared in the previous step: 12 | 13 | ```ts 14 | export interface PersonEditorProps { 15 | person: Person.Cursor; 16 | } 17 | ``` 18 | 19 | Now we can declare our stateless component for editing a `Person`. We use destructuring to get the `person` prop into a simple named variable: 20 | 21 | ```tsx 22 | export function PersonEditor({person}: PersonEditorProps) { 23 | return ( 24 |
25 |
26 | Person 27 | 29 | 31 | 32 |
33 |
34 | Summary 35 | {person.state.firstName} {person.state.lastName} 36 | : {person.state.trusted ? "Trusted" : "Not trusted"} 37 |
38 |
39 | ); 40 | } 41 | ``` 42 | 43 | By the way, you don't have to use `TextInput` and `CheckBox`. They are just thin wrappers around `` that make binding very succinct - [more about that here](../how_it_works/how_textinput_works.md). 44 | 45 | `TextInput` requires a `binding` to "talk to". Turns out we can make just what it requires by using the `firstName` property we defined in our model. Note the simple pattern for looking up a property inside a cursor. 46 | 47 | ```ts 48 | cursorToObject.$(TypeName.propertyName) 49 | ``` 50 | 51 | On a cursor the `.$(...)` is called the piping operator. This is logically similar to getting a property from an ordinary object: 52 | 53 | ```ts 54 | objectInstance.propertyName 55 | ``` 56 | 57 | If you're into the whole brevity thing, you can make it more succinct in the JSX by destructuring the properties first: 58 | 59 | ```tsx 60 | const { firstName, lastName, trusted } = Person; 61 | ``` 62 | 63 | Which means you can just say: 64 | 65 | ``` 66 | 67 | ``` 68 | 69 | Why does it have to work in this special way? Because we're not traversing through simple references between objects. `person` is a cursor, and so is `person.$(Person.firstName)`. This is what gives us the power to make changes to the data, even though it's represented immutably. 70 | 71 | Save this. One last thing we need to do is change `index.tsx`, which currently renders `Hello, world!`. So open it up and replace the content with: 72 | 73 | ```ts 74 | import * as React from "react"; 75 | import { bindToStore } from "immuto-react"; 76 | import { Person } from "./Person"; 77 | import { PersonEditor } from "./PersonEditor"; 78 | ``` 79 | 80 | Under the imports, add the following: 81 | 82 | ```ts 83 | const store = Person.reduce.store(); 84 | 85 | const App = bindToStore(store, p => ); 86 | ``` 87 | 88 | The first line creates a store to hold the current state of our `Person`. Behind the scenes it calls the `createStore` function from Redux, but this way it's given more strict static typing. 89 | 90 | The second line creates a new component `App` that requires no props, and which renders `PersonEditor` using the current content of the store. This means it re-renders whenever the store changes. 91 | 92 | Finally, put back the call to `ReactDOM.render` so it uses the new `App` component: 93 | 94 | ```ts 95 | ReactDOM.render(, document.querySelector("#root")); 96 | ``` 97 | 98 | Save this too. Rebuild everything: 99 | 100 | ```bash 101 | $ webpack 102 | ``` 103 | 104 | Now open `index.html` in your browser and have fun entering your own name, or that of a celebrity, and deciding whether you trust them. The possibilities are limited! 105 | -------------------------------------------------------------------------------- /docs-src/getting_started/06_dispatching_some_people.md: -------------------------------------------------------------------------------- 1 | # Dispatching some people 2 | 3 | Here's one way to set the `firstName` property of a person in position `0`. It's pure Redux, although the action format has a fussy structure: 4 | 5 | ```ts 6 | store.dispatch({ 7 | type: "AT", 8 | payload: { 9 | key: 0, 10 | action: { 11 | type: "FIRST_NAME", 12 | payload: { 13 | type: "REPLACE", 14 | payload: "John" 15 | } 16 | } 17 | } 18 | }); 19 | ``` 20 | 21 | See how it's nested? Actions inside actions. Each action has `type` and `payload`, and sometimes the `payload` contains a further action. We call this an **action path**. It just naturally emerges from the structure of the data model. 22 | 23 | Try it in `index.tsx`. Also, try changing `"John"` into `3.14` so it's the wrong type. Or misspell one of the action names. TypeScript won't let you do that. 24 | 25 | We can do it with cursors instead. Add these to your imports: 26 | 27 | ```ts 28 | import { snapshot, replace } from "immuto"; 29 | import { Person } from "./Person"; 30 | ``` 31 | 32 | And try: 33 | 34 | ```ts 35 | snapshot(store) 36 | .$(People.at(0)) 37 | .$(Person.lastName) 38 | (replace("Zoidberg")); 39 | ``` 40 | 41 | The `snapshot` function gets a cursor for the current state of the store. It's the Immuto way of doing `store.getState()`, except it also retains the power of `dispatch`. As it's a cursor, we can then take the path of our choice down the data structure. 42 | 43 | The result is a function that we can directly call to dispatch an action. It *is* a custom dispatch function. `replace` is a good old action creator, just like in Redux (remember: Immuto *is* Redux). So we we could have said: 44 | 45 | ```ts 46 | snapshot(store) 47 | .$(People.at(0)) 48 | .$(Person.lastName) 49 | ({ type: "REPLACE", payload: "Zoidberg" }); 50 | ``` 51 | 52 | Again, any typo such as `type: "REPLOCE"` is caught instantly. And try piping in the wrong order: 53 | 54 | ```ts 55 | snapshot(store).$(Person.firstName) // error: Person[] is not assignable to Person 56 | ``` 57 | 58 | Our store's internal state is of type `People` so it's just a plain ol' array of objects: 59 | 60 | ```ts 61 | const example: People = [ 62 | { firstName: "Homer", lastName: "Simpson", trusted: false }, 63 | { firstName: "Marge", lastName: "Simpson", trusted: true }, 64 | { firstName: "Bart", lastName: "Simpson", trusted: false }, 65 | { firstName: "List", lastName: "Simpson", trusted: true }, 66 | { firstName: "Maggie", lastName: "Simpson", trusted: false }, 67 | ] 68 | ``` 69 | 70 | So I guess we could copy it into the store like this: 71 | 72 | ``` 73 | for (let p = 0; p < example.length; p++) { 74 | const person = snapshot(store).$(People.at(p)); 75 | 76 | person.$(Person.firstName)(replace(example[p].firstName)); 77 | person.$(Person.lastName)(replace(example[p].lastName)); 78 | person.$(Person.trusted)(replace(example[p].trusted)); 79 | } 80 | ``` 81 | 82 | On the other hand, it would be nice if we could just throw the whole `example` straight in the store. Let's do something completely off-the-wall and define an action! Go into `People.ts` and fix your `immuto` import so it says: 83 | 84 | ```ts 85 | import { reducer, array, action } from "immuto"; 86 | ``` 87 | 88 | Then between the `at` and `reduce` declarations, add: 89 | 90 | ```ts 91 | export const initialize = action("INITIALIZE", 92 | (state: People, payload: People) => payload); 93 | ``` 94 | 95 | See how we have to write a kind a partial reducer? We just get given the current `state` and the `payload` of our action. The `state` needs to be the right type for the object, in this case `People`, but the type of `payload` could be anything we want (usually something JSON-compatible though. In this case we set the payload to be of type `People` so we can simply return it as the new state, throwing away the old state. 96 | 97 | Finally, tack it on to the reducer: 98 | 99 | ```ts 100 | export const reduce = reducer(empty) 101 | .action(at) 102 | .action(initialize); 103 | ``` 104 | 105 | With this added ingredient, all we need in `index.tsx` is: 106 | 107 | ```ts 108 | store.dispatch(People.initialize(example)); 109 | ``` 110 | 111 | `People.initialize` is an action creator. We just pass it the payload. Again, to be super clear, we could have said: 112 | 113 | ```ts 114 | store.dispatch({ type: "INITIALIZE", payload: example }); 115 | ``` 116 | 117 | So in summary, this is all `index.tsx` now needs to contain: 118 | 119 | ```tsx 120 | import * as React from "react"; 121 | import * as ReactDOM from "react-dom"; 122 | import { bindToStore } from "immuto-react"; 123 | import { People } from "./People"; 124 | import { PeopleEditor } from "./PeopleEditor"; 125 | 126 | const store = People.reduce.store(); 127 | 128 | const App = bindToStore(store, p => ); 129 | 130 | ReactDOM.render(, document.querySelector("#root")); 131 | 132 | const example: People = [ 133 | { firstName: "Homer", lastName: "Simpson", trusted: false }, 134 | { firstName: "Marge", lastName: "Simpson", trusted: true }, 135 | { firstName: "Bart", lastName: "Simpson", trusted: false }, 136 | { firstName: "Lisa", lastName: "Simpson", trusted: true }, 137 | { firstName: "Maggie", lastName: "Simpson", trusted: false }, 138 | ] 139 | 140 | store.dispatch({ type: "INITIALIZE", payload: example }); 141 | ``` 142 | 143 | Let's give it a whirl: 144 | 145 | ```bash 146 | $ webpack 147 | ``` 148 | 149 | Open `index.html` in your browser and try changing the names of the Simpsons family. It's really awesome. 150 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-search/search.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | var MAX_RESULTS = 15; 6 | var MAX_DESCRIPTION_SIZE = 500; 7 | 8 | var usePushState = (typeof history.pushState !== 'undefined'); 9 | 10 | // DOM Elements 11 | var $body = $('body'); 12 | var $bookSearchResults; 13 | var $searchInput; 14 | var $searchList; 15 | var $searchTitle; 16 | var $searchResultsCount; 17 | var $searchQuery; 18 | 19 | // Throttle search 20 | function throttle(fn, wait) { 21 | var timeout; 22 | 23 | return function() { 24 | var ctx = this, args = arguments; 25 | if (!timeout) { 26 | timeout = setTimeout(function() { 27 | timeout = null; 28 | fn.apply(ctx, args); 29 | }, wait); 30 | } 31 | }; 32 | } 33 | 34 | function displayResults(res) { 35 | $bookSearchResults.addClass('open'); 36 | 37 | var noResults = res.count == 0; 38 | $bookSearchResults.toggleClass('no-results', noResults); 39 | 40 | // Clear old results 41 | $searchList.empty(); 42 | 43 | // Display title for research 44 | $searchResultsCount.text(res.count); 45 | $searchQuery.text(res.query); 46 | 47 | // Create an
  • element for each result 48 | res.results.forEach(function(res) { 49 | var $li = $('
  • ', { 50 | 'class': 'search-results-item' 51 | }); 52 | 53 | var $title = $('

    '); 54 | 55 | var $link = $('', { 56 | 'href': gitbook.state.basePath + '/' + res.url, 57 | 'text': res.title 58 | }); 59 | 60 | var content = res.body.trim(); 61 | if (content.length > MAX_DESCRIPTION_SIZE) { 62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; 63 | } 64 | var $content = $('

    ').html(content); 65 | 66 | $link.appendTo($title); 67 | $title.appendTo($li); 68 | $content.appendTo($li); 69 | $li.appendTo($searchList); 70 | }); 71 | } 72 | 73 | function launchSearch(q) { 74 | // Add class for loading 75 | $body.addClass('with-search'); 76 | $body.addClass('search-loading'); 77 | 78 | // Launch search query 79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS) 80 | .then(function(results) { 81 | displayResults(results); 82 | }) 83 | .always(function() { 84 | $body.removeClass('search-loading'); 85 | }), 1000); 86 | } 87 | 88 | function closeSearch() { 89 | $body.removeClass('with-search'); 90 | $bookSearchResults.removeClass('open'); 91 | } 92 | 93 | function launchSearchFromQueryString() { 94 | var q = getParameterByName('q'); 95 | if (q && q.length > 0) { 96 | // Update search input 97 | $searchInput.val(q); 98 | 99 | // Launch search 100 | launchSearch(q); 101 | } 102 | } 103 | 104 | function bindSearch() { 105 | // Bind DOM 106 | $searchInput = $('#book-search-input input'); 107 | $bookSearchResults = $('#book-search-results'); 108 | $searchList = $bookSearchResults.find('.search-results-list'); 109 | $searchTitle = $bookSearchResults.find('.search-results-title'); 110 | $searchResultsCount = $searchTitle.find('.search-results-count'); 111 | $searchQuery = $searchTitle.find('.search-query'); 112 | 113 | // Launch query based on input content 114 | function handleUpdate() { 115 | var q = $searchInput.val(); 116 | 117 | if (q.length == 0) { 118 | closeSearch(); 119 | } 120 | else { 121 | launchSearch(q); 122 | } 123 | } 124 | 125 | // Detect true content change in search input 126 | // Workaround for IE < 9 127 | var propertyChangeUnbound = false; 128 | $searchInput.on('propertychange', function(e) { 129 | if (e.originalEvent.propertyName == 'value') { 130 | handleUpdate(); 131 | } 132 | }); 133 | 134 | // HTML5 (IE9 & others) 135 | $searchInput.on('input', function(e) { 136 | // Unbind propertychange event for IE9+ 137 | if (!propertyChangeUnbound) { 138 | $(this).unbind('propertychange'); 139 | propertyChangeUnbound = true; 140 | } 141 | 142 | handleUpdate(); 143 | }); 144 | 145 | // Push to history on blur 146 | $searchInput.on('blur', function(e) { 147 | // Update history state 148 | if (usePushState) { 149 | var uri = updateQueryString('q', $(this).val()); 150 | history.pushState({ path: uri }, null, uri); 151 | } 152 | }); 153 | } 154 | 155 | gitbook.events.on('page.change', function() { 156 | bindSearch(); 157 | closeSearch(); 158 | 159 | // Launch search based on query parameter 160 | if (gitbook.search.isInitialized()) { 161 | launchSearchFromQueryString(); 162 | } 163 | }); 164 | 165 | gitbook.events.on('search.ready', function() { 166 | bindSearch(); 167 | 168 | // Launch search from query param at start 169 | launchSearchFromQueryString(); 170 | }); 171 | 172 | function getParameterByName(name) { 173 | var url = window.location.href; 174 | name = name.replace(/[\[\]]/g, '\\$&'); 175 | var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i'), 176 | results = regex.exec(url); 177 | if (!results) return null; 178 | if (!results[2]) return ''; 179 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 180 | } 181 | 182 | function updateQueryString(key, value) { 183 | value = encodeURIComponent(value); 184 | 185 | var url = window.location.href; 186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), 187 | hash; 188 | 189 | if (re.test(url)) { 190 | if (typeof value !== 'undefined' && value !== null) 191 | return url.replace(re, '$1' + key + '=' + value + '$2$3'); 192 | else { 193 | hash = url.split('#'); 194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 196 | url += '#' + hash[1]; 197 | return url; 198 | } 199 | } 200 | else { 201 | if (typeof value !== 'undefined' && value !== null) { 202 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 203 | hash = url.split('#'); 204 | url = hash[0] + separator + key + '=' + value; 205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 206 | url += '#' + hash[1]; 207 | return url; 208 | } 209 | else 210 | return url; 211 | } 212 | } 213 | }); 214 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-fontsettings/fontsettings.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | // Configuration 3 | var MAX_SIZE = 4, 4 | MIN_SIZE = 0, 5 | BUTTON_ID; 6 | 7 | // Current fontsettings state 8 | var fontState; 9 | 10 | // Default themes 11 | var THEMES = [ 12 | { 13 | config: 'white', 14 | text: 'White', 15 | id: 0 16 | }, 17 | { 18 | config: 'sepia', 19 | text: 'Sepia', 20 | id: 1 21 | }, 22 | { 23 | config: 'night', 24 | text: 'Night', 25 | id: 2 26 | } 27 | ]; 28 | 29 | // Default font families 30 | var FAMILIES = [ 31 | { 32 | config: 'serif', 33 | text: 'Serif', 34 | id: 0 35 | }, 36 | { 37 | config: 'sans', 38 | text: 'Sans', 39 | id: 1 40 | } 41 | ]; 42 | 43 | // Return configured themes 44 | function getThemes() { 45 | return THEMES; 46 | } 47 | 48 | // Modify configured themes 49 | function setThemes(themes) { 50 | THEMES = themes; 51 | updateButtons(); 52 | } 53 | 54 | // Return configured font families 55 | function getFamilies() { 56 | return FAMILIES; 57 | } 58 | 59 | // Modify configured font families 60 | function setFamilies(families) { 61 | FAMILIES = families; 62 | updateButtons(); 63 | } 64 | 65 | // Save current font settings 66 | function saveFontSettings() { 67 | gitbook.storage.set('fontState', fontState); 68 | update(); 69 | } 70 | 71 | // Increase font size 72 | function enlargeFontSize(e) { 73 | e.preventDefault(); 74 | if (fontState.size >= MAX_SIZE) return; 75 | 76 | fontState.size++; 77 | saveFontSettings(); 78 | } 79 | 80 | // Decrease font size 81 | function reduceFontSize(e) { 82 | e.preventDefault(); 83 | if (fontState.size <= MIN_SIZE) return; 84 | 85 | fontState.size--; 86 | saveFontSettings(); 87 | } 88 | 89 | // Change font family 90 | function changeFontFamily(configName, e) { 91 | if (e && e instanceof Event) { 92 | e.preventDefault(); 93 | } 94 | 95 | var familyId = getFontFamilyId(configName); 96 | fontState.family = familyId; 97 | saveFontSettings(); 98 | } 99 | 100 | // Change type of color theme 101 | function changeColorTheme(configName, e) { 102 | if (e && e instanceof Event) { 103 | e.preventDefault(); 104 | } 105 | 106 | var $book = gitbook.state.$book; 107 | 108 | // Remove currently applied color theme 109 | if (fontState.theme !== 0) 110 | $book.removeClass('color-theme-'+fontState.theme); 111 | 112 | // Set new color theme 113 | var themeId = getThemeId(configName); 114 | fontState.theme = themeId; 115 | if (fontState.theme !== 0) 116 | $book.addClass('color-theme-'+fontState.theme); 117 | 118 | saveFontSettings(); 119 | } 120 | 121 | // Return the correct id for a font-family config key 122 | // Default to first font-family 123 | function getFontFamilyId(configName) { 124 | // Search for plugin configured font family 125 | var configFamily = $.grep(FAMILIES, function(family) { 126 | return family.config == configName; 127 | })[0]; 128 | // Fallback to default font family 129 | return (!!configFamily)? configFamily.id : 0; 130 | } 131 | 132 | // Return the correct id for a theme config key 133 | // Default to first theme 134 | function getThemeId(configName) { 135 | // Search for plugin configured theme 136 | var configTheme = $.grep(THEMES, function(theme) { 137 | return theme.config == configName; 138 | })[0]; 139 | // Fallback to default theme 140 | return (!!configTheme)? configTheme.id : 0; 141 | } 142 | 143 | function update() { 144 | var $book = gitbook.state.$book; 145 | 146 | $('.font-settings .font-family-list li').removeClass('active'); 147 | $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active'); 148 | 149 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, ''); 150 | $book.addClass('font-size-'+fontState.size); 151 | $book.addClass('font-family-'+fontState.family); 152 | 153 | if(fontState.theme !== 0) { 154 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, ''); 155 | $book.addClass('color-theme-'+fontState.theme); 156 | } 157 | } 158 | 159 | function init(config) { 160 | // Search for plugin configured font family 161 | var configFamily = getFontFamilyId(config.family), 162 | configTheme = getThemeId(config.theme); 163 | 164 | // Instantiate font state object 165 | fontState = gitbook.storage.get('fontState', { 166 | size: config.size || 2, 167 | family: configFamily, 168 | theme: configTheme 169 | }); 170 | 171 | update(); 172 | } 173 | 174 | function updateButtons() { 175 | // Remove existing fontsettings buttons 176 | if (!!BUTTON_ID) { 177 | gitbook.toolbar.removeButton(BUTTON_ID); 178 | } 179 | 180 | // Create buttons in toolbar 181 | BUTTON_ID = gitbook.toolbar.createButton({ 182 | icon: 'fa fa-font', 183 | label: 'Font Settings', 184 | className: 'font-settings', 185 | dropdown: [ 186 | [ 187 | { 188 | text: 'A', 189 | className: 'font-reduce', 190 | onClick: reduceFontSize 191 | }, 192 | { 193 | text: 'A', 194 | className: 'font-enlarge', 195 | onClick: enlargeFontSize 196 | } 197 | ], 198 | $.map(FAMILIES, function(family) { 199 | family.onClick = function(e) { 200 | return changeFontFamily(family.config, e); 201 | }; 202 | 203 | return family; 204 | }), 205 | $.map(THEMES, function(theme) { 206 | theme.onClick = function(e) { 207 | return changeColorTheme(theme.config, e); 208 | }; 209 | 210 | return theme; 211 | }) 212 | ] 213 | }); 214 | } 215 | 216 | // Init configuration at start 217 | gitbook.events.bind('start', function(e, config) { 218 | var opts = config.fontsettings; 219 | 220 | // Generate buttons at start 221 | updateButtons(); 222 | 223 | // Init current settings 224 | init(opts); 225 | }); 226 | 227 | // Expose API 228 | gitbook.fontsettings = { 229 | enlargeFontSize: enlargeFontSize, 230 | reduceFontSize: reduceFontSize, 231 | setTheme: changeColorTheme, 232 | setFamily: changeFontFamily, 233 | getThemes: getThemes, 234 | setThemes: setThemes, 235 | getFamilies: getFamilies, 236 | setFamilies: setFamilies 237 | }; 238 | }); 239 | 240 | 241 | -------------------------------------------------------------------------------- /spec/immutoSpec.ts: -------------------------------------------------------------------------------- 1 | import { snapshot, amend, Store, replace } from "../index"; 2 | 3 | import { Book, Product, Food } from "./book"; 4 | import { Shelf, Books } from "./shelf"; 5 | import { Shop, Shelves } from "./shop"; 6 | import { Founder } from "./founder"; 7 | 8 | const enableLogging = false; 9 | 10 | function logStore(store: Store) { 11 | if (enableLogging) { 12 | store.subscribe(() => { 13 | console.log(""); 14 | console.log(JSON.stringify(store.getState())); 15 | console.log(""); 16 | }); 17 | } 18 | return store; 19 | } 20 | 21 | describe("amend", () => { 22 | 23 | it("works with plain objects", () => { 24 | 25 | const o1 = { x: 5, y: 2 }; 26 | const o2 = amend(o1, { z: o1.x + o1.y }); 27 | 28 | expect(o2.x).toEqual(5); 29 | expect(o2.y).toEqual(2); 30 | expect(o2.z).toEqual(7); 31 | }); 32 | 33 | it("works with arrays", () => { 34 | 35 | const o1 = [5, 2]; 36 | const o2 = amend(o1, { z: o1[0] + o1[1] }); 37 | 38 | expect(o2[0]).toEqual(5); 39 | expect(o2[1]).toEqual(2); 40 | expect(o2.z).toEqual(7); 41 | }); 42 | }); 43 | 44 | describe("Immuto", () => { 45 | 46 | it("has an initial state available via cursor", () => { 47 | 48 | { 49 | const store = Shop.reduce.store(); 50 | 51 | const shelves = snapshot(store).$(Shop.shelves); 52 | const shelf = shelves.$(Shelves.at("fiction")); 53 | const books = shelf.$(Shelf.books); 54 | const book = books.$(Books.at(109423)); 55 | book(Book.setTitle("1985")); 56 | 57 | expect(store.getState().shelves["fiction"].books[109423].title).toEqual("1985"); 58 | } 59 | 60 | const book = snapshot(logStore(Book.reduce.store())).state; 61 | 62 | expect(book.title).toEqual(""); 63 | expect(book.price).toEqual(0); 64 | expect(book.authors.length).toEqual(0); 65 | 66 | expectJson(book, `{"title":"","price":0,"authors":[]}`); 67 | }); 68 | 69 | it("can be updated via cursors", () => { 70 | 71 | const store = logStore(Shelf.reduce.store()); 72 | 73 | const shelf1 = snapshot(store); 74 | const shelf2 = shelf1(Shelf.setDescription("Romance")); 75 | 76 | expectJson(shelf2.state, `{"description":"Romance","books":{}}`); 77 | 78 | const book1 = shelf2.$(Shelf.books).$(Books.at(1001))(Book.setTitle("1985")); 79 | 80 | expectJson(store.getState(), `{"description":"Romance","books":{"1001":{"title":"1985","price":0,"authors":[]}}}`); 81 | 82 | expect(book1.state.title).toEqual("1985"); 83 | expect(book1.state.price).toEqual(0); 84 | 85 | const book2 = book1(Product.setPrice(5.99)); 86 | 87 | expectJson(store.getState(), `{"description":"Romance","books":{"1001":{"title":"1985","price":5.99,"authors":[]}}}`); 88 | 89 | expect(book2.state.price).toEqual(5.99); 90 | 91 | const book3 = book1(Book.addAuthor("Fred Orwell")); 92 | 93 | expectJson(store.getState(), `{"description":"Romance","books":{"1001":{"title":"1985","price":5.99,"authors":["Fred Orwell"]}}}`); 94 | 95 | expect(book3.state.price).toEqual(5.99); 96 | expect(book3.state.authors[0]).toEqual("Fred Orwell"); 97 | }); 98 | 99 | it("supports references and cursors through them", () => { 100 | 101 | const store = logStore(Founder.reduce.store()); 102 | 103 | const founder1 = snapshot(store)(Founder.setName("Samuel J. Borders")); 104 | 105 | const shop1 = Founder.shop(founder1); 106 | 107 | shop1(Shop.setName("Borders")); 108 | 109 | expect(store.getState().name).toEqual("Samuel J. Borders"); 110 | expect(store.getState().shop.name).toEqual("Borders"); 111 | }); 112 | 113 | it("supports removing items from collections", () => { 114 | 115 | const store = logStore(Shelf.reduce.store()); 116 | 117 | const shelf1 = snapshot(store)(Shelf.setDescription("Romance")); 118 | 119 | expectJson(shelf1.state, `{"description":"Romance","books":{}}`); 120 | 121 | shelf1.$(Shelf.books).$(Books.at(1001))(Book.setTitle("1985")); 122 | 123 | const books1 = shelf1.$(Shelf.books) 124 | (Books.at.update(1001, Book.setTitle("1985"))) 125 | (Books.at.update(1002, Book.setTitle("Indiana Smith"))) 126 | (Books.at.update(1003, Book.setTitle("Gone With The Runs"))); 127 | 128 | expectJson(books1.state, `{"1001":{"title":"1985","price":0,"authors":[]},"1002":{"title":"Indiana Smith","price":0,"authors":[]},"1003":{"title":"Gone With The Runs","price":0,"authors":[]}}`); 129 | 130 | const books2 = books1(Books.remove(1002)); 131 | 132 | expectJson(books2.state, `{"1001":{"title":"1985","price":0,"authors":[]},"1003":{"title":"Gone With The Runs","price":0,"authors":[]}}`); 133 | }); 134 | 135 | it("supports nested layers of cursors", () => { 136 | 137 | const store = logStore(Shop.reduce.store()); 138 | const shop1 = snapshot(store); 139 | 140 | const shop2 = shop1(Shop.setName("Buy the Book, Inc.")); 141 | 142 | 143 | const advShelf1 = shop2.$(Shop.shelves).$(Shelves.at("ADV")); 144 | expect(advShelf1.state.description).toEqual(""); 145 | 146 | const advShelf2 = advShelf1(Shelf.setDescription("Adventure")); 147 | expect(advShelf2.state.description).toEqual("Adventure"); 148 | 149 | const firstBook1 = advShelf2.$(Shelf.books).$(Books.at(1002))(Book.setTitle("Indiana Smith")); 150 | 151 | expectJson(store.getState(), `{"name":"Buy the Book, Inc.","shelves":{"ADV":{"description":"Adventure","books":{"1002":{"title":"Indiana Smith","price":0,"authors":[]}}}}}`); 152 | 153 | expect(firstBook1.state.title).toEqual("Indiana Smith"); 154 | 155 | const firstBook2 = firstBook1(Product.setPrice(4.99)); 156 | const firstBook3 = firstBook2(Book.addAuthor("Jim Orwell")); 157 | 158 | expectJson(store.getState(), `{"name":"Buy the Book, Inc.","shelves":{"ADV":{"description":"Adventure","books":{"1002":{"title":"Indiana Smith","price":4.99,"authors":["Jim Orwell"]}}}}}`); 159 | 160 | expect(firstBook3.state.title).toEqual("Indiana Smith"); 161 | expect(firstBook3.state.price).toEqual(4.99); 162 | expect(firstBook3.state.authors[0]).toEqual("Jim Orwell"); 163 | }); 164 | 165 | it("supports properties with magic reducers (experimental)", () => { 166 | 167 | const store = logStore(Book.reduce.store()); 168 | 169 | const title1 = Book.title(snapshot(store)); 170 | title1(replace("Star Warts")); 171 | expect(store.getState().title).toEqual("Star Warts"); 172 | 173 | expect(Book.empty.title).toEqual(""); 174 | 175 | const title2 = Book.title(snapshot(store)); 176 | expect(title2.state).toEqual("Star Warts"); 177 | }); 178 | 179 | it("supports $ for piping on cursors", () => { 180 | 181 | const store = logStore(Shop.reduce.store()); 182 | const shop1 = snapshot(store); 183 | 184 | const advShelf1 = shop1 185 | .$(Shop.shelves) 186 | .$(Shelves.at("ADV")) 187 | .$(Shelf.books) 188 | .$(Books.at(123)) 189 | .$(Book.title) 190 | (replace("The Tiger Who Came To Tea")); 191 | 192 | expectJson(store.getState(), `{"name":"","shelves":{"ADV":{"description":"","books":{"123":{"title":"The Tiger Who Came To Tea","price":0,"authors":[]}}}}}`); 193 | }); 194 | 195 | it("supports sub-typing", () => { 196 | 197 | const store = logStore(Book.reduce.store()); 198 | 199 | store.dispatch(Book.setTitle("Fred")); 200 | 201 | const productCursor: Product.Cursor = snapshot(store); 202 | 203 | const p2 = productCursor(Product.setPrice(22)); 204 | 205 | expect(p2.state.price).toEqual(22); 206 | 207 | expect(store.getState().title).toEqual("Fred"); 208 | expect(store.getState().price).toEqual(22); 209 | }); 210 | 211 | }); 212 | 213 | function toCanonicalJsonStream(obj: any, stream: string[]) { 214 | if (obj) { 215 | if (Array.isArray(obj)) { 216 | stream.push('['); 217 | for (var n = 0; n < obj.length; n++) { 218 | if (n != 0) { 219 | stream.push(','); 220 | } 221 | toCanonicalJsonStream(obj[n], stream); 222 | } 223 | stream.push(']'); 224 | return; 225 | } 226 | 227 | if (typeof obj == 'object') { 228 | stream.push('{'); 229 | var keys = Object.keys(obj); 230 | keys.sort(); 231 | keys.forEach((key, i) => { 232 | if (i != 0) { 233 | stream.push(','); 234 | } 235 | stream.push(JSON.stringify(key)); 236 | stream.push(':'); 237 | toCanonicalJsonStream(obj[key], stream); 238 | }); 239 | stream.push('}'); 240 | return; 241 | } 242 | } 243 | stream.push(JSON.stringify(obj)); 244 | } 245 | 246 | export function toCanonicalJson(obj: any) { 247 | var parts: string[] = []; 248 | toCanonicalJsonStream(obj, parts); 249 | return parts.join(''); 250 | } 251 | 252 | export function expectJson(obj: any, json: string) { 253 | expect(toCanonicalJson(obj)).toEqual(toCanonicalJson(JSON.parse(json))); 254 | } 255 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-fontsettings/website.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Theme 1 3 | */ 4 | .color-theme-1 .dropdown-menu { 5 | background-color: #111111; 6 | border-color: #7e888b; 7 | } 8 | .color-theme-1 .dropdown-menu .dropdown-caret .caret-inner { 9 | border-bottom: 9px solid #111111; 10 | } 11 | .color-theme-1 .dropdown-menu .buttons { 12 | border-color: #7e888b; 13 | } 14 | .color-theme-1 .dropdown-menu .button { 15 | color: #afa790; 16 | } 17 | .color-theme-1 .dropdown-menu .button:hover { 18 | color: #73553c; 19 | } 20 | /* 21 | * Theme 2 22 | */ 23 | .color-theme-2 .dropdown-menu { 24 | background-color: #2d3143; 25 | border-color: #272a3a; 26 | } 27 | .color-theme-2 .dropdown-menu .dropdown-caret .caret-inner { 28 | border-bottom: 9px solid #2d3143; 29 | } 30 | .color-theme-2 .dropdown-menu .buttons { 31 | border-color: #272a3a; 32 | } 33 | .color-theme-2 .dropdown-menu .button { 34 | color: #62677f; 35 | } 36 | .color-theme-2 .dropdown-menu .button:hover { 37 | color: #f4f4f5; 38 | } 39 | .book .book-header .font-settings .font-enlarge { 40 | line-height: 30px; 41 | font-size: 1.4em; 42 | } 43 | .book .book-header .font-settings .font-reduce { 44 | line-height: 30px; 45 | font-size: 1em; 46 | } 47 | .book.color-theme-1 .book-body { 48 | color: #704214; 49 | background: #f3eacb; 50 | } 51 | .book.color-theme-1 .book-body .page-wrapper .page-inner section { 52 | background: #f3eacb; 53 | } 54 | .book.color-theme-2 .book-body { 55 | color: #bdcadb; 56 | background: #1c1f2b; 57 | } 58 | .book.color-theme-2 .book-body .page-wrapper .page-inner section { 59 | background: #1c1f2b; 60 | } 61 | .book.font-size-0 .book-body .page-inner section { 62 | font-size: 1.2rem; 63 | } 64 | .book.font-size-1 .book-body .page-inner section { 65 | font-size: 1.4rem; 66 | } 67 | .book.font-size-2 .book-body .page-inner section { 68 | font-size: 1.6rem; 69 | } 70 | .book.font-size-3 .book-body .page-inner section { 71 | font-size: 2.2rem; 72 | } 73 | .book.font-size-4 .book-body .page-inner section { 74 | font-size: 4rem; 75 | } 76 | .book.font-family-0 { 77 | font-family: Georgia, serif; 78 | } 79 | .book.font-family-1 { 80 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 81 | } 82 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal { 83 | color: #704214; 84 | } 85 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal a { 86 | color: inherit; 87 | } 88 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, 89 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2, 90 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h3, 91 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h4, 92 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h5, 93 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { 94 | color: inherit; 95 | } 96 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, 97 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2 { 98 | border-color: inherit; 99 | } 100 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { 101 | color: inherit; 102 | } 103 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal hr { 104 | background-color: inherit; 105 | } 106 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal blockquote { 107 | border-color: inherit; 108 | } 109 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre, 110 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code { 111 | background: #fdf6e3; 112 | color: #657b83; 113 | border-color: #f8df9c; 114 | } 115 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal .highlight { 116 | background-color: inherit; 117 | } 118 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table th, 119 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table td { 120 | border-color: #f5d06c; 121 | } 122 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr { 123 | color: inherit; 124 | background-color: #fdf6e3; 125 | border-color: #444444; 126 | } 127 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { 128 | background-color: #fbeecb; 129 | } 130 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal { 131 | color: #bdcadb; 132 | } 133 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal a { 134 | color: #3eb1d0; 135 | } 136 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, 137 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2, 138 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h3, 139 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h4, 140 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h5, 141 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { 142 | color: #fffffa; 143 | } 144 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, 145 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2 { 146 | border-color: #373b4e; 147 | } 148 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { 149 | color: #373b4e; 150 | } 151 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal hr { 152 | background-color: #373b4e; 153 | } 154 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal blockquote { 155 | border-color: #373b4e; 156 | } 157 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre, 158 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code { 159 | color: #9dbed8; 160 | background: #2d3143; 161 | border-color: #2d3143; 162 | } 163 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal .highlight { 164 | background-color: #282a39; 165 | } 166 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table th, 167 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table td { 168 | border-color: #3b3f54; 169 | } 170 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr { 171 | color: #b6c2d2; 172 | background-color: #2d3143; 173 | border-color: #3b3f54; 174 | } 175 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { 176 | background-color: #35394b; 177 | } 178 | .book.color-theme-1 .book-header { 179 | color: #afa790; 180 | background: transparent; 181 | } 182 | .book.color-theme-1 .book-header .btn { 183 | color: #afa790; 184 | } 185 | .book.color-theme-1 .book-header .btn:hover { 186 | color: #73553c; 187 | background: none; 188 | } 189 | .book.color-theme-1 .book-header h1 { 190 | color: #704214; 191 | } 192 | .book.color-theme-2 .book-header { 193 | color: #7e888b; 194 | background: transparent; 195 | } 196 | .book.color-theme-2 .book-header .btn { 197 | color: #3b3f54; 198 | } 199 | .book.color-theme-2 .book-header .btn:hover { 200 | color: #fffff5; 201 | background: none; 202 | } 203 | .book.color-theme-2 .book-header h1 { 204 | color: #bdcadb; 205 | } 206 | .book.color-theme-1 .book-body .navigation { 207 | color: #afa790; 208 | } 209 | .book.color-theme-1 .book-body .navigation:hover { 210 | color: #73553c; 211 | } 212 | .book.color-theme-2 .book-body .navigation { 213 | color: #383f52; 214 | } 215 | .book.color-theme-2 .book-body .navigation:hover { 216 | color: #fffff5; 217 | } 218 | /* 219 | * Theme 1 220 | */ 221 | .book.color-theme-1 .book-summary { 222 | color: #afa790; 223 | background: #111111; 224 | border-right: 1px solid rgba(0, 0, 0, 0.07); 225 | } 226 | .book.color-theme-1 .book-summary .book-search { 227 | background: transparent; 228 | } 229 | .book.color-theme-1 .book-summary .book-search input, 230 | .book.color-theme-1 .book-summary .book-search input:focus { 231 | border: 1px solid transparent; 232 | } 233 | .book.color-theme-1 .book-summary ul.summary li.divider { 234 | background: #7e888b; 235 | box-shadow: none; 236 | } 237 | .book.color-theme-1 .book-summary ul.summary li i.fa-check { 238 | color: #33cc33; 239 | } 240 | .book.color-theme-1 .book-summary ul.summary li.done > a { 241 | color: #877f6a; 242 | } 243 | .book.color-theme-1 .book-summary ul.summary li a, 244 | .book.color-theme-1 .book-summary ul.summary li span { 245 | color: #877f6a; 246 | background: transparent; 247 | font-weight: normal; 248 | } 249 | .book.color-theme-1 .book-summary ul.summary li.active > a, 250 | .book.color-theme-1 .book-summary ul.summary li a:hover { 251 | color: #704214; 252 | background: transparent; 253 | font-weight: normal; 254 | } 255 | /* 256 | * Theme 2 257 | */ 258 | .book.color-theme-2 .book-summary { 259 | color: #bcc1d2; 260 | background: #2d3143; 261 | border-right: none; 262 | } 263 | .book.color-theme-2 .book-summary .book-search { 264 | background: transparent; 265 | } 266 | .book.color-theme-2 .book-summary .book-search input, 267 | .book.color-theme-2 .book-summary .book-search input:focus { 268 | border: 1px solid transparent; 269 | } 270 | .book.color-theme-2 .book-summary ul.summary li.divider { 271 | background: #272a3a; 272 | box-shadow: none; 273 | } 274 | .book.color-theme-2 .book-summary ul.summary li i.fa-check { 275 | color: #33cc33; 276 | } 277 | .book.color-theme-2 .book-summary ul.summary li.done > a { 278 | color: #62687f; 279 | } 280 | .book.color-theme-2 .book-summary ul.summary li a, 281 | .book.color-theme-2 .book-summary ul.summary li span { 282 | color: #c1c6d7; 283 | background: transparent; 284 | font-weight: 600; 285 | } 286 | .book.color-theme-2 .book-summary ul.summary li.active > a, 287 | .book.color-theme-2 .book-summary ul.summary li a:hover { 288 | color: #f4f4f5; 289 | background: #252737; 290 | font-weight: 600; 291 | } 292 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-theme-api/theme-api.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Preboot v2 3 | * 4 | * Open sourced under MIT license by @mdo. 5 | * Some variables and mixins from Bootstrap (Apache 2 license). 6 | */.api-method{margin:30px -30px}.api-method:last-of-type{margin-bottom:0}.api-method .api-method-definition{padding:0 30px}.api-method .api-method-code{padding:30px 30px 15px;background-color:#FEFEFE;border-top:1px solid #F1EFEF;border-bottom:1px solid #F1EFEF}.api-method .api-method-code pre>code{white-space:pre-wrap;white-space:-moz- pre-wrap;white-space:- pre-wrap;white-space:-o- pre-wrap;word-wrap:break-word}.api-method:after{clear:both}.book.two-columns .api-method{position:relative;width:calc(100% + 60px)}.book.two-columns .api-method:after{content:" ";display:block;visibility:hidden;clear:both}.book.two-columns .api-method .api-method-title{margin-top:0}.book.two-columns .api-method .api-method-definition{float:left;width:50%}.book.two-columns .api-method .api-method-code{position:relative;float:left;width:50%;padding:30px 30px 15px;border-left:1px solid #F1EFEF;border-top:none;border-bottom:none;transition:opacity .3s ease}.book.two-columns .page-wrapper.comments-open-from-definition .api-method-code{opacity:.1}.book-header{padding:0;position:fixed;top:0;left:0;right:0;background-color:#FFF;border-bottom:1px solid rgba(0,0,0,.07);-webkit-transition:left 250ms ease;-moz-transition:left 250ms ease;-o-transition:left 250ms ease;transition:left 250ms ease}.book.with-summary .book-header{left:300px}@media (max-width:600px){.book.with-summary .book-header{left:0}}.book-header .btn.lang-switcher{text-transform:none;font-weight:500;border-radius:0}.book-header .btn.lang-switcher.active{background-color:#03677D;color:#FFF}#book-search-results .search-results{padding:20px 30px 0}#book-search-input{padding:5px;margin-top:0}.book .book-body .page-wrapper .page-inner .comments-section{max-width:calc(100% - 40px)}.book .book-body .page-wrapper .page-inner .comments-section .comments-area{z-index:1}.book .book-body .page-wrapper.comments-open-from-definition .page-inner{left:0!important}.color-theme-3 .dropdown-menu{background-color:#2D3134;border-color:#373B3E}.color-theme-3 .dropdown-menu .dropdown-caret .caret-inner{border-bottom:9px solid #2D3134}.color-theme-3 .dropdown-menu .buttons{border-color:#373B3E}.color-theme-3 .dropdown-menu .button{color:#D0D4D7}.color-theme-3 .dropdown-menu .button:hover{color:#EEE}.book.color-theme-3 .book-header{color:#D0D4D7;background:#272B2D}.book.color-theme-3 .book-header .btn{color:#D0D4D7}.book.color-theme-3 .book-header .btn:hover{color:#EEE;background:0 0}.book.color-theme-3 .book-header .btn.lang-switcher.active{background-color:#186469}.book.color-theme-3 .book-header .btn.lang-switcher.active:hover{background-color:#186469}.book.color-theme-3 .book-header h1{color:#EEE}.book.color-theme-3 .book-body section a{color:#3EAAB1}.book.color-theme-3 .book-body section .api-method-code{color:#EEE;background-color:#272B2D}.book.color-theme-3 .book-body section .api-method-code h1,.book.color-theme-3 .book-body section .api-method-code h2,.book.color-theme-3 .book-body section .api-method-code h3,.book.color-theme-3 .book-body section .api-method-code h4,.book.color-theme-3 .book-body section .api-method-code h5,.book.color-theme-3 .book-body section .api-method-code h6{color:#fff}.book.color-theme-3 .book-body section .api-method-code h6{color:#D0D4D7}.book.color-theme-3 .book-body section .api-method-code hr{background-color:#373B3E}.book.color-theme-3 .book-body section .api-method-code blockquote{border-color:#373B3E}.book.color-theme-3 .book-body section .api-method-code code,.book.color-theme-3 .book-body section .api-method-code pre{color:#EEE;background-color:#2D3134;border-radius:3px}.book.color-theme-3 .book-body section .api-method-code table{border-collapse:separate;border:1px solid #373B3E;border-radius:3px}.book.color-theme-3 .book-body section .api-method-code table td,.book.color-theme-3 .book-body section .api-method-code table th{border:none}.book.color-theme-3 .book-body section .api-method-code table th{border-bottom:1px solid #373B3E}.book.color-theme-3 .book-body section .api-method-code table tr{color:#EEE;background-color:transparent}.book.color-theme-3 .book-body section .api-method-code table tr:nth-child(2n){background-color:#2D3134}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs{display:block;overflow-x:auto;padding:.5em;background:#474949;color:#d1d9e1}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-comment,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-quote,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-comment,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-quote{color:#969896;font-style:italic}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-addition,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-keyword,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-literal,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-selector-tag,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-type,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-addition,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-keyword,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-literal,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-selector-tag,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-type{color:#c9c}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-number,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-selector-attr,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-selector-pseudo,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-number,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-selector-attr,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-selector-pseudo{color:#f99157}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-doctag,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-regexp,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-string,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-doctag,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-regexp,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-string{color:#8abeb7}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-built_in,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-name,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-section,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-title,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-built_in,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-name,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-section,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-title{color:#b5bd68}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-class .hljs-title,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-selector-id,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-template-variable,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-variable,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-class .hljs-title,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-selector-id,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-template-variable,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-variable{color:#fc6}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-name,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-section,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-strong,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-name,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-section,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-strong{font-weight:700}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-bullet,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-link,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-meta,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-subst,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-symbol,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-bullet,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-link,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-meta,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-subst,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-symbol{color:#f99157}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-deletion,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-deletion{color:#dc322f}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-formula,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-formula{background:#eee8d5}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-attr,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-attribute,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-attr,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-attribute{color:#81a2be}.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code code .hljs-emphasis,.book.color-theme-3 .book-body .page-wrapper .page-inner section.normal .api-method-code pre .hljs-emphasis{font-style:italic}.page-inner{max-width:100%;padding:0;margin-top:50px}.markdown-section{padding:20px 30px 0} -------------------------------------------------------------------------------- /docs/getting_started/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Getting Started · GitBook 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |

    74 |
    75 | 76 | 77 | 80 | 81 | 82 | 317 | 318 | 319 |
    320 | 321 |
    322 | 323 |
    324 | 325 | 326 | 327 | 336 | 337 | 338 | 339 | 340 |
    341 |
    342 | 343 |
    344 |
    345 | 346 |
    347 | 348 |

    Getting Started

    Let's start by building a very simple but complete working application. It will display a person's details, and let you edit them.

    Not very impressive, but we'll build from there.

    Let's get started!

    349 | 350 |
    351 | 352 |
    353 |
    354 |
    355 | 356 |

    results matching ""

    357 |
      358 | 359 |
      360 |
      361 | 362 |

      No results matching ""

      363 | 364 |
      365 |
      366 |
      367 | 368 |
      369 |
      370 | 371 |
      372 | 373 | 374 | 375 | 376 |
      377 | 378 | 384 |
      385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /docs/how_it_works/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | How It All Works · GitBook 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
      74 |
      75 | 76 | 77 | 80 | 81 | 82 | 317 | 318 | 319 |
      320 | 321 |
      322 | 323 |
      324 | 325 | 326 | 327 | 336 | 337 | 338 | 339 | 340 |
      341 |
      342 | 343 |
      344 |
      345 | 346 |
      347 | 348 |

      How It Works

      Here we'll lift the covers and discover how the pieces of Immuto fit together.

      You don't necessarily need to know this to use Immuto, but it will help you become a "power user" and become able to figure out solutions to problems more easily.

      349 | 350 |
      351 | 352 |
      353 |
      354 |
      355 | 356 |

      results matching ""

      357 |
        358 | 359 |
        360 |
        361 | 362 |

        No results matching ""

        363 | 364 |
        365 |
        366 |
        367 | 368 |
        369 |
        370 | 371 |
        372 | 373 | 374 | 375 | 376 |
        377 | 378 | 384 |
        385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /docs/how_it_works/cursors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | How cursors work · GitBook 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
        74 |
        75 | 76 | 77 | 80 | 81 | 82 | 317 | 318 | 319 |
        320 | 321 |
        322 | 323 |
        324 | 325 | 326 | 327 | 336 | 337 | 338 | 339 | 340 |
        341 |
        342 | 343 |
        344 |
        345 | 346 |
        347 | 348 |

        How Cursors work

        A cursor is a way to read the state at some location in a data structure, and also to dispatch actions to that same location.

        This is a simplified version, leaving out the piping operator:

        interface Cursor<S, A> {
        349 |     readonly state: S;
        350 |     (action: A): Cursor<S, A>;
        351 | }
        352 |

        Two things to note:

        1. A cursor doesn't have a dispatch function; it is a dispatch function. 353 |
        2. 354 |
        3. It returns another cursor of the same type, so you can access the new state after your action has been dispatched. The value of state never changes on an existing cursor. 355 |
        356 |

        You can call snapshot, passing it your store` to get a cursor to the whole store, and then using the piping operator to get to the objects inside.

        357 | 358 |
        359 | 360 |
        361 |
        362 |
        363 | 364 |

        results matching ""

        365 |
          366 | 367 |
          368 |
          369 | 370 |

          No results matching ""

          371 | 372 |
          373 |
          374 |
          375 | 376 |
          377 |
          378 | 379 |
          380 | 381 | 382 | 383 | 384 |
          385 | 386 | 392 |
          393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-lunr/lunr.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.12 3 | * Copyright (C) 2015 Oliver Nightingale 4 | * MIT Licensed 5 | * @license 6 | */ 7 | !function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.5.12",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(t){return arguments.length&&null!=t&&void 0!=t?Array.isArray(t)?t.map(function(t){return t.toLowerCase()}):t.toString().trim().toLowerCase().split(/[\s\-]+/):[]},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,o=0;n>o;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;no;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;n 3 | 4 | 5 | How the amend function works · GitBook 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
          72 |
          73 | 74 | 75 | 78 | 79 | 80 | 315 | 316 | 317 |
          318 | 319 |
          320 | 321 |
          322 | 323 | 324 | 325 | 334 | 335 | 336 | 337 | 338 |
          339 |
          340 | 341 |
          342 |
          343 | 344 |
          345 | 346 |

          How the amend function works

          The amend function is a little helper function. It is defined as:

          function amend<O1, O2>(o1: O1, o2: O2) {
          347 |     return assign({}, o1, o2);
          348 | }
          349 |

          And assign is just a substitute for the gradually appearing standard function Object.assign.

          The purpose of amend is to fill in for the object spread operator that is not yet in TypeScript. It would be cool if we could say:

          { ...book, title }
          350 |

          which would mean: make a new object with all the properties of book, and add (or replace) the title property with the value of the title variable. But in the meantime this isn't so bad:

          amend(book, { title })
          351 | 352 | 353 |
          354 | 355 |
          356 |
          357 |
          358 | 359 |

          results matching ""

          360 |
            361 | 362 |
            363 |
            364 | 365 |

            No results matching ""

            366 | 367 |
            368 |
            369 |
            370 | 371 |
            372 |
            373 | 374 |
            375 | 376 | 377 | 378 | 379 |
            380 | 381 | 387 |
            388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | -------------------------------------------------------------------------------- /docs/getting_started/04_what_just_happened.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 4. What just happened? · GitBook 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
            74 |
            75 | 76 | 77 | 80 | 81 | 82 | 317 | 318 | 319 |
            320 | 321 |
            322 | 323 |
            324 | 325 | 326 | 327 | 336 | 337 | 338 | 339 | 340 |
            341 |
            342 | 343 |
            344 |
            345 | 346 |
            347 | 348 |

            What just happened?

            We just achieved declarative two-way binding with Redux. No imperative code was required, just pure declarations. How is this possible?

            Immutable data is intentionally hard to modify. We are forced to clone the whole data structure just to change one tiny piece. What we need is that one weird trick (doctors hate her!) known as a cursor.

            A cursor represents a single location in the data structure, and when you pass it a new value for that location, it takes care of cloning the rest of the structure around it.

            Immuto generalises this idea and says that cursors must be sent actions (because Immuto is Redux, and in Redux actions are the only way to change anything).

            So because our PersonEditor has a Person.Cursor, it can both access the current state of that Person and also send actions to it.

            A property is a really simple example of a cursor, supporting a single action type, "REPLACE", that lets you assign a new value. So it mimics a traditional cursor.

            The binding prop of TextInput needs a cursor to a string. We have a cursor to a whole Person. We can "pipe" it through Person.firstName to get a cursor to the firstName string of our Person.

            How did we get a cursor to a person? Well, in this case we used bindToStore, because our whole store is a Person. But a cursor can refer to any Person embedded within a complex structure, so PersonEditor doesn't necessarily have to be at the root of our UI, as we'll see.

            It's good idea to only use properties for really simple individual values: strings, numbers, booleans, that can vary independently.

            If you find yourself storing your whole life story in a single property, that might need a rethink. You'll probably prefer to enforce rules about how its various parts can be modified, to keep it consistent. Immuto is all about structuring stuff, so we'll get on to some ways of doing that.

            349 | 350 |
            351 | 352 |
            353 |
            354 |
            355 | 356 |

            results matching ""

            357 |
              358 | 359 |
              360 |
              361 | 362 |

              No results matching ""

              363 | 364 |
              365 |
              366 |
              367 | 368 |
              369 |
              370 | 371 |
              372 | 373 | 374 | 375 | 376 |
              377 | 378 | 384 |
              385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /docs/how_it_works/the_piping_operator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | How the Piping Operator works · GitBook 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
              74 |
              75 | 76 | 77 | 80 | 81 | 82 | 317 | 318 | 319 |
              320 | 321 |
              322 | 323 |
              324 | 325 | 326 | 327 | 336 | 337 | 338 | 339 | 340 |
              341 |
              342 | 343 |
              344 |
              345 | 346 |
              347 | 348 |

              How the Piping Operator works

              The piping operator is the $ method available on cursors. It is ridiculously simple. You pass it a function, and it calls that function, passing itself as the only argument, then returns whatever the function returns. That's it. The implementation, sans type declarations, is equivalent to:

              $(ref) {
              349 |     return ref(this);
              350 | }
              351 |

              The type declarations are important, of course: the things being passed around must be cursors, though the input and output cursors can refer to different types:

              $<S2, A2>(ref: (outer: Cursor<S, A>) => Cursor<S2, A2>): Cursor<S2, A2>;
              352 |

              The parameter is called ref because reference definitions are ideal for passing in.

              Why bother with such a simple thing? So we can write easy-to-read expressions that navigate down a hierarchy. Suppose a Shop has a collection Shelves of Shelf objects, each having a collection Books of Book objects, each having a title.

              Without piping, we have to write inside-out expressions. Here, shop is the starting point, yet appears in the middle!

              const book = Book.title(
              353 |                 Books.at(
              354 |                     Shelf.books(
              355 |                         Shelves.at(
              356 |                             Shop.shelves(shop),
              357 |                             13
              358 |                         )
              359 |                     ), 
              360 |                     832
              361 |                 )
              362 |             );
              363 |

              With piping, we "send" the input through a pipeline of operators, and it's self-explanatory:

              const title = shop.$(Shop.shelves)
              364 |                   .$(Shelves.at(13))
              365 |                   .$(Shelf.books)
              366 |                   .$(Books.at(832))
              367 |                   .$(Book.title);
              368 | 369 | 370 |
              371 | 372 |
              373 |
              374 |
              375 | 376 |

              results matching ""

              377 |
                378 | 379 |
                380 |
                381 | 382 |

                No results matching ""

                383 | 384 |
                385 |
                386 |
                387 | 388 |
                389 |
                390 | 391 |
                392 | 393 | 394 | 395 | 396 |
                397 | 398 | 404 |
                405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | --------------------------------------------------------------------------------