├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── .vuepress │ └── config.js ├── README.md └── guide │ ├── README.md │ ├── how-it-works.md │ └── operators.md ├── package-lock.json ├── package.json ├── src └── index.ts ├── tests └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | index.d.ts 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | lib 18 | docs/.vuepress/dist 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tests 3 | tsconfig.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: '8' 3 | cache: 4 | directories: 5 | - node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christian Alfoni 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proxychain 2 | Create a chaining API for your application 3 | 4 | Writing readable code requires a lot of discipline or helpful tools. This is such a helpful tool. With a chaining API you are forced into a declarative pattern for your application logic and that will improve the readability of your code. **Proxychain** is a tiny library, less than 50 lines of Typescript code. It allows you to express and run logic in your application that has not even been defined yet. 5 | 6 | ## API 7 | 8 | ```js 9 | import { ProxyChain } from 'proxychain' 10 | 11 | const chain = ProxyChain() 12 | 13 | const appLoaded = chain() 14 | .state(state => state.isLoading = true) 15 | .http('get', '/user', { 16 | success: chain().state((state, user) => state.user = user), 17 | error: chain().state((state, error) => state.error = error) 18 | }) 19 | .state(state => state.isLoading = false) 20 | ``` 21 | 22 | You can call **appLoaded** if you want to, though nothing will really happen. We have to define a reducer function for our chains and implement how these chain operators should be managed. 23 | 24 | ```js 25 | import { ProxyChain } from 'proxychain' 26 | import myStateStore from './myStateStore' 27 | 28 | const chain = ProxyChain((payload, operator) => { 29 | return payload 30 | }) 31 | ``` 32 | 33 | The chain reducer is responsible for firing side effects and updating the payload of the chain. By default it should always return the current payload. Let us now add our first operator: 34 | 35 | ```js 36 | import { ProxyChain } from 'proxychain' 37 | import myStateStore from './myStateStore' 38 | 39 | const chain = ProxyChain((payload, operator) => { 40 | switch(operator.type) { 41 | case 'state': 42 | const stateCallback = operator.args[0] 43 | stateCallback(myStateStore, payload) 44 | } 45 | return payload 46 | }) 47 | ``` 48 | 49 | Whenever a state operator runs we will grab the callback and call it with our state store and the current payload of the chain. That is it... now any usage of **state** in your chains will allow you to do a state change. 50 | 51 | Lets us add our http operator. 52 | 53 | ```js 54 | import { ProxyChain } from 'proxychain' 55 | import myStateStore from './myStateStore' 56 | import axios from 'axios' 57 | 58 | const chain = ProxyChain((payload, operator) => { 59 | switch(operator.type) { 60 | case 'state': ... 61 | case 'http': 62 | const method = operator.args[0] 63 | const url = operator.args[1] 64 | const paths = operator.args[2] 65 | 66 | return axios[method](url).then((response) => paths.success(response)).catch((error) => paths.error(error)) 67 | 68 | } 69 | return payload 70 | }) 71 | ``` 72 | 73 | As you can see, we are calling **success** or **error** depending on the success of the request. These are also chains that will be merged into the existing execution. That means that last operator will not run until http is done. 74 | 75 | We can now improve the declarativeness of our code even more: 76 | 77 | ```js 78 | import { ProxyChain } from 'proxychain' 79 | 80 | const chain = ProxyChain() 81 | 82 | const setLoading = isLoading => state => state.isLoading = isLoading 83 | const setUser = user => state => state.user = user 84 | const setError = error => state => state.error = error 85 | 86 | const success = chain().state(setUser) 87 | const error = chain().state(setError) 88 | 89 | const appLoaded = chain() 90 | .state(setLoading(true)) 91 | .http('get', '/user', { success, error }) 92 | .state(setLoading(false)) 93 | ``` 94 | 95 | As you can see, chaining allows us to split up our code a lot more. Focus on what is actually happening when we express logic and then create reusable parts of our code. Proxychain takes this even further as you can just write out thea chain you want and then implement it as a second step. -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Emmis', 3 | description: 'Create a chaining API for your application', 4 | head: [ 5 | ['link', { rel: 'icon', href: `/logo.png` }] 6 | ], 7 | serviceWorker: true, 8 | themeConfig: { 9 | repo: 'christianalfoni/emmis', 10 | editLinks: true, 11 | docsDir: 'docs', 12 | nav: [ 13 | { 14 | text: 'Guide', 15 | link: '/guide/', 16 | } 17 | ], 18 | sidebar: { 19 | '/guide/': [ 20 | { 21 | title: 'Guide', 22 | collapsable: false, 23 | children: [ 24 | '', 25 | 'how-it-works', 26 | 'operators' 27 | ] 28 | } 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | actionText: Get Started → 4 | actionLink: /guide/ 5 | features: 6 | - title: Super tiny 7 | details: Only 40 lines of code gives you a ton of flexibility 8 | - title: Flexible 9 | details: Convert any application logic into a chain 10 | - title: Debuggable 11 | details: See flow of execution with all the details 12 | footer: MIT Licensed | Copyright © 2018-present Christian Alfoni 13 | --- 14 | 15 | # Why? 16 | 17 | When we write application logic we import different dependencies into the files that expresses our application logic. For example: 18 | 19 | ```js 20 | import axios from 'axios' 21 | import { withBaseUrl, encodeQuery, withDefaultHeaders } from './utils' 22 | import { error } from './services' 23 | 24 | export const getUser = (queryParams) => { 25 | const url = withBaseUrl('/user') 26 | const query = encodeQuery(queryParams) 27 | 28 | return axios.get(url, { 29 | headers: withDefaultHeaders() 30 | }) 31 | .then((response) => { 32 | return response.data 33 | }) 34 | .catch((responseError) => { 35 | error.track(responseError) 36 | }) 37 | } 38 | ``` 39 | 40 | With **Emmis** this type of code can be cleaned up like this: 41 | 42 | ```js 43 | import chain from './chain' 44 | 45 | export const getUser = chain('getUser') 46 | .http('get', '/user', { 47 | success: chain() 48 | .map(response => response.data), 49 | error: chain() 50 | .trackError() 51 | }) 52 | ``` 53 | 54 | You will also see the console log out something like: 55 | 56 | ```sh 57 | > getUser 58 | http { method: 'get', url: '/user', path: 'success' } 59 | map { id: '1', name: 'John' } 60 | ``` 61 | 62 | So to summarize this approach allows you to move all side effect utilities and helpers you use in your code behind a chaining API that debugs for you. Chaining APIs encourages functional code and generally makes your code more declarative. In addition to this you can express complex asynchronous flows as well. The greatest benefit though is that **you decide** how the chaining API should look and operate. -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | **Emmis** allows you to express any application logic as chains. Native chaining abilities like *map*, *reduce* and *filter* works only on arrays and are meant to transform values. But this way of expressing logic can be very useful when not doing strict value transformation, but running side effects as well. The big benefit is more declarative and testable code. 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install emmis 9 | ``` 10 | 11 | ## Example 12 | 13 | We want to create a very simple chaining API that just logs out the value passed into it: 14 | 15 | ```js 16 | const logPayload = chain() 17 | .log() // "foo" 18 | 19 | logPayload('foo') 20 | ``` 21 | 22 | We have to define our proxychain first passing in a reducer. 23 | 24 | ```js 25 | import { ProxyChain } from 'emmis' 26 | 27 | // We switch on the operator type, identifying the "log" operator. 28 | // Then we execute the logic. The reducer should always return 29 | // current payload or a new payload to the chain 30 | const chainReducer = (payload, operator) => { 31 | switch (operator.type) { 32 | case 'log': 33 | console.log(payload) 34 | break; 35 | } 36 | return payload 37 | } 38 | 39 | const chain = ProxyChain(chainReducer) 40 | ``` 41 | 42 | This is not very exciting, but it shows you the basic construct. A second part to this construct is asynchronicity. You can return a promise in the reducer. 43 | 44 | ```js 45 | const logPayloadAfterOneSecond = chain() 46 | .delay(1000) 47 | .log() // "foo" 48 | 49 | logPayloadAfterOneSecond('foo') 50 | ``` 51 | 52 | ```js 53 | import { ProxyChain } from 'emmis' 54 | 55 | const chainReducer = (payload, operator) => { 56 | switch (operator.type) { 57 | case 'log': 58 | console.log(payload) 59 | break; 60 | case 'delay': 61 | return new Promise(resolve => 62 | setTimeout(() => resolve(payload), operator.args[0]) 63 | ) 64 | } 65 | return payload 66 | } 67 | 68 | const chain = ProxyChain(chainReducer) 69 | ``` 70 | 71 | With the **delay** we return a promise that resolves using a *setTimeout*. The timeout value is grabbed from the first argument passed to the delay operator. 72 | 73 | ## Debug 74 | 75 | All named chains will be log debugging information. You can provide additional data to this logging. Let us look at an example of adding debugging information to a **map** operator: 76 | 77 | ```js 78 | import { ProxyChain } from 'emmis' 79 | 80 | const chainReducer = (payload, operator) => { 81 | switch (operator.type) { 82 | case 'map': 83 | const mappedResult = operator.args[0](payload) 84 | operator.debug({ 85 | input: payload, 86 | output: mappedResult 87 | }) 88 | return mappedResult 89 | } 90 | return payload 91 | } 92 | 93 | const chain = ProxyChain(chainReducer) 94 | ``` 95 | 96 | Now when the **map** operator runs for example like this: 97 | 98 | ```js 99 | const someChain = chain('someChain') 100 | .map(input => input.toUpperCase()) 101 | 102 | someChain('foo') 103 | ``` 104 | 105 | It will output the following: 106 | 107 | ```sh 108 | > someChain 109 | map { input: "foo", output: "FOO" } 110 | ``` -------------------------------------------------------------------------------- /docs/guide/how-it-works.md: -------------------------------------------------------------------------------- 1 | # How it works 2 | 3 | **Emmis** is based on proxies. That means when you do: 4 | 5 | ``` js 6 | const doThis = chain().log() 7 | 8 | doThis() 9 | ``` 10 | 11 | The **chain** actually returns a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) that intercepts all calls to the operators. Instead of running the method it creates a task to be executed. This task looks like: 12 | 13 | ```js 14 | { 15 | type: 'log', 16 | args: [] 17 | } 18 | ``` 19 | 20 | When you actually execute the chain, *doThis()*, it will reduce over these tasks with the passed in payload and manage usage of promises. This simple idea allows you to create a lot of different operators. -------------------------------------------------------------------------------- /docs/guide/operators.md: -------------------------------------------------------------------------------- 1 | # Operators 2 | 3 | **Emmis** does not ship with any operators. On this page you rather get examples of how to build your custom operators. This gives you insight into the flexbility of the API and inspiration to create your own operators. 4 | 5 | ## log 6 | Logs out current payload with a label. 7 | 8 | **example** 9 | ```js 10 | const doThis = chain() 11 | .log('payload') // "payload foo" 12 | 13 | doThis('foo') 14 | ``` 15 | 16 | **reducer** 17 | ```js 18 | function (payload, operator) { 19 | switch (operator.type) { 20 | case 'log': 21 | console.log(operator.args[0], payload) 22 | break; 23 | } 24 | return payload 25 | } 26 | ``` 27 | 28 | **typing** 29 | ```typescript 30 | interface Chain extends IProxyChain { 31 | log: (message: string) => Chain 32 | } 33 | ``` 34 | 35 | ## delay 36 | Delays further execution by *x* number of milliseconds. 37 | 38 | **example** 39 | ```js 40 | const doThis = chain() 41 | .delay(1000) // delayes further execution by 1 second 42 | 43 | doThis('foo') 44 | ``` 45 | 46 | **reducer** 47 | ```js 48 | function (payload, operator) { 49 | switch (operator.type) { 50 | case 'delay': 51 | return new Promise(resolve => 52 | setTimeout(() => resolve(payload), operator.args[0]) 53 | ) 54 | } 55 | return payload 56 | } 57 | ``` 58 | 59 | **typing** 60 | ```typescript 61 | interface Chain extends IProxyChain { 62 | delay: (ms: number) => Chain 63 | } 64 | ``` 65 | 66 | ## map 67 | Transforms the payload into a new payload. 68 | 69 | **example** 70 | ```js 71 | const toUpper = string => string.toUpperCase() 72 | 73 | const doThis = chain() 74 | .map(toUpper) // "FOO" 75 | 76 | doThis('foo') 77 | ``` 78 | 79 | **reducer** 80 | ```js 81 | function (payload, operator) { 82 | switch (operator.type) { 83 | case 'map': 84 | return operator.args[0](payload) 85 | } 86 | return payload 87 | } 88 | ``` 89 | 90 | **typing** 91 | ```typescript 92 | interface Chain extends IProxyChain { 93 | map: (cb: (payload: Input) => Output) => Chain 94 | } 95 | ``` 96 | 97 | ## http 98 | Does http requests. 99 | 100 | **example** 101 | ```js 102 | const doThis = chain() 103 | .http('get', '/user', { 104 | success: chain(), 105 | error: chain() 106 | }) 107 | 108 | doThis('foo') 109 | ``` 110 | 111 | **reducer** 112 | ```js 113 | function (payload, operator) { 114 | switch (operator.type) { 115 | case 'http': 116 | const method = operator.args[0] 117 | const url = operator.args[1] 118 | const paths = operator.args[2] 119 | return axios[method](url) 120 | .then((response) => paths.success(response.data)) 121 | .catch(error => paths.error(error)) 122 | } 123 | return payload 124 | } 125 | ``` 126 | 127 | **typing** 128 | ```typescript 129 | interface Chain extends IProxyChain { 130 | http: (method: string, url: string, paths: { 131 | success: Chain, 132 | error: Chain 133 | }) => Chain | Chain 134 | } 135 | ``` 136 | 137 | ## filter 138 | Only continues execution if the predicate is met. 139 | 140 | **example** 141 | ```js 142 | const minLength = length => string => string.length >= length 143 | 144 | const doThis = chain() 145 | .filter(minLength(3), chain() 146 | .log() 147 | ) 148 | 149 | doThis('foo') 150 | ``` 151 | 152 | **reducer** 153 | ```js 154 | function (payload, operator) { 155 | switch (operator.type) { 156 | case 'filter': 157 | const shouldContinue = operator.args[0](payload) 158 | return shouldContinue ? operator.args[1](payload) : payload 159 | } 160 | } 161 | ``` 162 | 163 | **typing** 164 | 165 | Note that this typing requires the filtered chain to return the same type as the flter input. 166 | 167 | ```typescript 168 | interface Chain extends IProxyChain { 169 | filter: ( 170 | cb: (payload: Input) => boolean, 171 | continueChain: Chain 172 | ) => Chain 173 | } 174 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emmis", 3 | "version": "1.1.0", 4 | "description": "Create a chaining API for your application", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "prepare": "npm run build", 8 | "docs:dev": "vuepress dev docs", 9 | "docs:build": "vuepress build docs", 10 | "build": "tsc", 11 | "test": "mocha -r ts-node/register tests/*.ts" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/christianalfoni/emmis.git" 16 | }, 17 | "keywords": [ "chain", "async", "proxy", "api" ], 18 | "author": "Christian Alfoni", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/christianalfoni/emmis/issues" 22 | }, 23 | "homepage": "https://github.com/christianalfoni/emmis#readme", 24 | "devDependencies": { 25 | "@types/chai": "^4.1.2", 26 | "@types/mocha": "^5.0.0", 27 | "chai": "^4.1.2", 28 | "mocha": "^5.1.0", 29 | "ts-node": "^5.0.1", 30 | "typescript": "^2.8.1", 31 | "vuepress": "^0.3.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export interface IProxyChain { 2 | (payload?: I): Promise; 3 | } 4 | 5 | export interface IOperator { 6 | type: keyof Operators; 7 | context: any; 8 | args: any[]; 9 | debug(details: {}): void 10 | } 11 | 12 | export function ProxyChain( 13 | reducer?: (payload: any, operator: IOperator) => any 14 | ) { 15 | return function createProxy

(name?: string): P { 16 | const tasks = []; 17 | const handler = function (payload?) { 18 | const debugInfo = [] 19 | const nextPayload = tasks.reduce((currentPayload, task, index) => { 20 | const populatedTask = { 21 | ...task, context: this, debug(details) { 22 | debugInfo[index].details = details 23 | } 24 | } 25 | debugInfo.push({ 26 | type: populatedTask.type, 27 | details: null 28 | }) 29 | if (currentPayload instanceof Promise) { 30 | return currentPayload.then( 31 | (promisedPayload) => (reducer ? reducer(promisedPayload, populatedTask) : promisedPayload) 32 | ); 33 | } 34 | 35 | return reducer ? reducer(currentPayload, populatedTask) : currentPayload; 36 | }, payload); 37 | 38 | return nextPayload instanceof Promise ? nextPayload : Promise.resolve(nextPayload) 39 | .then((returnValue) => { 40 | if (!name) { 41 | return returnValue 42 | } 43 | console.groupCollapsed('### ' + name + ' ###') 44 | debugInfo.forEach(info => { 45 | console.log(info.type, info.details) 46 | }) 47 | console.groupEnd() 48 | return returnValue 49 | }) 50 | }; 51 | const proxy = new Proxy(handler, { 52 | get(_, property) { 53 | return (...args) => { 54 | tasks.push({ 55 | type: property, 56 | args 57 | }); 58 | return proxy; 59 | }; 60 | } 61 | }); 62 | 63 | return proxy as P; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /tests/index.ts: -------------------------------------------------------------------------------- 1 | import { ProxyChain, IProxyChain } from '../src/index'; 2 | import { expect } from 'chai'; 3 | import 'mocha'; 4 | 5 | describe('ProxyChain', () => { 6 | it('should run', () => { 7 | expect(() => ProxyChain(() => { })).to.not.throw(); 8 | }); 9 | it('should return promise when run', () => { 10 | const chain = ProxyChain(() => { }); 11 | const doThis = chain(); 12 | expect(doThis()).to.be.instanceof(Promise); 13 | }); 14 | it('should proxy any method', () => { 15 | interface AppChain extends IProxyChain { 16 | set: () => AppChain; 17 | foo: () => AppChain; 18 | bar: () => AppChain; 19 | } 20 | const chain = ProxyChain(() => { }); 21 | 22 | expect(() => chain().set()).to.not.throw(); 23 | expect(() => chain().foo()).to.not.throw(); 24 | expect(() => chain().bar()).to.not.throw(); 25 | }); 26 | it('should return passed payload', () => { 27 | interface AppChain

extends IProxyChain

{ 28 | set: () => AppChain; 29 | } 30 | const chain = ProxyChain(); 31 | const doThis = chain>().set(); 32 | 33 | return doThis('foo').then((value) => expect(value).to.be.equal('foo')); 34 | }); 35 | it('should allow overriding payload', () => { 36 | interface ProxyChain extends IProxyChain { 37 | map:

(cb: (p: O) => P) => ProxyChain; 38 | } 39 | const chain = ProxyChain>((payload, operator) => { 40 | switch (operator.type) { 41 | case 'map': 42 | return operator.args[0](payload); 43 | } 44 | 45 | return payload; 46 | }); 47 | const doThis = chain>().map((p) => p.toUpperCase()); 48 | 49 | return doThis('foo').then((value) => expect(value).to.be.equal('FOO')); 50 | }); 51 | it('should handle promise results', () => { 52 | interface ProxyChain extends IProxyChain { 53 | map: (cb: (p: Current) => Output) => ProxyChain; 54 | } 55 | const chain = ProxyChain>((payload, operator) => { 56 | switch (operator.type) { 57 | case 'map': 58 | return Promise.resolve(operator.args[0](payload)); 59 | } 60 | 61 | return payload; 62 | }); 63 | const doThis = chain>().map((p) => p.toUpperCase()).map((p) => p.split('')); 64 | 65 | return doThis('foo').then((value) => expect(value).to.be.deep.equal(['F', 'O', 'O'])); 66 | }); 67 | it('should handle using new chain as callback', () => { 68 | interface ProxyChain extends IProxyChain { 69 | when: ; false: ProxyChain }, P extends keyof K>( 70 | path: () => P, 71 | paths: K 72 | ) => K[P]; 73 | map:

(cb: (p: Current) => P) => ProxyChain; 74 | } 75 | const chain = ProxyChain>((payload, operator) => { 76 | switch (operator.type) { 77 | case 'when': 78 | return operator.args[1][operator.args[0](payload)](payload); 79 | case 'map': 80 | return operator.args[0](payload); 81 | } 82 | 83 | return payload; 84 | }); 85 | const doThis = chain>() 86 | .when(() => 'true', { 87 | true: chain>().map(p => p.toUpperCase()), 88 | false: chain>() 89 | }) 90 | .map((p) => p.split('')); 91 | 92 | return doThis('foo').then((value) => expect(value).to.be.deep.equal(['F', 'O', 'O'])); 93 | }); 94 | it('should receive context of chain', () => { 95 | interface AppChain extends IProxyChain { 96 | set: () => AppChain; 97 | } 98 | const chain = ProxyChain((payload, operator) => { 99 | expect(operator.context).to.be.equal('foo') 100 | }); 101 | 102 | const doThis = chain().set() 103 | 104 | doThis.call('foo') 105 | }); 106 | it('should be able to name and add debug information to execution ', () => { 107 | interface ProxyChain extends IProxyChain { 108 | map: (cb: (p: Current) => Output) => ProxyChain; 109 | } 110 | const chain = ProxyChain>((payload, operator) => { 111 | switch (operator.type) { 112 | case 'map': 113 | const returnValue = operator.args[0](payload) 114 | operator.debug(returnValue) 115 | return returnValue 116 | } 117 | 118 | return payload; 119 | }); 120 | const doThis = chain>('named').map((p) => p.toUpperCase()).map((p) => p.split('')); 121 | 122 | return doThis('foo').then((value) => expect(value).to.be.deep.equal(['F', 'O', 'O'])); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "declaration": true, 6 | "declarationDir": "./", 7 | "outDir": "./lib" 8 | }, 9 | "include": [ 10 | "src/**/*" 11 | ] 12 | } --------------------------------------------------------------------------------