Comparing version 0.2.17 to 0.2.18
@@ -9,3 +9,3 @@ 'use strict'; | ||
var FunctionTree = require('./function-tree').default; | ||
var FunctionTree = require('function-tree').default; | ||
@@ -16,4 +16,5 @@ module.exports = function () { | ||
// TODO: Addd default context provider | ||
var runTask = FunctionTree(options.providers); | ||
return function (task, payload) { | ||
// TODO: factory should only be called once! This requires refactor from https://github.com/cerebral/cerebral/issues/710 | ||
var runTask = FunctionTree(options.providers); | ||
return new _promise2.default(function (resolve, reject) { | ||
@@ -20,0 +21,0 @@ var onEnd = function onEnd(execution, result) { |
@@ -13,2 +13,4 @@ 'use strict'; | ||
// TODO: Refactor to use tags! | ||
var A = require('../'); | ||
@@ -15,0 +17,0 @@ var get = require('get-value'); |
'use strict'; | ||
var get = require('get-value'); | ||
var set = require('set-value'); | ||
var _typeof2 = require('babel-runtime/helpers/typeof'); | ||
module.exports = function (targetTemplate, valueTemplate) { | ||
var _typeof3 = _interopRequireDefault(_typeof2); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
module.exports = function (target, value) { | ||
return function set(ctx) { | ||
var value = getValue(ctx, valueTemplate); | ||
setValue(ctx, targetTemplate, value); | ||
setValue(ctx, target, getValue(ctx, value)); | ||
}; | ||
}; | ||
function isContextPath(value) { | ||
var regexp = /^[a-z]+:/gi; | ||
return regexp.test(value); | ||
} | ||
function getValue(ctx, path) { | ||
if (typeof path === 'function') { | ||
path = path(ctx); | ||
function getValue(ctx, resolver) { | ||
if (typeof resolver === 'function') { | ||
return resolver(ctx); | ||
} | ||
if (isContextPath(path)) { | ||
return get(ctx, path.replace(':', '.')); | ||
if ((typeof resolver === 'undefined' ? 'undefined' : (0, _typeof3.default)(resolver)) === 'object') { | ||
return resolver.getValue(ctx); | ||
} | ||
if (typeof path === 'string') { | ||
return path.replace(/\\:/g, ':'); | ||
} | ||
return path; | ||
return resolver; | ||
} | ||
function setValue(ctx, path, value) { | ||
if (typeof path === 'function') { | ||
path = path(ctx); | ||
function setValue(ctx, resolver, value) { | ||
if (typeof resolver === 'function') { | ||
resolver(ctx, value); | ||
} | ||
return set(ctx, path.replace(':', '.'), value); | ||
if ((typeof resolver === 'undefined' ? 'undefined' : (0, _typeof3.default)(resolver)) === 'object') { | ||
resolver.setValue(ctx, value); | ||
} | ||
} |
'use strict'; | ||
var _typeof2 = require('babel-runtime/helpers/typeof'); | ||
var _typeof3 = _interopRequireDefault(_typeof2); | ||
var _keys = require('babel-runtime/core-js/object/keys'); | ||
@@ -9,4 +13,2 @@ | ||
var get = require('get-value'); | ||
module.exports = function (test) { | ||
@@ -16,2 +18,3 @@ return function when(ctx) { | ||
var path = getPath(ctx, value); | ||
if (typeof path !== 'function') { | ||
@@ -30,13 +33,15 @@ var paths = (0, _keys2.default)(ctx.path).join(', '); | ||
function getValue(ctx, test) { | ||
if (typeof test === 'function') { | ||
return test(ctx); | ||
function getValue(ctx, resolver) { | ||
if (typeof resolver === 'function') { | ||
return resolver(ctx); | ||
} | ||
if ((typeof resolver === 'undefined' ? 'undefined' : (0, _typeof3.default)(resolver)) === 'object') { | ||
return resolver.getValue(ctx); | ||
} | ||
return get(ctx, test.replace(':', '.')); | ||
return resolver; | ||
} | ||
function getPath(ctx, value) { | ||
var truthy = isTruthy(ctx.path); | ||
if (truthy) { | ||
if (isTruthy(ctx.path)) { | ||
return value ? ctx.path.true : ctx.path.false; | ||
@@ -43,0 +48,0 @@ } |
{ | ||
"name": "arnie", | ||
"version": "0.2.17", | ||
"version": "0.2.18", | ||
"description": "Action hero for backends", | ||
@@ -9,6 +9,7 @@ "main": "index.js", | ||
"pretest-disabled": "npm run lint", | ||
"test": "tape 'test/**/*.js'", | ||
"test": "tap 'test/**/*.test.js' --coverage", | ||
"test:one": "tap --coverage", | ||
"test:watch": "watch 'npm test' --ignoreDirectoryPattern='/node_modules/i' --ignoreDotFiles", | ||
"prebuild": "npm test", | ||
"build": "rimraf lib && BABEL_ENV=production babel src/ --out-dir=lib/", | ||
"postbuild": "npm test", | ||
"prepublish": "npm run build" | ||
@@ -32,3 +33,4 @@ }, | ||
"escape-html": "^1.0.3", | ||
"fresh": "^0.3.0", | ||
"fresh": "^0.5.0", | ||
"function-tree": "^0.5.0-b-alpha.22884964", | ||
"get-value": "^2.0.6", | ||
@@ -48,7 +50,7 @@ "koa-is-json": "^1.0.0", | ||
"rimraf": "^2.5.4", | ||
"standard": "^8.5.0", | ||
"supertest": "^2.0.1", | ||
"tape": "^4.6.2", | ||
"standard": "^9.0.0", | ||
"supertest": "^3.0.0", | ||
"tap": "^10.3.0", | ||
"watch": "^1.0.1" | ||
} | ||
} |
352
README.md
# ATTENTION PLEASE: I have just changed everything and refactored from the ground up (WIP) - DO NOT USE! | ||
# KOA Arnie | ||
# Arnie | ||
**A real action hero for backends - heavily inspired by [CerebralJS](http://www.cerebraljs.com/)** | ||
**A [CerebralJS](http://www.cerebraljs.com/) action hero for backends** | ||
[![NPM version][npm-image]][npm-url] | ||
Arnie wants action! Actions are structured in tasks and you compose them like legos to make things happen. Arnie is meant to be used with Koa2, but the core will work without it. The API is currently likely to change, so be adventures when using Arnie :) | ||
## Operators | ||
Here is what a task looks like. | ||
Best case: most operators are provided by function-tree. | ||
```javascript | ||
export default [ | ||
loadWishlist({shared: true}), { | ||
error: [ | ||
fail(404, 'Wish list not found.') | ||
] | ||
}, | ||
createTempFolder('wishlist-'), | ||
copyPreviewTemplate(), | ||
loadProductImages({maxImages: 8}), { | ||
error: [ | ||
fail(404, 'Product images not found.') | ||
] | ||
}, | ||
createPreviewMarkup(), | ||
renderPreviewMarkup(), | ||
send('input.previewImage'), | ||
removeTempFolder() | ||
] | ||
``` | ||
## Adapters | ||
Guess you can somewhat grasp what this task does just from reading it and that's the whole point here. Arnie is not about writing magic code with super powers - he is all about expressing complex things with simple words - and this is totally stolen from [CerebralJS](http://www.cerebraljs.com/). | ||
This is where function-tree gets connected to different environments. | ||
## Folder structure | ||
- express | ||
- koa | ||
- hapi | ||
- rpc | ||
- http | ||
- serverless | ||
This sample structure will be used throughout the docs and is a good way to start structuring your project. | ||
``` | ||
src/ | ||
├── actions/ | ||
│ ├── loadWishlist.js | ||
│ ├── createTempFolder.js | ||
│ ├── copyPreviewTemplate.js | ||
│ ├── ... | ||
│ └── send.js | ||
├── tasks/ | ||
│ └── showPreviewImage.js | ||
└── app.js | ||
``` | ||
## Building tasks | ||
Tasks are defined in arrays an consist of three things | ||
- **Actions** are defined in functions | ||
- **Paths** are defined by objects | ||
- **Tasks** are defined in arrays | ||
Yes tasks can contain other tasks :) | ||
Enough with the smart talk - let's get coding. | ||
```javascript | ||
// src/tasks/showPreviewImage.js | ||
import {fail} from 'arnie/addons' | ||
import copyPreviewTemplate from '../actions/copyPreviewTemplate' | ||
import createTempFolder from '../actions/createTempFolder' | ||
import createPreviewMarkup from '../actions/createPreviewMarkup' | ||
import loadProductImages from '../actions/loadProductImages' | ||
import loadWishlist from '../actions/loadWishlist' | ||
import removeTempFolder from '../actions/createTempFolder' | ||
import renderPreviewMarkup from '../actions/renderPreviewMarkup' | ||
import send from '../actions/send' | ||
export default [ | ||
loadWishlist({shared: true}), { | ||
error: [ | ||
fail(404, 'Wish list not found.') | ||
] | ||
}, | ||
createTempFolder('wishlist-'), | ||
copyPreviewTemplate(), | ||
loadProductImages({maxImages: 8}), { | ||
error: [ | ||
fail(404, 'Product images not found.') | ||
] | ||
}, | ||
createPreviewMarkup(), | ||
renderPreviewMarkup(), | ||
send('input.previewImage', 31536000), | ||
removeTempFolder() | ||
] | ||
``` | ||
This is a sample tasks - it's an array at core and holds a mix of functions, objects and other arrays. Each of those functions calls are factories for actions, they create a preconfigured function that get will get called when the tasks is run. The objects define paths, which can be triggered at the end of an action. When a path gets triggered another tasks is run - those are the other arrays in the example above. | ||
# Implementing actions | ||
Actions are simple functions. They get one argument (it's called context) and they return an object (let's call it output). That's about it. When using tasks as Koa2 middle the conext object will be the [Koa conext](https://github.com/koajs/koa/blob/v2.x/docs/api/context.md). | ||
```javascript | ||
// src/actions/createTempFolder.js | ||
import fs from 'mz/fs' | ||
import shortid from 'shortid' | ||
export default (prefix) => { | ||
return async function createTempFolder (ctx) { | ||
const tempFolder = `/tmp/${prefix}${shortid.generate()}` | ||
await fs.mkdir(tempFolder) | ||
return { | ||
tempFolder | ||
} | ||
} | ||
} | ||
``` | ||
This action factory creates the actual `createTempFolder()` action by returning it as a function. This function will be called within the task. It's handy to use a factory, because they allow you configure your actions when building tasks. | ||
In this example the action is an async function, but this is entirely up to you. I personally tend to use them a lot on the server, thanks to Koa2. It's needless to say that you will have to transpile your code when using these kind of features. | ||
The action's output will be an Object containing the folders path e.g. `{tempFolder: /tmp/wishlist-d4th1s}`. | ||
## Using paths | ||
You have already seen a path definition and trigger in the examples before - let's take a closer look. | ||
```javascript | ||
export default [ | ||
loadWishlist({shared: true}), { | ||
wishlist: [ | ||
send('input.wishlist') | ||
], | ||
error: [ | ||
fail(404, 'Wish list not found.') | ||
] | ||
} | ||
] | ||
``` | ||
In the example the defined paths are `wishlist` and `error`. Each path defines a tasks that gets run when the path is triggered. A path gets triggered when an action returns an object with a key that matches a path. | ||
```javascript | ||
export default ({id = 'input.wishlistId', shared = false}) => { | ||
return async function loadWishlist (ctx) { | ||
const wishlistId = get(ctx, id) | ||
const wishlist = await db.collection('wishlists').findOne({ | ||
permaId: ObjectId(wishlistId), | ||
shared | ||
}) | ||
if (!wishlist) { | ||
return { | ||
error: true | ||
} | ||
} | ||
return { | ||
wishlist | ||
} | ||
} | ||
} | ||
``` | ||
This action could trigger two paths `wishlist` and `error`. There is one other scenario that could trigger a path - it's when an exceptions is thrown. An uncaught exception would also trigger the path `error`. | ||
## Input & output | ||
When an actions returns output, the output will be shallow merged into `context.input`. That allows actions to pass along data, which can then be used by other actions. The `context.input` object should only be read and never directly mutated. | ||
## Koa2 middleware | ||
Arnie can take tasks and run them as Koa2 middlware. All tasks will be executed on every request and pass the Koa conext to your actions. | ||
```javascript | ||
// src/app.js | ||
import Koa from 'koa' | ||
import Arnie from 'arnie' | ||
import {route, restEndpoint, show, status} from 'arnie/addons' | ||
import showPreviewImage from './tasks/showPreviewImage' | ||
const app = new Koa() | ||
const arnie = Arnie() | ||
const tasks = { | ||
wishlistPreview: [ | ||
route({ | ||
'/wishlist-preview/health': [ | ||
status(200), | ||
show('request.path') | ||
], | ||
'/wishlist-preview/:wishlistId?': [ | ||
restEndpoint({ | ||
show: showPreviewImage | ||
}) | ||
] | ||
}) | ||
] | ||
} | ||
app.use(arnie.koa(tasks)) | ||
app.listen(80) | ||
``` | ||
You can see that Arnie comes with addons that will help you with the most common use cases. | ||
## Addons: batteries included | ||
Arnie comes with a bunch off legos, which are supposed to assist you in building your next backend application. Each addon will be explained with a short task definition - look into the [tests](https://github.com/maxfrigge/arnie/tree/master/test/addons) to find out more. | ||
### addon/accepts | ||
```javascript | ||
export default [ | ||
accepts({ | ||
json: [ | ||
sendSomeJSON() | ||
], | ||
otherwiss: [fail(406)] | ||
}) | ||
] | ||
``` | ||
### addon/cors | ||
```javascript | ||
export default [ | ||
cors('get, post', '*') | ||
] | ||
``` | ||
### addon/fail | ||
```javascript | ||
export default [ | ||
loadWishlist(), { | ||
error: fail(404, 'Wishlist not found.') | ||
} | ||
] | ||
``` | ||
### addon/header | ||
```javascript | ||
export default [ | ||
header({ | ||
'Cache-Control': 'no-cache' | ||
}) | ||
] | ||
``` | ||
### addon/method | ||
```javascript | ||
export default [ | ||
method({ | ||
get: [ | ||
showWishlist() | ||
], | ||
delete: [ | ||
removeWishlist() | ||
], | ||
otherwise: [ | ||
fail(405) | ||
] | ||
}) | ||
] | ||
``` | ||
### addon/redirect | ||
```javascript | ||
export default [ | ||
redirect('/redirect-target') | ||
] | ||
``` | ||
### addon/restEndpoint | ||
Is required to be used with route. | ||
```javascript | ||
export default [ | ||
route('/resource/:id?', [ | ||
restEndpoint({ | ||
list: [], // GET /resource | ||
show: [], // GET /resource/123 | ||
create: [], // POST /resource | ||
update: [], // POST /resource/123 | ||
clear: [], // DELETE /resource | ||
remove: [] // DELETE /resource/123 | ||
}) | ||
]) | ||
] | ||
``` | ||
Note to self: it should probably be combined in to: | ||
```javascript | ||
export default [ | ||
restEndpoint('/resource/:id?', { | ||
list: [], // GET /resource | ||
show: [], // GET /resource/123 | ||
create: [], // POST /resource | ||
update: [], // POST /resource/123 | ||
clear: [], // DELETE /resource | ||
remove: [] // DELETE /resource/123 | ||
}) | ||
] | ||
``` | ||
### addon/route | ||
The route addon uses [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and merges the url params into `context.input`. | ||
```javascript | ||
export default [ | ||
route({ | ||
'/route/abc': [], | ||
'/with/:param/in-path': [], | ||
otherwise: [ | ||
fail(404, 'Route not found.') | ||
] | ||
}) | ||
] | ||
``` | ||
### addon/show | ||
```javascript | ||
export default [ | ||
createWishlist(), | ||
show('input.wishlist') | ||
] | ||
``` | ||
### addon/status | ||
```javascript | ||
export default [ | ||
createWishlist(), | ||
status(201) | ||
] | ||
``` | ||
### addon/when | ||
```javascript | ||
export default [ | ||
when('request.query.foo', { | ||
bar: [], | ||
otherwise: [] | ||
}) | ||
] | ||
``` | ||
[npm-image]: https://img.shields.io/npm/v/arnie.svg?style=flat | ||
[npm-url]: https://npmjs.org/package/arnie |
@@ -1,7 +0,8 @@ | ||
const FunctionTree = require('./function-tree').default | ||
const FunctionTree = require('function-tree').default | ||
module.exports = (options = {}) => { | ||
// TODO: Addd default context provider | ||
const runTask = FunctionTree(options.providers) | ||
return (task, payload) => { | ||
// TODO: factory should only be called once! This requires refactor from https://github.com/cerebral/cerebral/issues/710 | ||
const runTask = FunctionTree(options.providers) | ||
return new Promise((resolve, reject) => { | ||
@@ -8,0 +9,0 @@ const onEnd = (execution, result) => { |
@@ -0,1 +1,3 @@ | ||
// TODO: Refactor to use tags! | ||
const A = require('../') | ||
@@ -2,0 +4,0 @@ const get = require('get-value') |
@@ -1,34 +0,29 @@ | ||
const get = require('get-value') | ||
const set = require('set-value') | ||
module.exports = (targetTemplate, valueTemplate) => { | ||
module.exports = (target, value) => { | ||
return function set (ctx) { | ||
const value = getValue(ctx, valueTemplate) | ||
setValue(ctx, targetTemplate, value) | ||
setValue( | ||
ctx, | ||
target, | ||
getValue(ctx, value) | ||
) | ||
} | ||
} | ||
function isContextPath (value) { | ||
const regexp = /^[a-z]+:/gi | ||
return regexp.test(value) | ||
} | ||
function getValue (ctx, path) { | ||
if (typeof path === 'function') { | ||
path = path(ctx) | ||
function getValue (ctx, resolver) { | ||
if (typeof resolver === 'function') { | ||
return resolver(ctx) | ||
} | ||
if (isContextPath(path)) { | ||
return get(ctx, path.replace(':', '.')) | ||
if (typeof resolver === 'object') { | ||
return resolver.getValue(ctx) | ||
} | ||
if (typeof path === 'string') { | ||
return path.replace(/\\:/g, ':') | ||
} | ||
return path | ||
return resolver | ||
} | ||
function setValue (ctx, path, value) { | ||
if (typeof path === 'function') { | ||
path = path(ctx) | ||
function setValue (ctx, resolver, value) { | ||
if (typeof resolver === 'function') { | ||
resolver(ctx, value) | ||
} | ||
return set(ctx, path.replace(':', '.'), value) | ||
if (typeof resolver === 'object') { | ||
resolver.setValue(ctx, value) | ||
} | ||
} |
@@ -1,3 +0,1 @@ | ||
const get = require('get-value') | ||
module.exports = (test) => { | ||
@@ -7,2 +5,3 @@ return function when (ctx) { | ||
const path = getPath(ctx, value) | ||
if (typeof path !== 'function') { | ||
@@ -21,13 +20,15 @@ const paths = Object.keys(ctx.path).join(', ') | ||
function getValue (ctx, test) { | ||
if (typeof test === 'function') { | ||
return test(ctx) | ||
function getValue (ctx, resolver) { | ||
if (typeof resolver === 'function') { | ||
return resolver(ctx) | ||
} | ||
if (typeof resolver === 'object') { | ||
return resolver.getValue(ctx) | ||
} | ||
return get(ctx, test.replace(':', '.')) | ||
return resolver | ||
} | ||
function getPath (ctx, value) { | ||
const truthy = isTruthy(ctx.path) | ||
if (truthy) { | ||
if (isTruthy(ctx.path)) { | ||
return value ? ctx.path.true : ctx.path.false | ||
@@ -34,0 +35,0 @@ } |
@@ -15,2 +15,3 @@ const supertest = require('supertest') | ||
} | ||
fn(awsEvent, awsContext, (error, response) => { | ||
@@ -23,3 +24,2 @@ // console.log('SLS: callback', error, response) | ||
} | ||
res.writeHead(response.statusCode, response.headers) | ||
@@ -26,0 +26,0 @@ res.end(response.body) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
226991
99
14
2931
27
3
+ Addedansi-regex@2.1.1(transitive)
+ Addedansi-styles@2.2.1(transitive)
+ Addedchalk@1.1.3(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedeventemitter3@2.0.3(transitive)
+ Addedfresh@0.5.2(transitive)
+ Addedfunction-tree@0.5.0-b-alpha.fb1baf7b(transitive)
+ Addedhas-ansi@2.0.0(transitive)
+ Addedoptions@0.0.6(transitive)
+ Addedstrip-ansi@3.0.1(transitive)
+ Addedsupports-color@2.0.0(transitive)
+ Addedultron@1.0.2(transitive)
+ Addedws@1.1.5(transitive)
- Removedfresh@0.3.0(transitive)
Updatedfresh@^0.5.0