Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

arnie

Package Overview
Dependencies
Maintainers
1
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

arnie - npm Package Compare versions

Comparing version 0.1.4 to 0.1.5

30

dist/core/task.js

@@ -21,17 +21,25 @@ 'use strict';

for (const action of task.actions) {
const output = yield action.fn(ctx, ...args);
if (output) {
for (const key in output) {
const value = output[key];
if (value !== _symbols.PATH_WITHOUT_VALUE) {
ctx.input[key] = value;
try {
const output = yield action.fn(ctx, ...args);
if (output) {
for (const key in output) {
const value = output[key];
if (value !== _symbols.PATH_WITHOUT_VALUE) {
ctx.input[key] = value;
}
}
}
for (const path in action.outputs) {
if (path in output) {
const outputTask = action.outputs[path];
yield runTask(outputTask, ctx, ...args);
for (const path in action.outputs) {
if (path in output) {
const outputTask = action.outputs[path];
yield runTask(outputTask, ctx, ...args);
}
}
}
} catch (error) {
if (action.outputs.error) {
yield runTask(action.outputs.error, ctx, ...args);
} else {
throw error;
}
}

@@ -38,0 +46,0 @@ }

{
"name": "arnie",
"version": "0.1.4",
"version": "0.1.5",
"description": "Action hero for backends",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -5,2 +5,344 @@ # KOA Arnie

Don't try this at home! I am currently testing and heavily changing the API. It will be released along with proper docs as soon as I have implemented it into a demo project.
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 :)
Here is what a task looks like.
```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()
]
```
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/).
## Folder structure
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: []
})
]
```

@@ -56,17 +56,25 @@ import {PATH_WITHOUT_VALUE} from './symbols'

for (const action of task.actions) {
const output = await action.fn(ctx, ...args)
if (output) {
for (const key in output) {
const value = output[key]
if (value !== PATH_WITHOUT_VALUE) {
ctx.input[key] = value
try {
const output = await action.fn(ctx, ...args)
if (output) {
for (const key in output) {
const value = output[key]
if (value !== PATH_WITHOUT_VALUE) {
ctx.input[key] = value
}
}
}
for (const path in action.outputs) {
if (path in output) {
const outputTask = action.outputs[path]
await runTask(outputTask, ctx, ...args)
for (const path in action.outputs) {
if (path in output) {
const outputTask = action.outputs[path]
await runTask(outputTask, ctx, ...args)
}
}
}
} catch (error) {
if (action.outputs.error) {
await runTask(action.outputs.error, ctx, ...args)
} else {
throw error
}
}

@@ -73,0 +81,0 @@ }

@@ -268,3 +268,3 @@ import test from 'tape'

test('Given a complex task, runTask()', (t) => {
t.plan(9)
t.plan(10)

@@ -319,3 +319,12 @@ let actionNum = 0

},
shouldRunWithInput(5, {initialInput: 'changedFoo', newData: 123, someOutput: '123'})
shouldRunWithInput(5, {initialInput: 'changedFoo', newData: 123, someOutput: '123'}),
(context) => {
throw new Error('Something bad happened')
}, {
error: [
(context) => {
t.pass('should trigger error path Error is thrown')
}
]
}
])

@@ -322,0 +331,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc