├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── circle.yml ├── contrib └── apintent-static │ ├── Dockerfile │ └── public_html │ ├── atlassian_user │ └── index.html │ ├── filters │ ├── com.iopipe.messaging.GenericMessage │ │ ├── com.facebook.statusRequest │ │ │ ├── index.html │ │ │ └── index.js │ │ └── com.twitter.statusRequest │ │ │ ├── index.html │ │ │ └── index.js │ ├── com.twitter.statusMessage │ │ └── com.iopipe.messaging.GenericMessage │ │ │ ├── index.html │ │ │ └── index.js │ └── string │ │ └── com.iopipe.messaging.GenericMessage │ │ ├── index.html │ │ └── index.js │ └── twitter_status │ └── index.html ├── docs ├── jsdoc.json ├── kernels.md ├── node │ └── .placeholder └── nodejs.md ├── js ├── ctxutils.js ├── exec_drivers │ ├── aws │ │ └── index.js │ └── local │ │ └── index.js └── iopipe.js ├── package.json └── spec ├── iopipe_spec.js └── support └── jasmine.json /.gitignore: -------------------------------------------------------------------------------- 1 | auto 2 | 3 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 4 | *.o 5 | *.a 6 | *.so 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.prof 26 | 27 | node_modules 28 | coverage 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.go 2 | node_modules 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to IO PIPE 2 | 3 | General rules: 4 | 5 | * We welcome pull requests! 6 | * Collaborate and be friendly (and follow community guidelines) 7 | * Sign your code! 8 | 9 | It's not a rule, but it's also recommended to drop into #iopipe on 10 | irc.freenode.org. 11 | 12 | # Submit pull requests 13 | 14 | Simply fork our repository on GitHub, then use the GitHub pull request feature. 15 | 16 | For non-trival fixes, please also submit a GitHub issue. 17 | 18 | # Sign your pull requests! 19 | 20 | If you agree to the developer certificate: 21 | 22 | ``` 23 | Developer Certificate of Origin 24 | Version 1.1 25 | 26 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 27 | 660 York Street, Suite 102, 28 | San Francisco, CA 94110 USA 29 | 30 | Everyone is permitted to copy and distribute verbatim copies of this 31 | license document, but changing it is not allowed. 32 | 33 | Developer's Certificate of Origin 1.1 34 | 35 | By making a contribution to this project, I certify that: 36 | 37 | (a) The contribution was created in whole or in part by me and I 38 | have the right to submit it under the open source license 39 | indicated in the file; or 40 | 41 | (b) The contribution is based upon previous work that, to the best 42 | of my knowledge, is covered under an appropriate open source 43 | license and I have the right under that license to submit that 44 | work with modifications, whether created in whole or in part 45 | by me, under the same open source license (unless I am 46 | permitted to submit under a different license), as indicated 47 | in the file; or 48 | 49 | (c) The contribution was provided directly to me by some other 50 | person who certified (a), (b) or (c) and I have not modified 51 | it. 52 | 53 | (d) I understand and agree that this project and the contribution 54 | are public and that a record of the contribution (including all 55 | personal information I submit with it, including my sign-off) is 56 | maintained indefinitely and may be redistributed consistent with 57 | this project or the open source license(s) involved. 58 | ``` 59 | 60 | Then simply sign your commits using `git commit -s` which will add a 61 | line to your commit message similar to: 62 | 63 | Signed-off-by: John Doe 64 | 65 | # Community Guidelines 66 | 67 | * No harrassment or abuse. This includes disrespect or abuse based on religion, 68 | sex, gender, or culture. 69 | 70 | * Encourage and welcome diversity. 71 | 72 | Failure to adhere to these guidelines will result in punative measures. 73 | Based on the severity of an infraction, violations may result in 74 | anything from a warning, to a probation, or even ejection from our 75 | community. 76 | 77 | Contact abuse@iopipe.com to report abuse or appeal violations. 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of yright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2015, Eric Windisch. IO PIPE 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Turtle 2 | --------------------------------------- 3 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000)](https://gitter.im/iopipe/iopipe) 4 | 5 | Apache 2.0 licensed. 6 | 7 | Turtle is a toolkit for building and orchestrating event-driven and 8 | serverless applications. These apps may run anywhere, either locally or, 9 | via execution drivers, in the cloud. It's turtles all the way down. 10 | 11 | Execution drivers exist for: 12 | 13 | - AWS Lambda 14 | 15 | Drivers are planned (or in development) for: 16 | 17 | - Google Cloud Functions 18 | - Azure Functions 19 | - Docker (Engine & Swarm) 20 | 21 | Turtle can: 22 | 23 | * Chain AWS Lambda Functions and local functions. 24 | * Convert NodeJS functions into serverless functions. 25 | * Compose applications with HTTP APIs. 26 | * Parallelize data into serverless workers (scatter & gather). 27 | 28 | # CLI 29 | 30 | Use the [Turtle CLI](https://github.com/iopipe/turtle-golang) to create and 31 | export npm modules, share code, & provide runtime of magnetic functions. 32 | The CLI is still in early development, with our NodeJS SDK being more 33 | mature. 34 | 35 | Find, download, and/or contribute to this tool in the [CLI repo](https://github.com/iopipe/turtle-golang). 36 | 37 | # SDK 38 | 39 | ### NodeJS SDK: 40 | 41 | The NodeJS SDK provides a generic callback chaining mechanism which allows 42 | mixing HTTP(S) requests/POSTs, and function calls. Callbacks 43 | receive the return of the previous function call or HTTP body. 44 | 45 | The callback variable received by a function is also an AWS Lambda-compatible 46 | "context" object. Because of this, you can chain standard callback-based NodeJS 47 | functions, and functions written for AWS Lambda. 48 | 49 | #### Installation 50 | 51 | Installation of the NodeJS module is easy via npm: 52 | 53 | ``` 54 | $ npm install @iopipe/turtle 55 | ``` 56 | 57 | Our CLI is still in early development and may be found on the releases 58 | page, with further instructions in the [CLI 59 | repo](https://github.com/iopipe/iopipe-golang). 60 | 61 | #### Basic usage: 62 | 63 | ```javascript 64 | /* Create a Lambda function which returns event.key + 1. */ 65 | var turtle = require("@iopipe/turtle")() 66 | 67 | exports.handle = turtle.define( 68 | (event, context) => { 69 | context.succeed(event.key + 1) 70 | } 71 | ) 72 | ``` 73 | 74 | #### Context argument 75 | 76 | The context argument operates as both a callback and 77 | an object with several methods, similar to the same 78 | argument passed to AWS Lambda functions. 79 | 80 | Developers may call `context()` directly, with its argument 81 | passed as the event to the next function, or may call its 82 | methods. 83 | 84 | Context Methods: 85 | 86 | - context.done(err, data) 87 | - context.succeed(data) 88 | - context.fail(err) 89 | 90 | 91 | Example of using context.fail to pass errors: 92 | 93 | ```javascript 94 | var turtle = require("@iopipe/turtle")() 95 | 96 | exports.handle = turtle.define( 97 | (event, context) => { 98 | try { 99 | throw "Ford, you're turning into a penguin. Stop it!" 100 | } 101 | catch (err) { 102 | context.fail(err) 103 | } 104 | } 105 | ) 106 | ``` 107 | 108 | #### Function Composition 109 | 110 | Turtle supports the composition of functions, HTTP endpoints, 111 | and modules, taken from functional-programming and flow-based 112 | programming models. This simplifies code-reuse and works as 113 | glue between algorithms. 114 | 115 | There is (some) compatibility with [Rambda](http://ramdajs.com) for 116 | function composition & developing functional applications. 117 | 118 | By using function composition, you will gain additional insights 119 | and increased granularity when utilizing (upcoming) telementry features. 120 | 121 | Example: 122 | 123 | ```javascript 124 | /* Return event.int + 1, square the result, 125 | print, then return the result. */ 126 | exports.handle = turtle.define( 127 | (event, context) => { 128 | context(event.int + 1) 129 | }, 130 | (event, context) => { 131 | context(Math.pow(event, 2)) 132 | }, 133 | (event, context) => { 134 | console.log(event) 135 | context(event) 136 | } 137 | ) 138 | ``` 139 | 140 | #### HTTP endpoints as "functions" 141 | 142 | The first argument to `define`, if a URL, is fetched via an HTTP get 143 | request. Any URL string specified elsewhere in the argument list to 144 | `define` is sent a POST rqeuest. 145 | 146 | This first example fetches data from a URL, then performs a POST request 147 | to another. 148 | 149 | ```javascript 150 | exports.handle = turtle.define("http://localhost/get-data", 151 | "http://localhost/post-data") 152 | ``` 153 | 154 | It's possible to use Turtle to fetch from a URL and perform data 155 | transformations via composition as follows: 156 | 157 | ```javascript 158 | exports.handle = turtle.define( 159 | "http://localhost/get-data", 160 | (data, callback) => { 161 | console.log("Fetched data: " + data) 162 | } 163 | ) 164 | ``` 165 | 166 | Often, users will need to use Turtle to fetch a URL somewhere in 167 | the middle of a composition and will need to use functional tools 168 | such as `turtle.fetch`. The following example also uses 169 | `turtle.property`, which extracts a key from an ECMAscript `Object`: 170 | 171 | ```javascript 172 | exports.handle = turtle.define( 173 | turtle.property("url"), 174 | turtle.fetch, 175 | (data, callback) => { 176 | console.log("Fetched data: " + data) 177 | } 178 | ) 179 | ``` 180 | 181 | #### Scatter & Gather 182 | 183 | Turtle also acts as a client to serverless infrastructure allowing 184 | the use of scatter & gather patterns such as map-reduce. 185 | 186 | Below we initialize an AWS Lambda Client where a Lambda function may 187 | be specified by its Amazon [URN](https://en.wikipedia.org/wiki/Uniform_Resource_Name) 188 | and included in the execution chain: 189 | 190 | ```javascript 191 | var turtle = require("@iopipe/turtle")() 192 | var turtle_aws = require("@iopipe/turtle")( 193 | exec_driver: 'aws' 194 | exec_driver_opts: { 195 | region: 'us-west-1', 196 | access_key: 'itsasecrettoeverybody', 197 | secret_key: 'itsasecrettoeverybody' 198 | } 199 | ) 200 | var crypto = require("crypto") 201 | 202 | export.handler = turtle_aws.define("urn:someLambdaFunction", 203 | "urn:anotherLambdaFunction", 204 | turtle.property("property-of-result"), 205 | turtle.fetch, // fetch that as a URL 206 | (event, callback) => { 207 | callback(JSON.parse(event)) 208 | }, 209 | turtle.map( 210 | iopipe_aws.define( 211 | "urn:spawn_this_on_aws_for_each_value_in_parallel" 212 | ) 213 | )) 214 | ``` 215 | 216 | For more information on using the NodeJS SDK, please refer to its documentation: 217 | ***https://github.com/iopipe/iopipe/blob/master/docs/nodejs.md*** 218 | 219 | ### Go SDK: 220 | 221 | Bundled with the [Turtle CLI](https://github.com/iopipe/turtle-golang) is 222 | a Go SDK, still in early development. 223 | 224 | --------- 225 | Security 226 | --------- 227 | 228 | Applications are executed in individual virtual machines 229 | whenever allowed by the executing environment. 230 | The definition of a virtual machine here is lax, 231 | such that it may describe a Javascript VM, 232 | a Linux container, or a hardware-assisted x86 233 | virtual machine. Users should exercise caution 234 | when running community contributed code. 235 | 236 | It is a project priority to make fetching, publishing, 237 | and execution of functions secure for a 238 | production-ready 1.0.0 release. 239 | 240 | Modules are fetched and stored using sha256 hashes, 241 | providing an advantage over module-hosting mechanisms 242 | which are based simply on a name and version. Future 243 | versions of Turtle will likely implement TUF for 244 | state-of-the-art software assurance. 245 | 246 | Contact security@iopipe.com for questions. 247 | 248 | ------- 249 | LICENSE 250 | ------- 251 | 252 | Apache 2.0. Copyright 2016. IOpipe, Inc. 253 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | pre: 3 | - go get github.com/axw/gocov/gocov 4 | - go get github.com/mattn/goveralls 5 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 6 | override: 7 | - ./node_modules/.bin/istanbul cover jasmine 8 | post: 9 | - $HOME/.go_workspace/bin/goveralls -gocovdata=./coverage/coverage.json -service=circle-ci -repotoken=$COVERALLS_TOKEN 10 | -------------------------------------------------------------------------------- /contrib/apintent-static/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM httpd:2.4 2 | COPY ./public_html/ /usr/local/apache2/htdocs/ 3 | -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/atlassian_user/index.html: -------------------------------------------------------------------------------- 1 | { 2 | "classid": "com.atlassian.user", 3 | "properties": { 4 | "expand" : "attributes", 5 | "link" : { 6 | "rel" : "self", 7 | "href" : "http://localhost:8095/crowd/rest/usermanagement/1/user?username=my_username" 8 | }, 9 | "name" : "my_username", 10 | "first-name" : "My", 11 | "last-name" : "Username", 12 | "display-name" : "My Username", 13 | "email" : "user@example.test", 14 | "password" : { 15 | "link" : { 16 | "rel" : "edit", 17 | "href" : "http://localhost:8095/crowd/rest/usermanagement/1/user/password?username=my_username" 18 | } 19 | }, 20 | "active" : true, 21 | "attributes" : { 22 | "link" : { 23 | "rel" : "self", 24 | "href" : "http://localhost:8095/crowd/rest/usermanagement/1/user/attribute?username=my_username" 25 | }, 26 | "attributes" : [] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/com.iopipe.messaging.GenericMessage/com.facebook.statusRequest/index.html: -------------------------------------------------------------------------------- 1 | index.js -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/com.iopipe.messaging.GenericMessage/com.facebook.statusRequest/index.js: -------------------------------------------------------------------------------- 1 | var obj = JSON.parse(input); 2 | var statusRequest = { 3 | "message": obj["properties"]["text"] 4 | }; 5 | JSON.stringify(statusRequest); 6 | -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/com.iopipe.messaging.GenericMessage/com.twitter.statusRequest/index.html: -------------------------------------------------------------------------------- 1 | index.js -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/com.iopipe.messaging.GenericMessage/com.twitter.statusRequest/index.js: -------------------------------------------------------------------------------- 1 | var obj = JSON.parse(input); 2 | var statusRequest = { 3 | "status": obj["properties"]["text"] 4 | }; 5 | JSON.stringify(statusRequest); 6 | -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/com.twitter.statusMessage/com.iopipe.messaging.GenericMessage/index.html: -------------------------------------------------------------------------------- 1 | index.js -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/com.twitter.statusMessage/com.iopipe.messaging.GenericMessage/index.js: -------------------------------------------------------------------------------- 1 | var obj = JSON.parse(input); 2 | var tweet = obj["properties"]; 3 | var statusMessage = { 4 | "id": "/objects/statusMessage/" + tweet["id_str"], 5 | "user": "/objects/user/" + tweet["user"]["id_str"], 6 | "text": tweet["text"] 7 | }; 8 | JSON.stringify(statusMessage); 9 | -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/string/com.iopipe.messaging.GenericMessage/index.html: -------------------------------------------------------------------------------- 1 | index.js -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/filters/string/com.iopipe.messaging.GenericMessage/index.js: -------------------------------------------------------------------------------- 1 | var statusRequest = { 2 | "classid": "com.iopipe.messaging.GenericMessage", 3 | "properties": { 4 | "text": input 5 | } 6 | }; 7 | JSON.stringify(statusRequest); 8 | -------------------------------------------------------------------------------- /contrib/apintent-static/public_html/twitter_status/index.html: -------------------------------------------------------------------------------- 1 | { 2 | "classid" : "com.twitter.statusMessage", 3 | "properties" : { 4 | "place" : null, 5 | "text" : "Along with our new #Twitterbird, we've also updated our Display Guidelines: https://t.co/Ed4omjYs ^JC", 6 | "in_reply_to_status_id_str" : null, 7 | "favorited" : false, 8 | "entities" : { 9 | "hashtags" : [ 10 | { 11 | "indices" : [ 12 | 19, 13 | 31 14 | ], 15 | "text" : "Twitterbird" 16 | } 17 | ], 18 | "urls" : [ 19 | { 20 | "expanded_url" : "https://dev.twitter.com/terms/display-guidelines", 21 | "display_url" : "dev.twitter.com/terms/display-…", 22 | "url" : "https://t.co/Ed4omjYs", 23 | "indices" : [ 24 | 76, 25 | 97 26 | ] 27 | } 28 | ], 29 | "user_mentions" : [] 30 | }, 31 | "geo" : null, 32 | "source" : "web", 33 | "in_reply_to_screen_name" : null, 34 | "user" : { 35 | "profile_background_color" : "C0DEED", 36 | "protected" : false, 37 | "contributors_enabled" : true, 38 | "profile_link_color" : "0084B4", 39 | "favourites_count" : 24, 40 | "profile_text_color" : "333333", 41 | "show_all_inline_media" : false, 42 | "created_at" : "Wed May 23 06:01:13 +0000 2007", 43 | "profile_background_image_url" : "http://a0.twimg.com/images/themes/theme1/bg.png", 44 | "utc_offset" : -28800, 45 | "profile_image_url" : "http://a0.twimg.com/profile_images/2284174872/7df3h38zabcvjylnyfe3_normal.png", 46 | "friends_count" : 31, 47 | "name" : "Twitter API", 48 | "location" : "San Francisco, CA", 49 | "profile_sidebar_border_color" : "C0DEED", 50 | "screen_name" : "twitterapi", 51 | "profile_background_image_url_https" : "https://si0.twimg.com/images/themes/theme1/bg.png", 52 | "is_translator" : false, 53 | "listed_count" : 10774, 54 | "followers_count" : 1212963, 55 | "profile_use_background_image" : true, 56 | "lang" : "en", 57 | "notifications" : null, 58 | "statuses_count" : 3333, 59 | "id" : 6253282, 60 | "profile_sidebar_fill_color" : "DDEEF6", 61 | "entities" : { 62 | "description" : { 63 | "urls" : [] 64 | }, 65 | "url" : { 66 | "urls" : [ 67 | { 68 | "url" : "http://dev.twitter.com", 69 | "expanded_url" : null, 70 | "indices" : [ 71 | 0, 72 | 22 73 | ] 74 | } 75 | ] 76 | } 77 | }, 78 | "following" : true, 79 | "default_profile" : true, 80 | "verified" : true, 81 | "follow_request_sent" : false, 82 | "profile_image_url_https" : "https://si0.twimg.com/profile_images/2284174872/7df3h38zabcvjylnyfe3_normal.png", 83 | "id_str" : "6253282", 84 | "profile_background_tile" : false, 85 | "description" : "The Real Twitter API. I tweet about API changes, service issues and happily answer questions about Twitter and our API. Don't get an answer? It's on my website.", 86 | "default_profile_image" : false, 87 | "time_zone" : "Pacific Time (US & Canada)", 88 | "geo_enabled" : true, 89 | "url" : "http://dev.twitter.com" 90 | }, 91 | "id_str" : "210462857140252672", 92 | "contributors" : [ 93 | 14927800 94 | ], 95 | "truncated" : false, 96 | "in_reply_to_user_id" : null, 97 | "retweeted" : true, 98 | "possibly_sensitive" : false, 99 | "created_at" : "Wed Jun 06 20:07:10 +0000 2012", 100 | "id" : 210462857140252672, 101 | "in_reply_to_status_id" : null, 102 | "in_reply_to_user_id_str" : null, 103 | "retweet_count" : 66, 104 | "coordinates" : null 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /docs/jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "plugins/markdown" 4 | ], 5 | "opts": { 6 | "destination": "docs/node", 7 | "template": "../node_modules/minami" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/kernels.md: -------------------------------------------------------------------------------- 1 | Kernels must currently be written in a subset of ECMAScript / Javascript. 2 | Future versions of IOpipe may allow kernels to be developed in 3 | other languages, or to run binary kernels. 4 | 5 | A CommonJS module format is employed, expecting a function defined 6 | as 'module.exports'. This function should accept two parameters, 7 | an input variable, and a "context" object providing callbacks. 8 | The context object has the properties 'succeed', 'done', and 'fail'. 9 | 10 | A function should pass its output as intended for the next kernel, 11 | function, or HTTP endpoint via context.done() or context.succeed() 12 | callbacks. 13 | 14 | ------------------ 15 | Example kernel 16 | ------------------ 17 | 18 | The following converts a JSON document representing a "GenericMessage" 19 | into a Twitter status update request (as expected by the Twitter API). 20 | 21 | ```javascript 22 | module.exports = function(input, context) { 23 | var obj = JSON.parse(input) 24 | var statusRequest = { 25 | "status": obj["properties"]["text"] 26 | } 27 | context.done(JSON.stringify(statusRequest)) 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/node/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iopipe/turtle/9d62335259b36e63715625aa5c8479f8bd27c94b/docs/node/.placeholder -------------------------------------------------------------------------------- /docs/nodejs.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | IOpipe simplifies the consumption and integration of 4 | web services. The NodeJS SDK allows the chaining of 5 | web service / HTTP requests, local functions, and 6 | kernels. 7 | 8 | Kernels are portable javascript snippets that take 9 | a single string argument and return a string. These 10 | kernels may be written and used locally, shared 11 | amongst the community, or shared privately within a team. 12 | 13 | Because kernels define their input and output types, 14 | it is easy for developers to discover and build 15 | request/response workflows which automatically build 16 | API requests and transform responses. 17 | 18 | # Installation 19 | 20 | IOpipe for NodeJS may be downloaded & installed using *npm*: 21 | 22 | ```bash 23 | $ npm install -g iopipe 24 | ``` 25 | 26 | Typically, users will use the command-line tool to create, download, 27 | share, and manage kernels. The tool may be used to seed a filter cache 28 | for embedding into your NodeJS project or to export complete npm-compatible 29 | packages. 30 | 31 | # Basic Usage: 32 | 33 | The following example demonstrates the use of IOpipe as a simple function and callback management mechanism: 34 | 35 | ```javascript 36 | var iopipe = require("iopipe") 37 | 38 | var mypipe = iopipe.define( 39 | function() { 40 | return "hello world" 41 | } 42 | ,iopipe.callback(console.log) 43 | ) 44 | 45 | mypipe() 46 | ``` 47 | 48 | Note that all arguments to iopipe.define or iopipe.exec require a callback parameter as its last argument. The method iopipe.callback() is provided as a convenience method to provide a callback to any function that does not, itself, offer a callback parameter. 49 | 50 | The *exec* function exists for those not needing a reference to the function: 51 | 52 | ```javascript 53 | var iopipe = require("iopipe") 54 | iopipe.exec( 55 | function() { 56 | return "hello world" 57 | } 58 | ,iopipe.callback(console.log) 59 | ) 60 | ``` 61 | 62 | # Integrating HTTP(S) requests 63 | 64 | HTTP(S) may be placed anywhere in a pipeline. If a URL is detected 65 | as the first argument, then an HTTP GET is performed. Otherwise, the 66 | POST method is sent. 67 | 68 | The following performs an HTTP GET and prints the output to the console: 69 | 70 | ```javascript 71 | var iopipe = require("iopipe") 72 | iopipe.exec("http://127.0.0.1/my_request/", iopipe.callback(console.log)) 73 | ``` 74 | 75 | Manipulating a response and forwarding it to another server is easily done: 76 | 77 | ```javascript 78 | var iopipe = require("iopipe") 79 | iopipe.exec( 80 | "http://127.0.0.1/my_request/" 81 | ,function(s) { var j = JSON.decode(s); return j["field"] }, 82 | ,"http://127.127.127.127/update/" 83 | ) 84 | ``` 85 | 86 | # Leveraging kernels 87 | 88 | Functions need not be inlined, in fact the greatest value of IOpipe 89 | is in using stored kernels. These allow sharing of functional, 90 | lambda-like methods to transform requests. 91 | 92 | Modifying the previous example to convert the inline function to a kernel: 93 | 94 | ```bash 95 | $ # Write a kernel via the shell: 96 | $ mkdir -p .iopipe/filter_cache/ 97 | $ cat <.iopipe/filter_cache/myscript 98 | module.exports = function(input, context) { 99 | var x = JSON.decode(input) 100 | context.done(x["field"]) 101 | } 102 | EOF 103 | ``` 104 | 105 | ```javascript 106 | var iopipe = require("iopipe") 107 | iopipe.exec( 108 | "http://127.0.0.1/my_request/" 109 | ,"myscript" 110 | ,"http://127.127.127.127/update/" 111 | ) 112 | ``` 113 | -------------------------------------------------------------------------------- /js/ctxutils.js: -------------------------------------------------------------------------------- 1 | exports.callback = function (context) { 2 | return function(err, data) { 3 | if (err) { 4 | context.fail(err) 5 | } else { 6 | context.succeed(data) 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /js/exec_drivers/aws/index.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk') 2 | var ctxutils = require('../../ctxutils') 3 | 4 | module.exports = function(opts) { 5 | return new LambdaDriver(opts) 6 | } 7 | 8 | function LambdaDriver(opts) { 9 | this._aws_lambda = new AWS.Lambda({ 10 | apiVersion: '2015-03-31', 11 | region: opts.region, 12 | accessKeyId: opts.access_key, 13 | secretAccessKey: opts.secret_key 14 | }) 15 | } 16 | 17 | LambdaDriver.prototype.invoke = function(event, context) { 18 | var driver = this._aws_lambda 19 | var funcID = event.id 20 | // api docs: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-property 21 | return function(prevResult) { 22 | var params = { 23 | FunctionName: funcID 24 | ,Payload: prevResult 25 | } 26 | driver.invoke(params, ctxutils.callback(context)) 27 | } 28 | } 29 | 30 | LambdaDriver.prototype.listFunctions = function(event, context) { 31 | this._aws_lambda.listFunctions({}, ctxutils.callback(context)) 32 | } 33 | 34 | LambdaDriver.prototype.getFunction = function(event, context) { 35 | // api docs: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#getFunction-property 36 | this._aws_lambda.getFunction(params, ctxutils.callback(context)) 37 | 38 | /* data: 39 | configuration { 40 | FunctionName: string 41 | FunctionArn: string 42 | Runtime: string 43 | Role: string 44 | CodeSize: integer (bytes) 45 | Description: string 46 | Timeout: integer (secs) 47 | MemorySize: integer (64mb chunks) 48 | LastMOdified: string 49 | CodeSha256: string (sha256 hash of the function deployment package) 50 | VpcConfig: {} 51 | } 52 | code { 53 | RepositoryType: string 54 | Location: string (presigned URL, valid for 10 minutes) 55 | } 56 | */ 57 | } 58 | 59 | LambdaDriver.prototype.createFunction = function(event, context) { 60 | /*params = { 61 | FunctionName: 62 | ,Role: 63 | ,Handler: "exports.handler" 64 | ,Code: { 65 | Zipfile: zip_data 66 | ,S3Bucket: 67 | ,S3Key 68 | ,S3ObjectVersion: ?? 69 | } 70 | ,Runtime: "nodejs" 71 | }*/ 72 | } 73 | -------------------------------------------------------------------------------- /js/exec_drivers/local/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var ctxutils = require('../../ctxutils') 3 | 4 | module.exports = LocalDriver 5 | 6 | function LocalDriver(opts) { 7 | } 8 | 9 | function get_filter_cache(id) { 10 | return path.join(".iopipe/filter_cache", id) 11 | } 12 | 13 | LocalDriver.prototype.invoke = function (event, context) { 14 | var id = event.id 15 | 16 | // Pull from index (or use cached pipescripts) 17 | /* download script */ 18 | var script = fs.readFileSync(get_filter_cache(id)) 19 | var input = "" 20 | 21 | return function(prevResult) { 22 | var sandbox = { "module": { "exports": function () {} } 23 | ,"msg": prevResult 24 | ,"context": context} 25 | var ctx = vm.createContext(sandbox) 26 | vm.runInContext(script, ctx) 27 | var result = vm.runInContext("module.exports(msg, context)", ctx) 28 | 29 | return context.done(result) 30 | } 31 | } 32 | 33 | LocalDriver.prototype.listFunctions = function(event, context) { 34 | fs.readdir(get_filter_cache("", cxtutils.callback(context))) 35 | } 36 | -------------------------------------------------------------------------------- /js/iopipe.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module iopipe 3 | @description 4 | IOpipe helps developers build, connect, and scale code. 5 | 6 | Using flows, iopipe simplifies the consumption and integration 7 | of web services through the chaining of kernels, 8 | single-function applications. 9 | 10 | Kernels take and transform input, providing straight-forward output 11 | in a fashion to Unix pipes. A kernel may receive input or send output 12 | to/from web service requests, functions, or local applications. 13 | 14 | IOpipe may be embedded in applications, used from shell scripts, 15 | or run manually via a CLI to form complete applications. Kernels 16 | and pipelines may be run within local processes, or dispatched to 17 | remote workers (i.e. "cloud"). 18 | 19 | Basic example usage: 20 | 21 | ```javascript 22 | var iopipe = require('iopipe') 23 | iopipe.exec("http://api.twitter.com/blah/blah" 24 | ,function() {} 25 | ,"sha256:DEADBEEF" 26 | ,"user/pipeline" 27 | ,"http://somedestination/") 28 | ``` 29 | */ 30 | var events = require('events') 31 | var url = require('url') 32 | var request = require("request") 33 | var vm = require('vm') 34 | var path = require('path') 35 | 36 | var local_driver = require('./exec_drivers/local/index.js') 37 | 38 | var USERAGENT = "iopipe/0.0.8" 39 | 40 | /** 41 | @description 42 | Initalizes an IOpipe environment, 43 | accepting an argument specifying runtime options 44 | such as the execution driver ('local', 'aws', 'gcp', etc), 45 | and settings for those execution drivers. 46 | 47 | Without arguments, performs local execution and reads/writes 48 | kernels to the directory .iopipe_cache. 49 | 50 | ```javascript 51 | IOpipe({ 52 | exec_driver: 'aws' 53 | exec_driver_opts: { 54 | region: 'us-west-1', 55 | access_key: 'itsasecrettoeverybody', 56 | secret_key: 'itsasecrettoeverybody' 57 | } 58 | }) 59 | ``` 60 | 61 | @param object options - Runtime options. 62 | */ 63 | function IOpipe(options) { 64 | var _exec_driver = 'local' 65 | if (options && "exec_driver" in options) { 66 | _exec_driver = options.exec_driver 67 | } 68 | driver_options = {} 69 | if (options && 70 | "exec_driver_opts" in options) { 71 | driver_options = options.exec_driver_opts 72 | } 73 | this._exec_driver = require("./" + path.join('./exec_drivers/', _exec_driver, 'index.js'))( 74 | driver_options 75 | ) 76 | } 77 | 78 | module.exports = function(options) { 79 | return new IOpipe(options) 80 | } 81 | 82 | function funcCallback(call, context) { 83 | return function() { 84 | var args = [].slice.call(arguments) 85 | if (args.length == 0) { 86 | args.push(undefined) 87 | } 88 | args.push(context) 89 | call.apply(this, args) 90 | } 91 | } 92 | 93 | function httpCallback(u, context) { 94 | return function() { 95 | if (arguments.length === 0) { 96 | request.get({url: url.format(u), strictSSL: true, 97 | headers: { 98 | "User-Agent": USERAGENT 99 | } 100 | }, function(error, response, body) { 101 | if (error || response.statusCode != 200) { 102 | context.fail("HTTP response != 200") 103 | } 104 | context.done(body) 105 | }) 106 | } else { 107 | prevResult = arguments[0] 108 | request.post({url: url.format(u), body: prevResult, strictSSL: true, 109 | headers: { 110 | "User-Agent": USERAGENT 111 | } 112 | }, 113 | function(error, response, body) { 114 | if (error || response.statusCode != 200) { 115 | context.fail("HTTP response != 200") 116 | } 117 | context.done(body) 118 | }) 119 | } 120 | } 121 | } 122 | 123 | IOpipe.prototype.make_context = function(done) { 124 | var ctx = done 125 | ctx.done = done 126 | ctx.fail = function(failure) { 127 | throw failure 128 | } 129 | ctx.succeed = function(result) { 130 | var args = [].slice.call(arguments) 131 | return done.apply(this, args) 132 | } 133 | ctx.raw = done 134 | return ctx 135 | } 136 | 137 | /** 138 | @description 139 | Defines a pipeline, returning a function. 140 | Used for passing arguments to a pipeline as this 141 | is not possible with 'exec', or for reusing a 142 | pipeline. Users seeking a method with callback 143 | should use exec (which actually wraps define), 144 | or call: 145 | 146 | ```javascript 147 | define(args...)(input) 148 | ``` 149 | 150 | @param {...(string|function)} kernel - Kernels specified as functions, scripts, or HTTP endpoints. 151 | */ 152 | IOpipe.prototype.define = function() { 153 | /* We return a function that executes the pipeline, 154 | if arguments are supplied, the first is input, and the remainder 155 | are callbacks. */ 156 | var iopipe = this 157 | var defargs = [].slice.call(arguments) 158 | var aws_context = null 159 | 160 | return function() { 161 | var done = function(e) { return e }; 162 | 163 | /* support callback to function returned by define() 164 | * i.e. define(f1)(data, f2) ~= f2(f1(data)) */ 165 | var largs = [].slice.call(arguments) 166 | if (largs.length > 1) { 167 | aws_context = largs[1] 168 | /* check for awsRequestId to detect Lambda */ 169 | done = function (e) { 170 | if ('awsRequestId' in aws_context) { 171 | aws_context.succeed(e) 172 | } else { 173 | largs[1](e) 174 | } 175 | } 176 | } 177 | 178 | for (var i = defargs.length - 1; i > -1; i--) { 179 | var arg = defargs[i]; 180 | 181 | var context = iopipe.make_context(done) 182 | 183 | if (typeof arg === "function") { 184 | done = funcCallback(arg, context) 185 | } else if (typeof(arg) === "string") { 186 | var u = url.parse(arg); 187 | 188 | if (u.protocol === 'http:' || u.protocol === 'https:') { 189 | var server = u.hostname 190 | done = httpCallback(u, context) 191 | } else { 192 | done = this._exec_driver.invoke({ id: arg }, context) 193 | } 194 | } else { 195 | throw new Error("ERROR: unknown argument: " + arg) 196 | } 197 | } 198 | 199 | // Call function with input data. 200 | done(largs[0]) 201 | } 202 | } 203 | 204 | /** 205 | @description 206 | Executes a pipeline, a la waterfall async pattern. 207 | Each argument is a callback for the result of the previous 208 | function. The final function may be seen as being the penultimate 209 | callback for triggering events. 210 | 211 | Usage: 212 | 213 | ```javascript 214 | iopipe.exec("http://127.0.0.1" 215 | ,"my_pipescript" 216 | ,function(i) { return i } 217 | ,"http://127.0.0.2/post" 218 | ,callback) 219 | ``` 220 | 221 | @param {...(string|function)} kernel - Kernels specified as functions, scripts, or HTTP endpoints. 222 | */ 223 | IOpipe.prototype.exec = function() { 224 | var l = [].slice.call(arguments) 225 | return this.define.apply(this, l)() 226 | } 227 | 228 | /** 229 | Returns a function to access a property/index in an input array. 230 | 231 | Example: 232 | 233 | ```javascript 234 | iopipe.define(iopipe.property(0))(["hello", "world"]) 235 | //=> "hello" 236 | ``` 237 | 238 | @param {*} property - Property to access in input to returned function. 239 | */ 240 | IOpipe.prototype.property = function (index) { 241 | return function (obj, done) { 242 | done(obj[index]) 243 | } 244 | } 245 | 246 | IOpipe.prototype.bind = function (method, arg) { 247 | return function (obj, done) { 248 | done(obj[method].apply(obj, [].slice.call(arguments).slice(1))) 249 | } 250 | } 251 | 252 | /** 253 | Return a function that accepts a function parameter, 254 | currying any parameters passed to apply() itself. 255 | for instance, the following is a "hello world" for apply: 256 | apply("Hello world")(function(x) { console.log(x) })) 257 | 258 | This is useful with iopipe where a function returns another 259 | function and the developer wishes to call this with an iopipe 260 | pipeline: 261 | 262 | ```javascript 263 | iopipe.exec(function() { return function (x) { console.log(x) } } 264 | ,iopipe.apply("hello world")) 265 | ``` 266 | 267 | @param {...*} arguments - Arguments to pass to input of returned function. 268 | */ 269 | IOpipe.prototype.apply = function () { 270 | var l = [].slice.call(arguments) 271 | return function (input, done) { 272 | done(input.apply(input, l)) 273 | } 274 | } 275 | 276 | /** 277 | Returns a map function for executing pipelines for each value 278 | in an input array. This is how one loops over elements and performs 279 | transformations of multiple elements with iopipe. 280 | 281 | Example (adds 1 to each array value): 282 | 283 | ```javascript 284 | iopipe.map(function(i) { return i + 1 })([0, 1, 2]) 285 | //=> [1, 2, 3] 286 | ``` 287 | 288 | @param function function - Function to call against each input provided to output function. 289 | */ 290 | IOpipe.prototype.map = function(fun) { 291 | var iopipe = this 292 | return function(input, done) { 293 | var result = [] 294 | var waiter = new events.EventEmitter() 295 | var eventid = 'map-callback' 296 | waiter.setMaxListeners(1) 297 | waiter.on(eventid, function(msg) { 298 | setImmediate(function() { 299 | result.push(msg) 300 | if (input.length === result.length) { 301 | done(result) 302 | waiter.removeAllListeners(eventid) 303 | } 304 | }) 305 | }) 306 | for (i in input) { 307 | fun(input[i], iopipe.make_context(function(msg) { 308 | waiter.emit(eventid, msg) 309 | })) 310 | } 311 | } 312 | } 313 | 314 | /** 315 | Returns a function which executes each argument 316 | function/pipeline against a single input. That is, 317 | each passed argument (function) is called with 318 | the given input. The effective opposite of map(), 319 | although equally parallelizable. 320 | 321 | Example: 322 | 323 | ```javascript 324 | function echo(i) { 325 | return i 326 | } 327 | iopipe.tee(echo, echo)("hello world") 328 | //=> ["hello world", "hello world"] 329 | ``` 330 | 331 | @param {...function} function - Functions to call against the input to the output function. 332 | */ 333 | IOpipe.prototype.tee = function() { 334 | var iopipe = this 335 | var tfuncs = [].slice.call(arguments) 336 | return function(input, context) { 337 | var result = [] 338 | var waiter = new events.EventEmitter() 339 | var eventid = 'tee-callback' 340 | waiter.setMaxListeners(1) 341 | waiter.on(eventid, function(msg) { 342 | setImmediate(function() { 343 | result.push(msg) 344 | if (result.length === tfuncs.length) { 345 | context(result) 346 | waiter.removeAllListeners(eventid) 347 | } 348 | }) 349 | }) 350 | for (f in tfuncs) { 351 | tfuncs[f].apply(tfuncs[f], [input, iopipe.make_context(function(msg) { 352 | waiter.emit(eventid, msg) 353 | })]) 354 | } 355 | } 356 | } 357 | 358 | /** 359 | Returns a reduce function for consolidating results or 360 | "squeezing" an array into a single value output. 361 | 362 | Example (sum): 363 | 364 | ```javascript 365 | iopipe.reduce(function(prev, curr) { return prev + curr })([2, 2]) 366 | //=> 4 367 | ``` 368 | 369 | @param function function - Function to reduce params to returned function. 370 | */ 371 | IOpipe.prototype.reduce = function(fun) { 372 | return function(input, done) { 373 | done(input.reduce(fun)) 374 | } 375 | } 376 | 377 | /** 378 | Returns a function which fetches the input URL via HTTP(s). 379 | This is useful if using iopipe to create a URL, as may happen 380 | if transforming some data or some API result into a new API request. 381 | 382 | Example: 383 | 384 | ```javascript 385 | var getHNitem = iopipe.define( 386 | "https://hacker-news.firebaseio.com/v0/items/".concat, 387 | ,iopipe.fetch) 388 | getHNitem(1000, function(data) { 389 | console.log("Got HackerNews story:") 390 | console.log(data) 391 | }) 392 | ``` 393 | */ 394 | IOpipe.prototype.fetch = function(u) { 395 | return function(input, done) { 396 | request.get({url: url.format(u), strictSSL: true, 397 | headers: { 398 | "User-Agent": USERAGENT 399 | } 400 | }, function(error, response, body) { 401 | if (error || response.statusCode != 200) { 402 | throw "HTTP response != 200" 403 | } 404 | done(body) 405 | }) 406 | } 407 | } 408 | 409 | 410 | /** 411 | Creates a new function accepting a callback around a function 412 | which does not accept a callback parameter as its last argument. 413 | 414 | @param {...function} function - Function to wrap a callback around. 415 | */ 416 | IOpipe.prototype.callback = function(fun) { 417 | return function(input, done) { 418 | done(fun(input)) 419 | } 420 | } 421 | 422 | /** 423 | We monkey-patch the Object.values function, 424 | this makes it easier to map assoc arrays using tee. 425 | Some Javascript implementations already offer this 426 | function with the same interface. Monkey-patching this is 427 | ugly, but should be mostly-safe(?) 428 | 429 | Example: 430 | 431 | ```javascript 432 | iopipe.tee(Object.keys, Object.values)({"hello": "world"}) 433 | //=> ["hello", "world"] 434 | ``` 435 | 436 | @param array array - Associative array to return values of. 437 | */ 438 | if (!Object.hasOwnProperty("values")) { 439 | Object.values = function (arr) { 440 | return Object.keys(arr).map(function(y) {return arr[y]}) 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iopipe/turtle", 3 | "private": false, 4 | "version": "0.0.8", 5 | "description": "iopipe sdk", 6 | "author": "Eric Windisch", 7 | "files": [ 8 | "js" 9 | ], 10 | "keywords": [ 11 | "iopipe", 12 | "flow", 13 | "waterfall", 14 | "async", 15 | "functional" 16 | ], 17 | "dependencies": { 18 | "aws-sdk": "", 19 | "read-stream": "", 20 | "request": "" 21 | }, 22 | "main": "js/iopipe.js", 23 | "license": "Apache-2.0", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/iopipe/iopipe" 27 | }, 28 | "bugs": "https://github.com/iopipe/iopipe/issues", 29 | "devDependencies": { 30 | "istanbul": "^0.4.4", 31 | "jasmine": "^2.4.1" 32 | }, 33 | "scripts": { 34 | "test": "istanbul cover jasmine" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spec/iopipe_spec.js: -------------------------------------------------------------------------------- 1 | var iopipe = require("../js/iopipe")() 2 | 3 | describe("define", function() { 4 | it("returns a function", function() { 5 | var output = iopipe.define(function() { }) 6 | expect(typeof output).toEqual("function") 7 | }) 8 | }) 9 | 10 | describe("defined-function", function() { 11 | it("can pass no args", function(done) { 12 | var fun = iopipe.define(function(i, ctx) { 13 | expect(i).toEqual(undefined) 14 | ctx.done() 15 | done() 16 | }) 17 | fun() 18 | }) 19 | it("can pass one arg", function(done) { 20 | var expected = "hello world" 21 | var fun = iopipe.define(function(i, ctx) { 22 | expect(i).toEqual(expected); ctx.done() 23 | }, done) 24 | fun(expected) 25 | }) 26 | it("context is callback", function(done) { 27 | var fun = iopipe.define(function(_, ctx) { ctx() }) 28 | fun(undefined, done) 29 | }) 30 | it("can trigger context.done", function(done) { 31 | var fun = iopipe.define(function(_, ctx) { ctx.done() }) 32 | fun(undefined, done) 33 | }) 34 | it("can trigger context.succeed", function(done) { 35 | var fun = iopipe.define(function(_, ctx) { ctx.succeed() }) 36 | fun(undefined, done) 37 | }) 38 | it("can trigger context.raw", function(done) { 39 | var fun = iopipe.define(function(_, ctx) { ctx.raw() }) 40 | fun(undefined, done) 41 | }) 42 | it("passes result to callback", function(done) { 43 | var input = 2 44 | var expected = 4 45 | var fun = iopipe.define( 46 | function(i, ctx) { 47 | ctx(i + 1) 48 | }, 49 | function(i, ctx) { 50 | ctx(i + 1) 51 | } 52 | ) 53 | fun(input, function(i) { 54 | expect(i).toEqual(expected) 55 | done() 56 | }) 57 | }) 58 | }) 59 | 60 | describe("map", function() { 61 | it("has as many outputs as inputs", function(done) { 62 | var input = [0, 1, 2] 63 | iopipe.map(function(i, cxt) { cxt.done(i + 2) })(input, function(output) { 64 | expect(input.length).toEqual(output.length); 65 | done() 66 | //ctx() 67 | }) 68 | }); 69 | it("preserves order", function(done) { 70 | var input = [0, 1, 2] 71 | iopipe.map(function(i, ctx) { ctx.done(i) })(input, function(output, ctx) { 72 | expect(output).toEqual(input); 73 | done() 74 | //ctx() 75 | }) 76 | }); 77 | it("transforms each input element", function(done) { 78 | var input = [0, 1, 2] 79 | var expected = [1, 2, 3] 80 | iopipe.map(function(i, ctx) { ctx.done(i + 1) })(input, function(output, ctx) { 81 | expect(output).toEqual(expected) 82 | done() 83 | //ctx() 84 | }) 85 | }); 86 | }); 87 | 88 | describe("tee", function() { 89 | it("has as many outputs as functions", function(done) { 90 | var input = [0, 1, 2, 3, 4] 91 | var echo = function(i, ctx) { ctx.done(i) } 92 | iopipe.tee(echo, echo)(input, iopipe.make_context(function(output) { 93 | expect(output.length).toEqual(2); 94 | done() 95 | })) 96 | }); 97 | it("preserves order", function(done) { 98 | var input = [0, 1, 2] 99 | var echo = function(i, ctx) { ctx(i) } 100 | var ret2 = function(i, ctx) { ctx(2) } 101 | iopipe.tee(echo, ret2)(input, iopipe.make_context(function(output) { 102 | echo(input, function(e) { 103 | ret2(input, function(r) { 104 | expect(output).toEqual([e, r]) 105 | done() 106 | }) 107 | }) 108 | })) 109 | }); 110 | }); 111 | 112 | describe("reduce", function() { 113 | it("can sum all input elements", function(done) { 114 | var input = [1, 2, 3] 115 | var sum = function(prev, next) { 116 | return prev + next 117 | } 118 | iopipe.reduce(sum)(input, function(output, ctx) { 119 | expect(output).toEqual(6) 120 | done() 121 | //ctx.done() 122 | }) 123 | }); 124 | }) 125 | 126 | describe("exec", function() { 127 | it("can chain functions", function(done) { 128 | iopipe.exec(function(_, ctx) { ctx.done("hello world") } 129 | ,function(input, ctx) { 130 | expect(input).toEqual("hello world") 131 | done() 132 | ctx.done() 133 | } 134 | ,function(input, ctx) { done(); ctx.done() }) 135 | }); 136 | }) 137 | 138 | describe("apply", function() { 139 | it("executes function", function(done) { 140 | iopipe.apply()(done) 141 | }); 142 | }) 143 | 144 | describe("property", function() { 145 | it("returns property for arg", function(done) { 146 | var obj = { "key": "hello world" } 147 | iopipe.property("key")(obj, function (output) { 148 | expect(output).toEqual(obj["key"]) 149 | done() 150 | }) 151 | }); 152 | }) 153 | 154 | describe("callback", function() { 155 | it("calls function", function(done) { 156 | iopipe.callback(done)() 157 | }) 158 | it("passes input to function", function(done) { 159 | iopipe.callback(function(input) { 160 | expect(input).toEqual("hello world") 161 | done() 162 | })("hello world") 163 | }) 164 | }) 165 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false 11 | } 12 | --------------------------------------------------------------------------------