Socket
Socket
Sign inDemoInstall

effects-as-data

Package Overview
Dependencies
Maintainers
1
Versions
155
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

effects-as-data - npm Package Compare versions

Comparing version 2.5.5 to 2.5.6

38

es5/index.js

@@ -15,27 +15,25 @@ 'use strict';

var g = fn.apply(null, args);
return run(config, handlers, g);
var gen = fn.apply(null, args);
var el = newExecutionLog();
return run(config, handlers, gen, null, el);
}
function run(config, handlers, fn, input, el) {
var generatorOperation = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 'next';
var genOperation = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 'next';
try {
var el1 = getExecutionLog(el);
var _getNextOutput = getNextOutput(fn, input, generatorOperation),
var _getNextOutput = getNextOutput(fn, input, genOperation),
output = _getNextOutput.output,
isList = _getNextOutput.isList,
done = _getNextOutput.done;
if (done) return toPromise(output);
var isList = Array.isArray(output);
var commandsList = toArray(output);
return processCommands(config, handlers, commandsList, el1).then(function (results) {
return processCommands(config, handlers, commandsList, el).then(function (results) {
var unwrappedResults = unwrapResults(isList, results);
el1.step = el1.step + 1; // mutate in place
return run(config, handlers, fn, unwrappedResults, el1);
el.step++;
return run(config, handlers, fn, unwrappedResults, el, 'next');
}).catch(function (e) {
// ok to mutate?
el1.step = el1.step + 1; // mutate in place
return run(config, handlers, fn, e, el1, 'throw');
el.step++;
return run(config, handlers, fn, e, el, 'throw');
});

@@ -47,4 +45,4 @@ } catch (e) {

function getExecutionLog(el) {
return el || {
function newExecutionLog() {
return {
step: 0

@@ -65,7 +63,3 @@ };

return {
output: output,
isList: Array.isArray(output),
done: done
};
return { output: output, done: done };
}

@@ -89,3 +83,5 @@

try {
result = handlers[command.type](command, { call: call, config: config, handlers: handlers });
var handler = handlers[command.type];
if (!handler) throw new Error('Handler of type "' + command.type + '" is not registered.');
result = handler(command, { call: call, config: config, handlers: handlers });
} catch (e) {

@@ -92,0 +88,0 @@ result = Promise.reject(e);

@@ -49,2 +49,27 @@ 'use strict';

});
test('call should throw error for unregistered handler', async function () {
var fn = regeneratorRuntime.mark(function fn() {
return regeneratorRuntime.wrap(function fn$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return { type: 'dne' };
case 2:
case 'end':
return _context.stop();
}
}
}, fn, this);
});
try {
await call({}, handlers, fn);
} catch (e) {
expect(e.message).toEqual('Handler of type "dne" is not registered.');
return;
}
fail('Did not throw.');
});
//# sourceMappingURL=errors.spec.js.map

@@ -8,3 +8,4 @@ 'use strict';

handlers = _require2.handlers,
functions = _require2.functions;
functions = _require2.functions,
cmds = _require2.cmds;

@@ -50,2 +51,28 @@ var badHandler = functions.badHandler,

});
test('handlers should be able to return an array of results', async function () {
var fn = regeneratorRuntime.mark(function fn(a, b) {
var result;
return regeneratorRuntime.wrap(function fn$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return [cmds.echo(a), cmds.echo(b)];
case 2:
result = _context.sent;
return _context.abrupt('return', result);
case 4:
case 'end':
return _context.stop();
}
}
}, fn, this);
});
var actual = await call({}, handlers, fn, ['foo', 'bar'], ['foo', 'baz']);
var expected = [['foo', 'bar'], ['foo', 'baz']];
expect(actual).toEqual(expected);
});
//# sourceMappingURL=handlers.spec.js.map
{
"name": "effects-as-data",
"version": "2.5.5",
"version": "2.5.6",
"description": "Express async workflows using pure functions.",

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

@@ -15,8 +15,10 @@ # Effects-as-data

#### First, create a command creator.
This function creates a plain JSON `command` object that effects-as-data will pass to a handler function which will perform the actual HTTP request. The `type` field on the command matches the name of the handler to which it will be passed.
This function creates a plain JSON `command` object that effects-as-data will pass to a handler function which will perform the actual HTTP request. The `type` field on the command matches the name of the handler to which it will be passed (see step 4). *Note* we have not yet actually implemented the function that will actual do the HTTP GET request, we have just defined a `command`. The command is placed on the `cmds` object for convenience.
```js
function httpGetCommand(url) {
return {
type: 'httpGet',
url
const cmds = {
httpGet(url) {
return {
type: 'httpGet',
url
}
}

@@ -26,7 +28,22 @@ }

#### Second, write your business logic.
Effects-as-data uses a generator function's ability to give up execution flow and to pass a value to an outside process using the `yield` keyword. You create `command` objects in your business logic and `yield` them to effects-as-data.
#### Second, test your business logic.
Write a test for `getPeople` function that you are about to create. These tests can be used stand-alone or in any test runner like Jest, Mocha, etc. There are a few ways to test `effects-as-data` functions demonstrated below.
Semantic test example:
```js
const { testFn, args } = require('effects-as-data/test')
testFn(getPeople, () => {
const apiResults = { results: [{ name: 'Luke Skywalker' }] }
return args()
.yieldCmd(cmds.httpGet('https://swapi.co/api/people')).yieldReturns(apiResults)
.returns(['Luke Skywalker'])
})()
```
#### Third, write your business logic.
Effects-as-data uses a generator function's ability to give up execution flow and to pass a value to an outside process using the `yield` keyword. You create `command` objects in your business logic and `yield` them to `effects-as-data`. It is important to understand that when using effects-as-data that your business logic never actually `httpGet`'s anything. It ONLY creates plain JSON objects and `yield`'s them out (`cmds.httpGet()` simply returns the JSON object from step 1). This is one of the main reasons `effects-as-data` functions are easy to test.
```js
function* getPeople() {
const { results } = yield httpGetCommand('https://swapi.co/api/people')
const { results } = yield cmds.httpGet('https://swapi.co/api/people')
const names = results.map(p => p.name)

@@ -37,12 +54,14 @@ return names

#### Third, create a command handler.
After the `command` object is `yield`ed, effects-as-data will pass it to a handler function that will perform the side-effect producing operation (in this case, an HTTP GET request).
#### Fourth, create a command handler.
After the `command` object is `yield`ed, effects-as-data will pass it to a handler function that will perform the side-effect producing operation (in this case, an HTTP GET request). This is the function mentioned in step 1 that actually performs the HTTP GET request. Notice that the business logic does not call this function directly; the business logic in step 1 simply `yield`s the `httpGet` `command` out, and `effects-as-data` takes care of getting it to the handler.
```js
function httpGetHandler(cmd) {
return fetch(cmd.url).then(r => r.json())
const handlers = {
httpGet(cmd) {
return fetch(cmd.url).then(r => r.json())
}
}
```
#### Fourth, setting up monitoring / telemetry.
The effects-as-data config accepts an `onCommandComplete` callback which will be called every time a `command` completes, giving detailed information about the operation. This data can be logged to the console or sent to a logging service.
#### Fifth, optionally setting up monitoring / telemetry.
The effects-as-data config accepts an `onCommandComplete` callback which will be called every time a `command` completes, giving detailed information about the operation. This data can be logged to the console or sent to a logging service. *Note*, this step is optional.
```js

@@ -56,13 +75,9 @@ const config = {

#### Fifth, wire everything up.
#### Sixth, wire everything up.
This will turn your effects-as-data functions into normal, promise-returning functions. In this case, `functions` will be an object with one key, `getPeople`, which will be a promise-returning function.
```js
const functions = buildFunctions(
config,
{ httpGet: httpGetHandler },
{ getPeople }
)
const functions = buildFunctions(config, handlers, { getPeople })
```
#### Sixth, use your functions.
#### Lastly, use your functions.
Once you have built your functions, you can use them like normal promise-returning functions anywhere in your application.

@@ -81,27 +96,35 @@ ```js

Turn your effects-as-data functions into normal promise-returning functions.
#### Full Example
### Full Example
See full example in the `effects-as-data-examples` repository: [https://github.com/orourkedd/effects-as-data-examples/blob/master/basic/index.js](https://github.com/orourkedd/effects-as-data-examples/blob/master/basic/index.js).
You can run this example by cloning `https://github.com/orourkedd/effects-as-data-examples` and running `npm run basic`.
### Using existing commands and handlers
This example demonstrates using the `effects-as-data-universal` module which contains commands/handler that can be used anywhere Javascript runs.
Full example: [https://github.com/orourkedd/effects-as-data-examples/blob/master/basic-existing-handlers/index.js](https://github.com/orourkedd/effects-as-data-examples/blob/master/basic-existing-handlers/index.js).
Run it: Clone `https://github.com/orourkedd/effects-as-data-examples` and run `npm run basic-existing-handlers`.
```js
const { call, buildFunctions } = require('effects-as-data')
const fetch = require('isomorphic-fetch')
const { testFn, args } = require('effects-as-data/test')
const { cmds, handlers } = require('effects-as-data-universal')
function httpGetCommand(url) {
return {
type: 'httpGet',
url
}
}
function httpGetHandler(cmd) {
return fetch(cmd.url).then(r => r.json())
}
function* getPeople() {
const { results } = yield httpGetCommand('https://swapi.co/api/people')
const names = results.map(p => p.name)
const { payload } = yield cmds.httpGet('https://swapi.co/api/people')
const names = payload.results.map(p => p.name)
return names
}
// Semantic test style
testFn(getPeople, () => {
const apiResults = { payload: { results: [{ name: 'Luke Skywalker' }] } }
// prettier-ignore
return args()
.yieldCmd(cmds.httpGet('https://swapi.co/api/people')).yieldReturns(apiResults)
.returns(['Luke Skywalker'])
})()
const config = {

@@ -113,7 +136,3 @@ onCommandComplete: telemetry => {

const functions = buildFunctions(
config,
{ httpGet: httpGetHandler },
{ getPeople }
)
const functions = buildFunctions(config, handlers, { getPeople })

@@ -130,34 +149,134 @@ functions

### Using existing commands and handlers
### Error handling
This example demonstrates handling errors with `either`. Unlike the above examples, this example has been separated into a few files showing more what production code looks like.
Full example: [https://github.com/orourkedd/effects-as-data-examples/tree/master/error-handling](https://github.com/orourkedd/effects-as-data-examples/tree/master/error-handling).
Run it: Clone `https://github.com/orourkedd/effects-as-data-examples` and run `npm run error-handling`.
Below is the `getPeople` function. Notice the use of `cmds.either`. The `either` handler will process the `httpGet` command, and if the command is successful, will return the response. If the `httpGet` command fails or returns a falsey value, the `either` handler will return `emptyResults`. Because the `either` handler will never throw an exception and will either return a successful result or `emptyResults`, there is no need for an `if` statement to ensure success before the `map`. Using this pattern will reduce the number of code paths and simplify code.
```js
const { call, buildFunctions } = require('../index')
const { cmds, handlers } = require('effects-as-data-universal')
const { cmds } = require('effects-as-data-universal')
// Pure business logic functions
function* getUsers() {
return yield cmds.httpGet('http://example.com/api/users')
function* getPeople() {
const httpGet = cmds.httpGet('https://swapi.co/api/people')
const emptyResults = { payload: { results: [] } }
const { payload } = yield cmds.either(httpGet, emptyResults)
return payload.results.map(p => p.name)
}
function* getUserPosts(userId) {
return yield cmds.httpGet(`http://example.com/api/user/${userId}/posts`)
}
module.exports = getPeople
```
// Use onCommandComplete to gather telemetry
const config = {
onCommandComplete: telemetry => {
console.log('Telemetry:', telemetry)
}
Tests for the `getPeople` function. These tests are using Jest:
```js
const { cmds } = require('effects-as-data-universal')
const { testFn, args } = require('effects-as-data/test')
const getPeople = require('./get-people')
const testGetPeople = testFn(getPeople)
test(
"getPeople should return a list of people's names",
testGetPeople(() => {
const apiResults = { payload: { results: [{ name: 'Luke Skywalker' }] } }
const httpGet = cmds.httpGet('https://swapi.co/api/people')
const emptyResults = { payload: { results: [] } }
// prettier-ignore
return args()
.yieldCmd(cmds.either(httpGet, emptyResults)).yieldReturns(apiResults)
.returns(['Luke Skywalker'])
})
)
test(
'getPeople should return an empty list if http get errors out',
testGetPeople(() => {
const apiResults = { payload: { results: [{ name: 'Luke Skywalker' }] } }
const httpGet = cmds.httpGet('https://swapi.co/api/people')
const emptyResults = { payload: { results: [] } }
// prettier-ignore
return args()
.yieldCmd(cmds.either(httpGet, emptyResults)).yieldReturns(emptyResults)
.returns([])
})
)
```
The index file that runs it. `onCommandComplete` is removed for brevity:
```js
const { call, buildFunctions } = require('effects-as-data')
const { handlers } = require('effects-as-data-universal')
const getPeople = require('./get-people')
const functions = buildFunctions({}, handlers, { getPeople })
functions
.getPeople()
.then(names => {
console.log('Function Results:')
console.log(names.join(', '))
})
.catch(console.error)
```
### Parallelization of commands
Full example: [https://github.com/orourkedd/effects-as-data-examples/tree/master/parallelization](https://github.com/orourkedd/effects-as-data-examples/tree/master/parallelization).
Run it: Clone `https://github.com/orourkedd/effects-as-data-examples` and run `npm run parallelization`.
```js
const { cmds } = require('effects-as-data-universal')
function* getPeople(person1, person2) {
const httpGet1 = cmds.httpGet(`https://swapi.co/api/people/${person1}`)
const httpGet2 = cmds.httpGet(`https://swapi.co/api/people/${person2}`)
const [result1, result2] = yield [httpGet1, httpGet2]
return [result1.payload, result2.payload].map(p => p.name)
}
// Turn effects-as-data functions into normal,
// promise-returning functions
const functions = buildFunctions(
config,
handlers, // command handlers
{ getUsers, getUserPosts } // effects-as-data functions
module.exports = getPeople
```
Tests for the `getPeople` function. These tests are using Jest:
```js
const { cmds } = require('effects-as-data-universal')
const { testFn, args } = require('effects-as-data/test')
const getPeople = require('./get-people')
const testGetPeople = testFn(getPeople)
test(
"getPeople should return a list of people's names",
testGetPeople(() => {
const apiResult1 = { payload: { name: 'Luke Skywalker' } }
const apiResult2 = { payload: { name: 'C-3PO' } }
const httpGet1 = cmds.httpGet('https://swapi.co/api/people/1')
const httpGet2 = cmds.httpGet('https://swapi.co/api/people/2')
// prettier-ignore
return args(1, 2)
.yieldCmd([httpGet1, httpGet2]).yieldReturns([apiResult1, apiResult2])
.returns(['Luke Skywalker', 'C-3PO'])
})
)
```
// Use the functions like you normally would
functions.getUsers().then(console.log)
The index file that runs it. `onCommandComplete` is removed for brevity:
```js
const { call, buildFunctions } = require('effects-as-data')
const { handlers } = require('effects-as-data-universal')
const getPeople = require('./get-people')
const functions = buildFunctions({}, handlers, { getPeople })
functions
.getPeople(1, 2)
.then(names => {
console.log('Function Results:')
console.log(names.join(', '))
})
.catch(console.error)
```

@@ -5,26 +5,22 @@ const { isGenerator, toArray, toPromise } = require('./util')

if (!fn) return Promise.reject(new Error('A function is required.'))
const g = fn.apply(null, args)
return run(config, handlers, g)
const gen = fn.apply(null, args)
const el = newExecutionLog()
return run(config, handlers, gen, null, el)
}
function run(config, handlers, fn, input, el, generatorOperation = 'next') {
function run(config, handlers, fn, input, el, genOperation = 'next') {
try {
const el1 = getExecutionLog(el)
const { output, isList, done } = getNextOutput(
fn,
input,
generatorOperation
)
const { output, done } = getNextOutput(fn, input, genOperation)
if (done) return toPromise(output)
const isList = Array.isArray(output)
const commandsList = toArray(output)
return processCommands(config, handlers, commandsList, el1)
return processCommands(config, handlers, commandsList, el)
.then(results => {
const unwrappedResults = unwrapResults(isList, results)
el1.step = el1.step + 1 // mutate in place
return run(config, handlers, fn, unwrappedResults, el1)
el.step++
return run(config, handlers, fn, unwrappedResults, el, 'next')
})
.catch(e => {
// ok to mutate?
el1.step = el1.step + 1 // mutate in place
return run(config, handlers, fn, e, el1, 'throw')
el.step++
return run(config, handlers, fn, e, el, 'throw')
})

@@ -36,8 +32,6 @@ } catch (e) {

function getExecutionLog(el) {
return (
el || {
step: 0
}
)
function newExecutionLog() {
return {
step: 0
}
}

@@ -51,7 +45,3 @@

const { value: output, done } = fn[op](input)
return {
output,
isList: Array.isArray(output),
done
}
return { output, done }
}

@@ -73,3 +63,6 @@

try {
result = handlers[command.type](command, { call, config, handlers })
const handler = handlers[command.type]
if (!handler)
throw new Error(`Handler of type "${command.type}" is not registered.`)
result = handler(command, { call, config, handlers })
} catch (e) {

@@ -76,0 +69,0 @@ result = Promise.reject(e)

@@ -42,1 +42,14 @@ const { call } = require('../index')

})
test('call should throw error for unregistered handler', async () => {
const fn = function*() {
yield { type: 'dne' }
}
try {
await call({}, handlers, fn)
} catch (e) {
expect(e.message).toEqual('Handler of type "dne" is not registered.')
return
}
fail('Did not throw.')
})
const { call } = require('../index')
const { handlers, functions } = require('./effects')
const { handlers, functions, cmds } = require('./effects')
const {

@@ -42,1 +42,11 @@ badHandler,

})
test('handlers should be able to return an array of results', async () => {
const fn = function*(a, b) {
const result = yield [cmds.echo(a), cmds.echo(b)]
return result
}
const actual = await call({}, handlers, fn, ['foo', 'bar'], ['foo', 'baz'])
const expected = [['foo', 'bar'], ['foo', 'baz']]
expect(actual).toEqual(expected)
})

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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