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

clues

Package Overview
Dependencies
Maintainers
2
Versions
158
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

clues - npm Package Compare versions

Comparing version 3.6.0-rc5 to 3.6.0

test/class-fn-test.js

2

bower.json
{
"name": "clues",
"version": "3.5.24",
"version": "3.5.45",
"main": "clues.js",

@@ -5,0 +5,0 @@ "scripts": [

@@ -10,25 +10,11 @@ (function(self) {

// Make 3.x branch future compatible
clues.reject = function(d) {
return clues.Promise.reject(d);
};
var reArgs = /^\s*function.*?\(([^)]*?)\).*/;
var reEs6 = /^\s*\({0,1}(.*?)\){0,1}\s*=>/;
var reEs6 = /^\s*\({0,1}([^)]*?)\){0,1}\s*=>/;
var reEs6Class = /^\s*[a-zA-Z0-9\-\$\_]+\((.*?)\)\s*{/;
function checkCircular(d,value) {
var checked = [],circular;
return (function check(c) {
if (circular || !c || checked.indexOf(c) !== -1) return;
checked.push(c);
if (c == value)
return (circular = true);
if (c._cancellationParent)
check(c._cancellationParent);
if (c._onCancelField && c._onCancelField._values)
[].concat(c._onCancelField._values || []).forEach(check);
if (c._followee && c._followee())
check(c._followee());
})(d) || circular;
}
function matchArgs(fn) {

@@ -38,6 +24,10 @@ if (!fn.__args__) {

match = match.replace(/^\s*async/,'');
match = reArgs.exec(match) || reEs6.exec(match);
fn.__args__ = match[1].replace(/\s/g,'')
match = reArgs.exec(match) || reEs6.exec(match) || reEs6Class.exec(match);
fn.__args__ = !match ? [] : match[1].replace(/\s/g,'')
.split(',')
.filter(function(d) {
if (d === '$private')
fn.private = true;
if (d === '$prep')
fn.prep = true;
return d.length;

@@ -49,3 +39,3 @@ });

function clues(logic,fn,$global,caller,fullref,last) {
function clues(logic,fn,$global,caller,fullref) {
var args,ref;

@@ -56,5 +46,5 @@

if (typeof logic === 'function' || (logic && typeof logic.then === 'function'))
return clues({},logic,$global,caller,fullref,last)
return clues({},logic,$global,caller,fullref)
.then(function(logic) {
return clues(logic,fn,$global,caller,fullref,last);
return clues(logic,fn,$global,caller,fullref);
});

@@ -68,3 +58,3 @@

var next = ref.slice(0,dot);
return clues(logic,next,$global,caller,fullref,last)
return clues(logic,next,$global,caller,fullref)
.then(function(d) {

@@ -74,7 +64,7 @@ logic = d;

fullref = (fullref ? fullref+'.' : '')+next;
return clues(logic,ref,$global,caller,fullref,last);
return clues(logic,ref,$global,caller,fullref);
})
.catch(function(e) {
if (e && e.notDefined && logic && logic.$external && typeof logic.$external === 'function')
return logic[ref] = logic[ref] || clues(logic,function() { return logic.$external.call(logic,ref); },$global,caller,(fullref ? fullref+'.' : '')+ref);
return logic[ref] = logic[ref] || clues(logic,function() { return logic.$external.call(logic,ref); },$global,ref,(fullref ? fullref+'.' : '')+ref);
else throw e;

@@ -84,3 +74,4 @@ });

fullref = (fullref ? fullref+'.' : '')+ref;
var separator = fullref && fullref[fullref.length-1] !== '(' && '.' || '';
fullref = (fullref ? fullref+separator : '')+ref;
fn = logic ? logic[ref] : undefined;

@@ -91,3 +82,3 @@ if (fn === undefined) {

else if ($global[ref] && caller && caller !== '__user__')
return clues($global,ref,$global,caller,fullref,last);
return clues($global,ref,$global,caller,fullref);
else if (logic && logic.$property && typeof logic.$property === 'function')

@@ -105,3 +96,7 @@ fn = logic[ref] = function() { return logic.$property.call(logic,ref); };

if (fn.length === 1) fn = fn[0];
return clues(obj,fn,$global,caller,fullref,last);
var result = clues(obj,fn,$global,caller,fullref);
if (ref) {
logic[ref] = result;
}
return result;
}

@@ -117,11 +112,11 @@ args = fn.slice(0,fn.length-1);

if (typeof fn === 'function')
args = (args || matchArgs(fn));
// If fn name is private or promise private is true, reject when called directly
if (fn && (!caller || caller == '__user__') && ((typeof(fn) === 'function' && (fn.name == '$private' || fn.name == 'private')) || (fn.then && fn.private)))
if (fn && (!caller || caller == '__user__') && ((typeof(fn) === 'function' && (fn.private || fn.name == '$private' || fn.name == 'private')) || (fn.then && fn.private)))
return clues.Promise.reject({ref : ref, message: ref+' not defined', fullref:fullref,caller: caller, notDefined:true});
// If the logic reference is not a function, we simply return the value
if (typeof fn !== 'function' || (ref && ref[0] === '$')) {
if (fn && fn._cancellationParent && !clues.ignoreCircular && fn.isPending && fn.isPending() && checkCircular(fn,last))
return clues.Promise.rejected({ref: ref, message: 'circular', fullref:fullref, caller: caller});
if (typeof fn !== 'function' || ((ref && ref[0] === '$') && !fn.prep && fn.name !== '$prep')) {
// If the value is a promise we wait for it to resolve to inspect the result

@@ -138,6 +133,7 @@ if (fn && typeof fn.then === 'function')

// Shortcuts to define empty objects with $property or $external
if (fn.name == '$property') return logic[ref] = clues.Promise.resolve({$property: fn.bind(logic)});
if (fn.name == '$external') return logic[ref] = clues.Promise.resolve({$external: fn.bind(logic)});
args = clues.Promise.map(args || matchArgs(fn),function(arg) {
if (fn.name === '$property' || (args[0] === '$property' && args.length === 1)) return logic[ref] = clues.Promise.resolve({$property: fn.bind(logic)});
if (fn.name === '$external' || (args[0] === '$external' && args.length === 1)) return logic[ref] = clues.Promise.resolve({$external: fn.bind(logic)});
if (fn.name === '$service') return fn;
args = args.map(function(arg) {
var optional,showError,res;

@@ -154,8 +150,9 @@ if (optional = (arg[0] === '_')) arg = arg.slice(1);

res = clues.Promise.resolve($global);
else if (arg === '$private')
res = fn.private = true;
else if (arg === '$prep')
res = fn.prep = true;
}
return res || clues.Promise.resolve()
.then(function() {
return clues(logic,arg,$global,ref || 'fn',fullref,value);
})
return res || clues(logic,arg,$global,ref || 'fn',fullref+'(')
.then(null,function(e) {

@@ -178,2 +175,11 @@ if (optional) return (showError) ? e : undefined;

.catch(function(e) {
// If fn is a class we solve for the constructor variables (if defined) and return a new instance
if (e instanceof TypeError && /^Class constructor/.exec(e.message)) {
args = (/constructor\s*\((.*?)\)/.exec(fn.toString()) || [])[1];
args = args ? args.split(',').map(function(d) { return d.trim(); }) : [];
return [logic].concat(args).concat(function() {
args = [null].concat(Array.prototype.slice.call(arguments));
return new (Function.prototype.bind.apply(fn,args));
});
}
if (e && e.stack && typeof $global.$logError === 'function')

@@ -186,3 +192,3 @@ $global.$logError(e, fullref);

if (typeof $global.$duration === 'function')
$global.$duration(fullref || ref || (fn && fn.name),[(Date.now()-duration),(Date.now())-wait]);
$global.$duration(fullref || ref || (fn && fn.name),[(Date.now()-duration),(Date.now())-wait],ref);
})

@@ -204,3 +210,3 @@ .then(function(d) {

if (fn.name == 'private' || fn.name == '$private')
if (fn.private || fn.name == 'private' || fn.name == '$private')
value.private = true;

@@ -229,2 +235,2 @@

})(this);
})(this);
{
"name": "clues",
"version": "3.5.30",
"version": "3.6.0-rc5",
"version": "3.6.0",
"description": "Lightweight logic tree solver using promises.",

@@ -18,11 +17,11 @@ "keywords": [

"dependencies": {
"bluebird": "~3.4.0"
"bluebird": "~3.5.0"
},
"devDependencies": {
"mocha": "~2.5.3"
"tap": "~10.3.2"
},
"license": "MIT",
"scripts": {
"test": "./node_modules/mocha/bin/mocha"
"test": "tap test --jobs=10 -Rspec --coverage-report=html --no-browser"
}
}
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Test Coverage][circle-image]][circle-url]
[![Coverage][coverage-image]][coverage-url]
**clues.js** is a lean-mean-promisified-getter-machine that crunches through nested javascript objects, resolving functions (including ES6 arrow functions), values and promises. Clues consists of a single getter function (just under 200 loc) that dynamically resolves dependency trees and memoizes resolutions (derived facts) along the way. It handles the ordering of execution and allows you to think more about the logic of determining the result of a calculation, whether the inputs to those calculations are known values, functions, or asynchronous calls.

@@ -70,3 +72,3 @@

* [`$property`](#property---lazily-create-children-by-missing-reference) and [`$external`](#external-property-for-undefined-paths) are special handlers for missing properties (if they are functions)
* Any function named in-line as `$property` or `$external` will act as shorthands
* Any function named in-line as `$property` or `$external` (or as only argument name of a function) will act as shorthands
* `$global` will always return the full global object provided, in any context.

@@ -313,5 +315,41 @@ * `$caller` and `$fullref` are reserved to provide access to the current state of the clues solver when it hits a function for the first time.

It is worth noting that this functionality only applies to functions. If an object has a $ prefix, then any functions inside that object will be crunched by `clues` as usual.
It is worth noting that this functionality only applies to functions. If an object has a $ prefix, then any functions inside that object will be crunched by `clues` as usual.
If you would like to have a service method that is partially fed by the rest of the `facts`, you can name your function `$prep`. If you want to make a `$prep` for your service, you must eventually return a `$service` method. For example:
```js
var Cabinet = {
something: () => 5,
complicated: () => 6,
$adder: function $prep(something, complicated) {
let work = something + complicated;
return function $service(number) {
return work * number;
}
}
};
```
You can also define a function as a `$prep` function by including an argument name `$prep`, which in a class definition would be something like this:
```js
class Cabinet {
something() {
return 5;
}
complicated() {
return 6
}
$adder ($prep, something, complicated) {
let work = something + complicated;
return function $service(number) {
return work * number;
}
}
}
```
### $property - lazily create children by missing reference

@@ -383,2 +421,9 @@ If a particular property can not be found in a given object, clues will try to locate a `$property` function. If that function exists, it is executed with the missing property name as the first argument and the missing value is set to be the function outcome.

Another way to create this shortcut is to have a function where only argument name is `$property`
```js
var fib = $property => ($property <= 1) ? +$property : [''+($property-1),''+($property-2), (a,b) => a+b];
```
### $external property for undefined paths

@@ -422,8 +467,20 @@ If an undefined property can not locate a `$property` function it will look for an `$external` function. The purpose of the `$external` function is similar except that the argument passed to the function will be the full remaining reference (in dot notation), not just the next reference in the chain.

return function $external(ref) {
ref = ref.replace(/\./g,'/');
....
}
}
return request.getAsync({
url : 'http://api.vendor.com/api/'+ref.replace(/\./g,'/'),
json : {user_id : userid}
});
....
```
A function with a sole argument of `$external` will behave the same:
```js
...
externalApi : userid => $external => request.getAsync({
url: 'http://api.vendor.com/api/'+$external.replace(/\./g,'/').
json: {user_id: userid}
});
```
### function that returns a function that returns a...

@@ -435,9 +492,3 @@ If the resolved value of any function is itself a function, that returned function will also be resolved (within the same scope). This allows for very powerful 'gateways' that constrains the tree traversal only to segments that are relevant for a particular solutions.

tree_b : {....},
next_step: function(step) {
if (step === 'a')
return ['tree_a',Object]
else
return ['tree_b',Object];
}
}
next_step: step => (step === 'a') ? ['tree_a',Object] : ['tree_b',Object]
}

@@ -521,3 +572,3 @@

#### making anonymous functions private
An even easier way to declare functions as private is simply [naming](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name) them `$private` (or `private`). Any functions named `$private` will not be accessible directly, only indirectly through a different function, as an input argument. Specifically the `caller` has to be a defined value and not equal to `__user__`). Here is a quick example:
An even easier way to declare functions as private is simply [naming](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name) them `$private` (or `private`). Any functions named `$private` will not be accessible directly, only indirectly through a different function, as an input argument. Specifically the `caller` has to be a defined value and not equal to `__user__`). Any function or class function that has an argument `$private` will also be defined as private. Here is a quick example:

@@ -527,7 +578,9 @@ ```js

a : function $private() { return 2; },
b : function(a) { return a+2; }
b : function($private, a) { return a + 1; },
sum : function(a,b) { return a + b; }
};
clues(facts,'b').then(console.log) // prints 4
clues(facts,'sum').then(console.log) // prints 5
clues(facts,'a').catch(console.log) // prints error
clues(facts,'b').catch(console.log) // prints error
```

@@ -626,1 +679,3 @@

[downloads-url]: https://npmjs.org/package/clues
[coverage-image]: https://3tjjj5abqi.execute-api.us-east-1.amazonaws.com/prod/clues/badge
[coverage-url]: https://3tjjj5abqi.execute-api.us-east-1.amazonaws.com/prod/clues/url

@@ -1,5 +0,5 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const t = require('tap');
var Logic = {
const Logic = {
a : 41,

@@ -9,3 +9,3 @@ b: 1,

return function(b) {
return a+b;
return a + b;
};

@@ -17,9 +17,4 @@ }

// This test verifies the fix
describe('Inner function arguments',function() {
it('are not affecting function signature',function() {
return clues(Logic,'c')
.then(function(d) {
assert.equal(d,'42');
});
});
t.test('Inner function arguments', async t => {
t.same( await clues(Logic,'c'), 42, 'do not affect function signature');
});

@@ -1,8 +0,7 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('Array functions',function() {
var logic = {
t.test('array functions', async t => {
const logic = {
M1 : [function() { return Promise.delay(100,10); }],

@@ -31,40 +30,31 @@ M2 : function() { return Promise.delay(20,300); },

var facts = Object.create(logic);
const facts = Object.create(logic);
it('should resolve to the top',function() {
return clues(facts,'M3')
.then(function(d) {
assert.equal(d,310);
});
});
t.same(await clues(facts,'M3'),310, 'should resolve to the top');
t.same(await clues(facts,'M4'),310, 'should work for individual functions');
t.same(await clues(facts,'nested'),15,'should work for nested structures');
t.same(await clues(facts,'regular_array'),logic.regular_array,'should not affect regular array');
t.same(await clues(facts,'partial'),42,'partial positional arguments ok');
t.same(await clues(facts,'recursive'),[10,[300,310]],'should work recursively');
t.test('should only execute arrays once', async function(t) {
let counter = 0;
const otherContext = {
M1: Promise.delay(100, 10),
M2: Promise.delay(200, 20)
};
it('should work for individual functions',function() {
return clues(facts,['M4',function(a) {
assert.equal(a,310);
}]);
});
const facts = {
M3: [otherContext, 'M1', 'M2', function(a,b) {
counter++;
return a + b;
}]
};
it('should work for nested structures',function() {
return clues(facts,['nested',function(d) {
assert.equal(d,15);
}]);
const d = await Promise.all([clues(facts,'M3'), clues(facts,'M3')]);
t.same(d[0],30,'results ok');
t.same(d[1],30,'results ok');
t.same(counter,1,'fn only executed once');
});
it('should not affect regular arrays',function() {
return clues(facts,function(regular_array) {
assert.equal(regular_array,logic.regular_array);
});
});
it('should work with partial positional arguments',function() {
return clues(facts, 'partial').then(function(r){
assert.equal(r, 42);
});
});
it('should work recursively',function() {
return clues(facts,'recursive').then(function(d) {
assert.deepEqual(d,[10,[300,310]]);
});
});
});
});

@@ -1,19 +0,16 @@

var clues = require('../clues'),
assert = require('assert'),
crypto = require('crypto');
const clues = require('../clues');
const crypto = require('crypto');
const t = require('tap');
function shouldError() { throw 'Should not run';}
describe('Array fn private scope',function() {
t.test('Array fn private scope', {autoend:true}, t => {
describe('With only array logic',function() {
t.test('With only array logic', async t => {
function privateObj() {
return {
answer : function(forty,two) {
return forty+two;
},
answer : (forty,two) => forty + two,
forty : 40,
two : 2,
err : function() {
err : () => {
throw 'This is an error';

@@ -24,3 +21,3 @@ }

var pub = {
const pub = {
M1 : 100,

@@ -30,59 +27,31 @@ M2 : 200

it('works without array arguments',function() {
var obj = privateObj();
return clues(null,[obj,function(answer) {
assert.equal(answer,42);
}]);
});
const obj = privateObj();
it('works with array arguments',function() {
var obj = privateObj();
return clues({},[obj,'answer',function(d) {
assert.equal(d,42);
}]);
});
t.same(await clues(null,[obj,'answer',Number]),42,'works without array arguments');
t.same(await clues(null,[privateObj,'answer',Number]),42,'works with private scope');
it('works with private scope defined from function',function() {
return clues({},[privateObj,function(answer) {
assert.equal(answer,42);
}]);
});
let d1 = await clues({},[privateObj,'err',String])
.then(shouldError,e => e);
it('handles errors correctly',function() {
return clues({},[privateObj,'err',String])
.then(function() { console.log(arguments);throw 'Should Error';},function(e) {
assert.equal(e.ref,'err');
assert.equal(e.fullref,'err');
assert.equal(e.message,'This is an error');
});
});
t.same(d1.message,'This is an error','handles errors correctly');
it('works recursively',function() {
return clues({},[Object.create(pub),[['M1',Number],['M2',Number],[privateObj(),'forty',Number],Array],function(d) {
assert.deepEqual(d,[100,200,40]);
}]);
});
let d2 = await clues(null,[Object.create(pub),[['M1',Number],['M2',Number],[privateObj(),'forty',Number],Array],Object]);
t.same(d2,[100,200,40],'works recursively');
});
describe('public/private structure',function() {
t.test('public/private structure', async t => {
var PrivateLogic = {
const PrivateLogic = {
secret : 'Hidden secret',
hash : function(secret,userid) {
return crypto.createHash('sha1')
hash : (secret,userid) => crypto.createHash('sha1')
.update(secret)
.update(userid)
.digest('hex');
},
.digest('hex'),
public : function(hash,_userid) {
return {
hash : hash,
userid : _userid,
};
}
public : (hash,_userid) => ({hash, userid : _userid})
};
var Logic = {
const Logic = {
info : function(_userid) {

@@ -97,20 +66,21 @@ return [

it('provides custom access to private segments',function() {
var obj = Object.create(Logic,{userid: {value:'admin'}});
t.test('custom access to private segments',t => {
const obj = Object.create(Logic,{userid: {value:'admin'}});
return clues(obj,function(info) {
assert.equal(info.userid,'admin');
assert.equal(info.hash,'b21b8fd516dbb99584d651f040d970dba2245b2a');
assert.equal(obj.secret,undefined);
assert.equal(info.secret,undefined);
t.same(info.userid,'admin','username matches');
t.same(info.hash,'b21b8fd516dbb99584d651f040d970dba2245b2a','hash matches');
t.same(obj.secret,undefined,'no obj.secret');
t.same(info.secret,undefined,'no info.secret');
});
});
it('handles errors correctly',function() {
var obj = Object.create(Logic);
t.test('handles errors correctly',t => {
const obj = Object.create(Logic);
return clues(obj,'info')
.then(shouldError,function(e) {
assert.equal(e.ref,'userid');
assert.equal(e.message,'userid not defined');
assert.equal(e.caller,'hash');
assert.equal(e.fullref,'info.public.hash.userid');
t.same(e.error,true,'errors');
t.same(e.ref,'userid','userid as ref');
t.same(e.message,'userid not defined','right error message');
t.same(e.caller,'hash','caller is hash');
t.same(e.fullref,'info(public(hash(userid','fullref');
});

@@ -117,0 +87,0 @@ });

@@ -1,8 +0,7 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('Arrow functions',function() {
var logic = {
t.test('Arrow functions', async t => {
const logic = {
M1 : () => Promise.delay(100,10),

@@ -16,18 +15,11 @@ M2 : () => Promise.delay(20,300),

var facts = Object.create(logic);
const facts = Object.create(logic);
const MTOP = await clues(facts,'MTOP');
it('should resolve to the top',function() {
return clues(facts,'MTOP')
.then(function(d) {
assert.equal(d,380);
});
});
it('should update the fact table',function() {
assert.equal(facts.M1.value(),10);
assert.equal(facts.M2.value(),300);
assert.equal(facts.M3.value(),310);
assert.equal(facts.M4.value(),70);
assert.equal(facts.MTOP.value(),380);
});
t.same(MTOP,380,'resolves tree to the top');
t.same(facts.M1.value(),10,'fact M1 resolved');
t.same(facts.M2.value(),300,'fact M2 resolved');
t.same(facts.M3.value(),310,'fact M3 resolved');
t.same(facts.M4.value(),70,'fact M4 resolved');
t.same(facts.MTOP.value(),380,'fact MTOP resolved');
});

@@ -1,17 +0,15 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
var Logic = {
const facts = {
answer : async function() {
var value = await Promise.delay(100).then(function() { return 41;});
const value = await Promise.delay(100).then(() => 41);
return value+1;
}
}
};
describe('Async function',function() {
it('should resolve',async function() {
var answer = await clues(Logic,'answer');
assert.equal(answer,42);
})
});
t.test('Async function', async t => {
const answer = await clues(facts,'answer');
t.same(answer,42,'resolves to a value');
});

@@ -1,86 +0,41 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const t = require('tap');
function obj() {
return {
a : {
b : {
caller : function($caller) { return $caller; },
fullref : function($fullref) { return $fullref; }
}
},
call : ['a.b.caller',String],
fullref : ['a.b.fullref',String]
};
}
const facts = () => ({
a : {
b : {
caller : function($caller) { return $caller; },
fullref : function($fullref) { return $fullref; }
}
},
call : ['a.b.caller',String],
fullref : ['a.b.fullref',String]
});
describe('$caller',function() {
describe('with fn called directly',function() {
it('is null without provided caller',function() {
return clues(obj,'a.b.caller')
.then(function(caller) {
assert.equal(caller,undefined);
});
});
it('shows provided caller',function() {
return clues(obj,'a.b.caller',{},'__user__')
.then(function(caller) {
assert.equal(caller,'__user__');
});
});
t.test('$caller', {autoend:true}, t => {
t.test('with fn called directly', async t => {
t.same(await clues(facts,'a.b.caller'),undefined,'is null without caller');
t.same(await clues(facts,'a.b.caller',{},'__user__'),'__user__','shows with caller');
});
});
describe('with fn called indirectly',function() {
it('show last fn without provided caller',function() {
return clues(obj,'call')
.then(function(caller) {
assert.equal(caller,'call');
});
});
t.test('with fn called indirectly', async t => {
t.same(await clues(facts,'call'),'call','shows last fn without provided caller');
t.same(await clues(facts,'call',{},'__user__'),'call','shows last fn even with caller');
});
it('shows last fn even with provded caller',function() {
return clues(obj,'call',{},'__user__')
.then(function(caller) {
assert.equal(caller,'call');
});
});
});
t.test('with $caller override in factsect', async t => {
const o = facts();
o.a.b.$caller = 'CUSTOM_CALLER';
t.same(await clues(o,'call',{},'__user__'),'CUSTOM_CALLER','returns override');
});
describe('with $caller override in object',function() {
it('returns override',function() {
var o = obj();
o.a.b.$caller = 'CUSTOM_CALLER';
return clues(o,'call',{},'__user__')
.then(function(caller) {
assert.equal(caller,'CUSTOM_CALLER');
});
});
});
t.test('$fullref', async t => {
t.same(await clues(facts,'a.b.fullref'),'a.b.fullref','direct call shows fullref');
t.same(await clues(facts,'fullref'),'fullref(.a.b.fullref','indirect call shows fullref');
const o = facts();
o.a.b.$fullref = 'CUSTOM_FULLREF';
t.same(await clues(o,'fullref',{}),'CUSTOM_FULLREF','$fullref override returns override');
});
describe('$fullref',function() {
it('direct call shows fullref',function() {
return clues(obj,'a.b.fullref')
.then(function(fullref) {
assert.equal(fullref,'a.b.fullref');
});
});
it('indirect call shows fullref',function() {
return clues(obj,'fullref')
.then(function(fullref) {
assert.equal(fullref,'fullref.a.b.fullref');
});
});
it('with $fullref override in object returns override',function() {
var o = obj();
o.a.b.$fullref = 'CUSTOM_FULLREF';
return clues(o,'fullref',{})
.then(function(fullref) {
assert.equal(fullref,'CUSTOM_FULLREF');
});
});
});

@@ -1,84 +0,63 @@

var clues = require('../clues'),
Promise = clues.Promise,
assert = require('assert');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
Promise.config({cancellation:true});
describe('cancellation',function() {
var cancel = {};
t.test('cancellation', {autoend: true}, async t => {
const cancel = {};
var logic = {
M1 : function() {
return new Promise(function(resolve,reject,onCancel) {
onCancel(function() {
cancel.M1 = true;
});
const Logic = {
M1 : () => new Promise(function(resolve,reject,onCancel) {
onCancel(() => cancel.M1 = true);
setTimeout(() => resolve(10),130);
}),
setTimeout(function() {
resolve(10);
},130);
});
},
M2 : () => new Promise(function(resolve,reject,onCancel) {
onCancel(() => cancel.M2 = true);
setTimeout(() => resolve(50),170);
}),
M2 : function() {
return new Promise(function(resolve,reject,onCancel) {
onCancel(function() {
cancel.M2 = true;
});
M3 : () => new Promise(function(resolve,reject,onCancel) {
onCancel(() => cancel.M3 = true);
setTimeout(() => resolve(10));
}),
setTimeout(function() {
resolve(50);
},170);
});
},
M4 : (M1,M2,M3) => M1+M2+M3,
M3 : function() {
return new Promise(function(resolve,reject,onCancel) {
onCancel(function() {
cancel.M3 = true;
});
MTOP : (M1,M4) => M1+M4
};
setTimeout(function() {
resolve(10);
},10);
});
},
const facts = Object.create(Logic);
let res;
M4 : function(M1,M2,M3) {
return M1+M2+M3;
},
let gotResults;
MTOP : function(M1,M4) {
return M1+M4;
}
};
res = clues(facts,'MTOP')
.then(function() {
gotResults = true;
});
var facts = Object.create(logic),res;
// Cancelling in 100ms
setTimeout(res.cancel.bind(res),100);
it('should not return results',function() {
var gotResults;
await Promise.delay(200);
res = clues(facts,'MTOP')
.then(function() {
gotResults = true;
});
// Cancelling in 100ms
setTimeout(res.cancel.bind(res),100);
return Promise.delay(150)
.then(function() {
assert.equal(gotResults,undefined);
});
t.test('should not return results', t => {
t.same(gotResults,undefined);
t.end();
});
it('should trigger the cancel higher up',function() {
assert.equal(cancel.M1,true);
assert.equal(cancel.M2,true);
t.test('Cancellation affects dependencies', t => {
t.same(cancel.M1,true,'M1 cancelled');
t.same(cancel.M2,true,'M2 cancelled');
t.end();
});
it('should not invalidate results aquired before cancellation',function() {
assert.equal(cancel.M3,undefined);
assert.equal(facts.M3.value(),10);
t.test('Any result resolved before cancellation should not be invalidated',t => {
t.same(cancel.M3,undefined,'M3 not cancelled');
t.same(facts.M3.value(),10,'M3 resolved to a promise with correct value');
t.end();
});
});

@@ -1,8 +0,8 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('complex tree',function() {
t.test('Arrow functions',async t => {
var logic = {
const Logic = {
M1 : function() { return Promise.delay(100,10); },

@@ -18,18 +18,11 @@ M2 : function() { return Promise.delay(20,300); },

var facts = Object.create(logic);
const facts = Object.create(Logic);
const MTOP = await clues(facts,'MTOP');
it('should resolve to the top',function() {
return clues(facts,'MTOP')
.then(function(d) {
assert.equal(d,380);
});
});
it('should update the fact table',function() {
assert.equal(facts.M1.value(),10);
assert.equal(facts.M2.value(),300);
assert.equal(facts.M3.value(),310);
assert.equal(facts.M4.value(),70);
assert.equal(facts.MTOP.value(),380);
});
t.same(MTOP,380,'resolves tree to the top');
t.same(facts.M1.value(),10,'fact M1 resolved');
t.same(facts.M2.value(),300,'fact M2 resolved');
t.same(facts.M3.value(),310,'fact M3 resolved');
t.same(facts.M4.value(),70,'fact M4 resolved');
t.same(facts.MTOP.value(),380,'fact MTOP resolved');
});

@@ -1,160 +0,139 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const t = require('tap');
function shouldErr() { throw 'Should throw an error'; }
describe('error',function() {
var c = {a: function(b) { return b;}};
t.test('error', {autoend: true},t => {
const c = {a: function(b) { return b;}};
describe('when argument can´t be found',function() {
it('should throw an error',function() {
return clues({},'SOMETHING')
.then(shouldErr,function(e) {
assert.equal(e.ref,'SOMETHING');
assert.equal(e.message, 'SOMETHING not defined');
});
t.test('when argument is not found', async t => {
t.test('direct call',async t => {
const e = await clues({},'SOMETHING').then(shouldErr,Object);
//t.equal(e.error,true,'shows errors');
t.same(e.message,'SOMETHING not defined','message: not defined');
t.same(e.ref,'SOMETHING','ref: missing variable');
});
it('should show caller if a named logic function',function() {
return clues(c,'a')
.then(shouldErr,function(e) {
assert.equal(e.ref,'b');
assert.equal(e.message,'b not defined');
assert.equal(e.caller,'a');
});
t.test('caller is logic function', async t => {
const e = await clues(c,'a').catch(Object);
t.same(e.error,true,'errors');
t.same(e.ref,'b','ref is correct');
t.same(e.message,'b not defined','message is correct');
t.same(e.caller,'a','caller is correct');
});
});
describe('thrown',function() {
var logic = {
ERR : function() { throw 'Could not process'; },
DEP : function(ERR) { return 'Where is the error'; }
};
var facts = Object.create(logic);
const Logic = {
ERR : function() { throw 'Could not process'; },
DEP : function(ERR) { return 'Where is the error'; }
};
describe('directly',function() {
it('should show up as first argument (err)',function() {
return clues(facts,'ERR')
.then(shouldErr,function(e) {
assert.equal(e.ref,'ERR');
assert.equal(e.message,'Could not process');
});
});
t.test('throw', {autoend:true}, t => {
it ('should update the facts',function() {
var e = facts.ERR.reason();
assert.equal(e.ref,'ERR');
assert.equal(e.message,'Could not process');
});
t.test('directly', async t => {
const facts = Object.create(Logic);
const e = await clues(facts,'ERR').catch(Object);
const reason = facts.ERR.reason();
t.equal(e.message,'Could not process','message ok');
t.same(e.ref,'ERR','ref ok');
t.same(reason.message,'Could not process','facts register message');
});
describe('indirectly',function() {
it('should throw same error for dependent logic',function() {
return clues(facts,'DEP')
.then(shouldErr,function(e) {
assert.equal(e.ref,'ERR');
assert.equal(e.message,'Could not process');
});
});
t.test('indirectly - dependent fn', async t => {
const facts = Object.create(Logic);
const e = await clues(facts,'DEP').catch(Object);
t.equal(e.message,'Could not process','message ok');
t.same(e.ref,'ERR','ref ok');
t.same(e.caller,'DEP','Should reference the first caller');
});
});
it('should contain reference to the first caller',function() {
facts = Object.create(logic);
return clues(facts,'DEP')
.catch(function() {
return clues(facts,'ERR');
})
.then(shouldErr,function(e) {
assert.equal(e.caller,'DEP');
});
});
t.test('function named $noThrow', {autoend: true}, t => {
const facts = Object.create(Logic);
t.test('thrown rejection', async t => {
const e = await clues(facts,function $noThrow(DEP) { return DEP;});
t.same(e.error,true,'Returns an object with the error');
t.same(e.ref,'ERR','ref ok');
t.equal(e.message,'Could not process','message ok');
});
describe('function named $noThrow',function() {
it('should return the error obj',function() {
return clues(facts,function $noThrow(DEP) {
return DEP;
})
.then(function(e) {
assert.equal(e.ref,'ERR');
assert.equal(e.message,'Could not process');
},function() {
throw 'Should return the error object';
});
});
it('should return the error obj for subsequent fn',function() {
return clues(facts,function $noThrow() {
return ['DEP',Object];
})
.then(function(e) {
assert.equal(e.ref,'ERR');
assert.equal(e.message,'Could not process');
},function() {
throw 'Should return the error object';
});
});
t.test('error within subsequent fn', async t => {
const fn = function $noThrow() {
return ['DEP',Object];
};
const e = await clues(facts,fn);
t.same(e.error,true,'Returns an object with the error');
t.same(e.ref,'ERR','ref ok');
t.equal(e.message,'Could not process','message ok');
});
});
describe('$logError',function() {
var logic = {
stack_error: function() { throw new Error('error');},
stack_error_promise: function() { return clues.Promise.reject(new Error('error'));},
rejection: function() { throw 'error';},
rejection_promise: function() { return Promise.reject('error');},
optional: function(_stack_error,_rejection) {
return 'OK';
}
};
t.test('$logError', {autoend: true}, t => {
it('should log an error with stack',function() {
var facts = Object.create(logic),error,fullref;
return clues(facts,'stack_error',{$logError:function(e,f) {error = e;fullref = f;}})
.catch(Object)
.then(function() {
assert.equal(error && error.message,'error');
assert.equal(fullref,'stack_error');
});
});
const Global = {
$logError : function(e,f) {
this.error = e;
this.fullref = f;
}
};
it('should log a rejected promise with stack',function() {
var facts = Object.create(logic),error,fullref;
return clues(facts,'stack_error_promise',{$logError:function(e,f) {error = e; fullref = f;}})
.catch(Object)
.then(function() {
assert.equal(error && error.message,'error');
assert.equal(fullref,'stack_error_promise');
});
});
t.test('error with a stack', async t => {
const $global = Object.create(Global);
const facts = {
stack_error: function() { throw new Error('error');}
};
await clues(facts,'stack_error',$global).then(shouldErr,Object);
t.same($global.error.message,'error');
t.ok($global.error.stack,'contains a stack');
t.same($global.error.fullref,'stack_error','fullref ok');
});
it('should not log an error with no stack',function() {
var facts = Object.create(logic),error;
return clues(facts,'rejection',{$logError:function(e) {error = e;}})
.catch(Object)
.then(function() {
assert.equal(error,undefined);
});
});
t.test('promise rejected with a stack', async t => {
const $global = Object.create(Global);
const facts = {
stack_error_promise: () => clues.reject(new Error('error'))
};
it('should not log a rejection with no stack',function() {
var facts = Object.create(logic),error;
return clues(facts,'rejection',{$logError:function(e) {error = e;}})
.catch(Object)
.then(function() {
assert.equal(error,undefined);
});
});
it('should log an error even if only used optionally',function() {
var facts = Object.create(logic),error;
return clues(facts,'optional',{$logError:function(e) {error = e;}})
.catch(Object)
.then(function() {
assert.equal(error.message,'error');
});
});
await clues(facts,'stack_error_promise',$global).then(shouldErr,Object);
t.same($global.error.message,'error','error passed to $logError');
t.ok($global.error.stack,'contains a stack');
t.same($global.error.fullref,'stack_error_promise','fullref ok');
});
t.test('error without a stack (rejection)', async t => {
const $global = Object.create(Global);
const facts = {
rejection: function() { throw 'error';}
};
await clues(facts,'rejection',$global).then(shouldErr,Object);
t.same($global.error,undefined,'should not $logError');
});
t.test('Promise rejection without a stack', async t => {
const $global = Object.create(Global);
const facts = {
rejection_promise: function() { return clues.reject('error');}
};
await clues(facts,'rejection_promise',$global).then(shouldErr,Object);
t.same($global.error,undefined,'should not $logError');
});
t.test('stack error in an optional dependency', async t => {
const $global = Object.create(Global);
const facts = {
stack_error: () => { throw new Error('error');},
rejection: () => { throw 'error';},
optional: (_stack_error,_rejection) => 'OK'
};
await clues(facts,'optional',$global);
t.same($global.error.message,'error','error passed to $logError');
t.ok($global.error.stack,'contains a stack');
t.same($global.error.fullref,'optional(stack_error','fullref ok');
});
});
});
});

@@ -1,107 +0,144 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('$external',function() {
var logic = {
a : 5,
simple : Object.create({
count : 0,
$external : function(ref) {
this.count+=1;
if (ref == 'STOP') throw {message:'STOP_ERROR',error:true};
return 'simple:'+ref;
t.test('$external', {autoend:true, jobs: 10}, t => {
t.test('in simple logic with $external', {autoend: true}, t => {
const facts = {
simple: {
count : 0,
$external : function(ref) {
this.count+=1;
if (ref == 'STOP') throw {message:'STOP_ERROR',error:true};
return 'simple:'+ref;
}
}
}),
shorthand : function $external(ref) {
return 'answer:'+ref;
},
shorthandThis : function $external() {
return this;
},
concurrent : function $external() {
return clues.Promise.delay(Math.random()*1000)
.then(function() {
return new Date();
});
}
};
};
var facts = Object.create(logic);
t.test('asking for non-existent property', async t => {
const d = await clues(facts,'simple.test.1');
t.same(d,'simple:test.1','returns the $external function value');
t.ok(facts.simple['test.1'].isFulfilled(),'resolved promise is placed on logic');
t.same(facts.simple['test.1'].value(),'simple:test.1','promise contains value');
t.same(facts.simple.count,1,'function is called once');
});
describe('in simple logic',function() {
it('runs $external function for non-existent property',function() {
return clues(facts,'simple.test')
.then(function(d) {
assert.equal(d,'simple:test');
assert(facts.simple.test.isFulfilled());
assert.equal(facts.simple.test.value(),'simple:test');
assert.equal(facts.simple.count,1);
});
t.test('asking for a different property',async t => {
t.same(await clues(facts,'simple.test.2'),'simple:test.2','returns new value');
t.same(facts.simple.count,2,'$external has now been called twice');
});
it('concurrent requests should return first response',function() {
var requests = Array.apply(null, {length: 10})
.map(function() {
return clues(facts,'concurrent.test_concurrent');
});
return clues.Promise.all(requests)
.then(function(d) {
return d.map(function(e) {
assert.equal(e,d[0],'Return time should be the same');
});
});
t.test('results so far are cached', async t => {
const d = await Promise.all([ clues(facts,'simple.test.1'), clues(facts,'simple.test.2')]);
t.same(d[0],'simple:test.1','first is cached');
t.same(d[1],'simple:test.2','second is cached');
t.equal(facts.simple.count,2,'an $external is not run again');
});
it('passes the full path as argument',function() {
return clues(facts,'simple.test.a.b.c')
.then(function(d) {
assert.equal(d,'simple:test.a.b.c');
assert.equal(facts.simple.count,2);
});
t.test('error raised in $external', async t => {
const e = await clues(facts,'simple.STOP',{},'__test__').catch(Object);
t.same(e.message,'STOP_ERROR','error message ok');
//t.same(e.ref,'abc','ref is ok');
t.same(e.fullref,'simple.STOP','fullref is ok');
t.same(e.caller,'STOP','caller is ok');
t.equal(facts.simple.STOP.isRejected(),true,'rejected promise in logic');
t.equal(facts.simple.STOP.reason().message,'STOP_ERROR','rejected promise has error');
t.equal(facts.simple.count,3,'$external has now been called 3 times');
});
});
it('caches previous results',function() {
return clues(facts,['simple.test','simple.test.a.b.c',function(a,b) {
assert.equal(a,'simple:test');
assert.equal(b,'simple:test.a.b.c');
assert.equal(facts.simple.count,2);
}]);
t.test('concurrent request', async t => {
const facts = {
concurrent : {
count: 0,
$external : function() {
return clues.Promise.delay(Math.random()*1000)
.then(() => {
this.count += 1;
return new Date();
});
}
}
};
const d = await Promise.map([...Array(10)],() => clues(facts,'concurrent.test.value'));
t.ok( d.every(e => e == d[0]),'should return the same value');
t.same(facts.concurrent.count,1,'and should not rerun the function');
});
t.test('$external inside a function', {autoend: true}, t => {
const facts = {
a : 'ok:',
funct : a => ({
count : 0,
$external : function(ref) {
this.count+=1;
return a+ref;
}
})
};
t.test('asking for non-existent property', async t => {
t.same( await clues(facts,'funct.test.1'), 'ok:test.1', 'returns value');
t.same(facts.funct.value().count,1,'$external is called once');
t.same( await clues(facts,'funct.test.1'), 'ok:test.1', 'previous results are cached');
t.same(facts.funct.value().count,1,'$an fn not run again for same property');
});
});
it('handles errors correctly',function() {
return clues(facts,'simple.STOP')
.then(function() {
throw 'Should Error';
},function(e) {
assert.equal(e.error,true);
assert.equal(e.message,'STOP_ERROR');
assert.equal(facts.simple.count,3);
assert.equal(e.fullref,'simple.STOP');
});
t.test('when function name is $external', {autoend: true}, t => {
const facts = {
a: 'ok:',
shorthand : function $external(ref) {
return this.a+ref;
}
};
t.test('asking for a property', async t => {
t.same( await clues(facts,'shorthand.test.1'),'ok:test.1','returns right value');
t.same( facts.shorthand.value()['test.1'].value(),'ok:test.1','stores promise with value');
});
t.test('asking for another property', async t => {
t.same( await clues(facts,'shorthand.test.2'),'ok:test.2','returns right value');
t.same( facts.shorthand.value()['test.2'].value(),'ok:test.2','stores promise with value');
});
});
describe('when function name is $external',function() {
it('acts as a shorthand for empty object with $external',function() {
return clues(facts,'shorthand.first')
.then(function(d) {
assert.equal(d,'answer:first');
assert.equal(facts.shorthand.value().first.value(),'answer:first');
return clues(facts,'shorthand.second');
})
.then(function(d) {
assert.equal(d,'answer:second');
assert.equal(facts.shorthand.value().first.value(),'answer:first');
assert.equal(facts.shorthand.value().second.value(),'answer:second');
});
t.test('when argument name is $external', {autoend: true}, t => {
const facts = {
a: 'ok:',
as_argument: function ($external) {
return this.a + $external;
},
as_argument_es6 : a => $external => a + $external
};
t.test('regular function', async t => {
t.test('asking for a property', async t => {
t.same( await clues(facts,'as_argument.test.1'),'ok:test.1','returns right value');
t.same( facts.as_argument.value()['test.1'].value(),'ok:test.1','stores promise with value');
});
t.test('asking for another property', async t => {
t.same( await clues(facts,'as_argument.test.2'),'ok:test.2','returns right value');
t.same( facts.as_argument.value()['test.2'].value(),'ok:test.2','stores promise with value');
});
});
it('assumes this of the parent',function() {
return clues(facts,'shorthandThis.test')
.then(function(d) {
assert.equal(d,facts);
});
t.test('ES6 fat arrow', async t => {
t.test('asking for a property', async t => {
t.same( await clues(facts,'as_argument_es6.test.1'),'ok:test.1','returns right value');
t.same( facts.as_argument_es6.value()['test.1'].value(),'ok:test.1','stores promise with value');
});
t.test('asking for another property', async t => {
t.same( await clues(facts,'as_argument_es6.test.2'),'ok:test.2','returns right value');
t.same( facts.as_argument_es6.value()['test.2'].value(),'ok:test.2','stores promise with value');
});
});
});
});

@@ -1,7 +0,7 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('facts',function() {
var logic = {
t.test('facts',{autoend: true}, t => {
const Logic = {
response : function() { return Promise.delay(500,42);},

@@ -11,27 +11,22 @@ other : function() { return 5; }

var facts = Object.create(logic);
const facts = Object.create(Logic);
it('should return the solved logic when determined',function() {
var start = new Date();
return clues(facts,'response')
.then(function(d) {
var wait = (new Date()) - start;
assert.equal(wait >= 500,true,'wait was '+wait);
assert.equal(d,42);
});
t.test('called first time', async t => {
const start = new Date();
t.same(await clues(facts,'response'),42,'resolves function when called');
const wait = new Date() - start;
t.ok( wait >= 500,true, `wait was ${wait}`);
});
it('should return value immediately when solved for again',function() {
var start = new Date();
return clues(facts,'response')
.then(function(d) {
var wait = (new Date()) - start;
assert.equal(wait <= 20,true,'wait was '+wait);
assert.equal(d,42);
});
t.test('called second time', async t => {
const start = new Date();
t.same(await clues(facts,'response'),42,'resolves function when called');
const wait = new Date() - start;
t.ok( wait <= 100,true, 'should return immediately');
});
it('should not be solved for unrequested logic',function() {
assert(typeof facts.other === 'function');
t.test('unrequested logic function', {autoend: true}, t => {
t.same(typeof facts.other,'function','should not be resolved');
});
});

@@ -1,8 +0,10 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const t = require('tap');
describe('Frozen logic',function() {
var calls = 0;
const shouldErr = () => { throw 'SHOULD_ERROR'; };
var Logic = {
t.test('Frozen logic', {autoend: true}, t => {
let calls = 0;
const Logic = {
a : b => b,

@@ -16,36 +18,27 @@ b : () => {

Object.freeze(Logic);
describe('direct access',function() {
it('should not run directly ',function() {
return clues(Logic,'a')
.then(function() {
throw 'SHOULD_ERROR';
},function(e) {
assert.equal(e.message,'Object immutable');
assert.equal(e.value,15);
assert.equal(typeof Logic.a,'function');
});
});
t.test('solving property of a frozen object directly', async t => {
const e = await clues(Logic,'a').then(shouldErr,Object);
t.same(e.message,'Object immutable','errors as Object immutable');
//t.same(calls,0,'does not execute function');
t.same(typeof Logic.a,'function','does not modify function');
t.same(typeof Logic.b,'function','does not modify function');
});
describe('cloned access',function() {
var facts = Object.create(Logic);
it('should resolve',function() {
return clues(facts,'a')
.then(function(a) {
assert.equal(a,15);
assert.equal(calls,2);
assert.equal(facts.a.value(),15);
});
t.test('solving on a clone of a frozen object', async t => {
const facts = Object.create(Logic);
t.test('first time', async t => {
const d = await clues(facts,'a');
t.same(d,15,'correct result');
t.same(calls,2,'two calls have been made');
t.same(facts.a.value(),15,'promise is registered');
});
it('should memoize',function() {
return clues(facts,'a')
.then(function(a) {
assert.equal(a,15);
assert.equal(calls,2);
assert.equal(facts.a.value(),15);
});
t.test('second time', async t => {
const d = await clues(facts,'a');
t.same(d,15,'correct result');
t.same(calls,2,'two calls have been made');
t.same(facts.a.value(),15,'promise is registered');
});
});
});
});

@@ -1,6 +0,8 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const t = require('tap');
describe('Global variable',function() {
var logic = {
const shouldErr = () => { throw 'Should error'; };
t.test('Global variable', {autoend: true}, t => {
const Logic = {
a: 123,

@@ -39,5 +41,5 @@ b: {

var facts = Object.create(logic);
const facts = Object.create(Logic);
var global = {
const global = {
$input : {

@@ -51,32 +53,19 @@ test: 10,

it('should be applied where referenced',function() {
return clues(facts,'b.c',global)
.then(function(d) {
assert.equal(d,133);
assert.equal(facts.b.count,1);
});
t.test('when referenced as a dependency of a function', async t => {
t.same(await clues(facts,'b.c',global), 133, 'uses the global variable');
t.same(facts.b.count,1,'fn called once');
});
it('can not be applied directly',function() {
return clues(facts,'$input',global)
.then(function() {
throw 'Should Error';
},function(e) {
assert.equal(e.message,'$input not defined');
});
t.test('when referenced directly', async t => {
const e = await clues(facts,'$input',global).then(shouldErr,Object);
t.same(e.message,'$input not defined','should error');
});
it('can not be applied as part of dot notation',function() {
return clues(facts,'d.c',global)
.then(function(d) {
assert.equal(d,29);
});
t.test('inside a nested structure', async t => {
t.same(await clues(facts,'d.c',global),29,'uses the global variable');
});
it('can be used as object $global',function() {
return clues(facts,'e.f.g',global)
.then(function(d) {
assert.equal(d,4);
});
t.test('$global object as dependency', async t => {
t.same(await clues(facts,'e.f.g',global), 4, 'uses the $global object');
});
});

@@ -1,12 +0,13 @@

var clues = require('../clues'),
inject = require('../util/inject'),
assert = require('assert');
const clues = require('../clues');
const Promise = require('bluebird');
const inject = require('../util/inject');
const t = require('tap');
var Db = {
user : function(userid) {
return // database.query('users where usedid = ?',[userid]);
}
const shouldErr = () => { throw 'Should Error'; };
const Db = {
user : (userid) => undefined // database.query('users where usedid = ?',[userid]);
};
var Logic = {
const Logic = {
db : function(userid) {

@@ -18,51 +19,67 @@ return Object.create(Db,{

userid : ['input.userid',String],
never_traversed : {}
never_traversed : {},
$transform : function $prep(possibleᐅvalue) {
return function $service(d) {
return d + possibleᐅvalue;
};
}
};
var injected = function($global) {
return inject(Object.create(Logic), {
'userid': function(original_userid) {
return 'test_'+original_userid;
},
'db.user' : function(userid) {
return { desc: 'this is an injected response', userid: userid};
},
'impossible.to.reach' : 42,
'possible' : {},
'possible.value': 42,
'never_traversed.value' : 11
},$global);
};
const injected = $global => inject(Object.create(Logic), {
'userid': function(original_userid) {
return 'test_'+original_userid;
},
'db.user' : function(userid) {
return { desc: 'this is an injected response', userid: userid};
},
'impossible.to.reach' : 43,
'possible' : {},
'possible.value': 42,
'never_traversed.value' : 11,
'$transform': function $prep($original_transform) {
return function $service(d) {
return $original_transform(d)+10;
};
}
describe('inject',function() {
it('modifies the api tree',function() {
return clues(injected,'db.user',{input:{userid:'johndoe'}})
.then(function(d) {
assert.equal(d.userid,'test_johndoe');
});
});
},$global);
it('allows sequential injections',function() {
return clues(injected,'possible.value',{})
.then(function(d) {
assert.equal(d,42);
});
t.test('inject', {autoend: true}, t => {
t.test('simple example', {autoend: true}, t => {
t.test('modified api tree', async t => {
const d = await clues(injected,'db.user',{input:{userid:'johndoe'}});
t.same(d.userid,'test_johndoe','username is now available');
});
t.test('sequential injection into a nested value', async t => {
const d = await clues(injected,'possible.value',{});
t.same(d,42,'resolves to injected value');
});
t.test('injecting base paths that do not exist', async t => {
const d = await clues(injected,'impossible.to.reach',{});
t.same(d,43,'resolves to injected value');
});
t.test('injected paths that are not traversed', async t => {
const d = await clues({},injected);
t.same(typeof d.never_traversed,'function','are not executed');
});
t.test('injecting a $service', async t => {
const $transform = await clues(injected,'$transform',{});
t.same($transform(10),62);
});
});
it('ignores base paths that do not exists',function() {
return clues(injected,'impossible.to.reach',{})
.then(function() {
throw 'SHOULD_ERROR';
},function(e) {
assert.equal(e.message,'impossible not defined');
});
t.test('injecting without $global', async t => {
const d = await Promise.try(() =>inject(Object.create(Logic),{'possible.value': 42})).then(shouldErr,Object);
t.same(d.message,'$global not supplied to inject','throws');
});
it('does not execute injected paths that are not traversed',function() {
return clues({},injected)
.then(function(injected) {
assert(typeof injected.never_traversed === 'function');
});
t.test('injecting a non-object', async t => {
const d = await Promise.try(() =>inject(Object.create(Logic),42,{})).then(shouldErr,Object);
t.same(d.message,'Invalid properties passed to inject','throws');
});
});

@@ -1,5 +0,5 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const t = require('tap');
function logic($global) {
function facts($global) {
$global.$constant = 380;

@@ -9,22 +9,11 @@ return { a : { b : function($constant) { return $constant; } } };

describe('When logic is a function',function() {
it('it is evaluated before solving',function() {
return clues(logic,'a.b')
.then(function(d) {
assert.equal(d,380);
});
});
function wrapped() {
return function($global) {
return facts;
};
}
it('even nested functions are evaluated before solving',function() {
function wrapped() {
return function($global) {
return logic;
};
}
return clues(wrapped,'a.b')
.then(function(d) {
assert.equal(d,380);
});
});
t.test('when logic is a function', async t => {
t.same( await clues(facts,'a.b'),380,'direct call is evaluated before solving');
t.same( await clues(wrapped,'a.b'),380,'nested functions evaluated');
});

@@ -1,20 +0,14 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
var logic = Promise.delay(100).then(function() {
const Logic = Promise.delay(100).then(function() {
return {
$constant : 380,
a : function($constant) { return {b: $constant}; }
a : function($constant) { return {b: $constant}; }
};
});
describe('When logic is a promise',function() {
it('is used when resolved',function() {
return clues(logic,'a.b')
.then(function(d) {
assert.equal(d,380);
});
});
t.test('When logic is a promise', async t => {
t.same( await clues(Logic,'a.b'),380,'logic is resolved before solving');
});

@@ -1,7 +0,7 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('function name',function() {
var logic = {
t.test('function name', {autoend: true}, t => {
const logic = {
M1 : function() { return Promise.delay(100,10); },

@@ -12,19 +12,15 @@ M2 : function $hide() { return Promise.delay(20,300); },

var facts = Object.create(logic);
const facts = Object.create(logic);
describe('before resolution ',function() {
it('is defined in function',function() {
assert.equal(facts.M2.name,'$hide');
});
t.test('before resolution', t => {
t.same(facts.M2.name,'$hide','name is a property of function');
t.end();
});
describe('after resolution',function() {
it('is retained in promise',function() {
return clues(facts,'M3')
.then(function() {
assert.equal(facts.M3.value(),310);
assert.equal(facts.M2.name,'$hide');
});
});
t.test('after resolution', {autoend:true}, async t => {
await clues(facts,'M3');
t.same(facts.M2.name,'$hide','name is a property of promise');
t.same(facts.M3.value(),310,'and promise resolves to value');
});
});

@@ -1,54 +0,43 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
function shouldError(e) { throw 'Should error '+e;}
function notDefined(e) { assert.deepEqual(e.message,'test not defined'); }
[null,undefined,4,'a',0,Object,Array,Object].forEach(function(nothing) {
describe('with value '+String(nothing),function() {
describe(' as logic',function() {
t.test('null logic', {autoend: true}, t => {
const values = [null,undefined,4,'a',0,Object,Array,Object];
it('property not defined',function() {
return clues(nothing,'test')
.then(shouldError,notDefined);
});
t.test('solving for a variable of null logic', async t => {
await Promise.map(values, async nothing => {
const value = await clues(nothing,'test').then(shouldError,Object);
t.same(value.message,'test not defined',`${nothing}.test is undefined`);
});
t.end();
});
it('path not defined',function() {
return clues(nothing,'test.test')
.then(shouldError,notDefined);
});
t.test('solving for a nested variable of null logic', async t => {
await Promise.map(values, async nothing => {
const value = await clues(nothing,'test.test').then(shouldError,Object);
t.same(value.message,'test not defined',`${nothing}.test.test is undefined`);
});
t.end();
});
it('context of empty function is empty object',function() {
return clues(nothing,function() {
if (nothing == 'a')
return assert.deepEqual(this,{0:'a'});
else
assert.deepEqual(this,{});
});
});
it('property fallback to global (if exists)',function() {
return clues(nothing,function(test) {
assert.equal(test,'global');
},{test:'global'});
});
t.test('solving for context of null logic', async t => {
await Promise.map(values, async nothing => {
const context = await clues(nothing,function() { return this;});
t.same(context, nothing == 'a' ? {0:'a'} : {},`${nothing} has a context of ${JSON.stringify(context)}`);
});
t.end();
});
describe('as a property',function() {
it('should resolve correctly',function() {
var obj = {};
obj.test = nothing;
return clues(obj,'test')
.then(function(d) {
// functions are always resolved to objects
return assert.deepEqual(d, typeof nothing === 'function' ? nothing() : nothing);
},function(e) {
// we only error if the property is `undefined`
assert.equal(nothing,undefined);
assert.equal(e.message,'test not defined');
});
});
t.test('null logic as a property', async t => {
await Promise.map(values.filter(d => d !== undefined), async nothing => {
const d = await clues({test: nothing},'test');
const expected = typeof nothing === 'function' ? nothing() : nothing;
t.same(d, expected,`test.${nothing} == ${expected}`);
});
t.end();
});
t.end();
});
// See optimization killers https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#1-tooling
const Promise = require('bluebird');
const execAsync = Promise.promisify(require('child_process').exec);
const t = require('tap');
var child_process = require('child_process');
var statusCodes = {
const statusCodes = {
1: ['OK','Clues Optimized'],

@@ -14,3 +15,3 @@ 2: ['ERR','Clues Not Optimized'],

var code = `
const code = `
var clues = require('../clues');

@@ -29,9 +30,5 @@

describe('V8 compiler',function() {
it('optimize the clues function',function(cb) {
child_process.exec('echo "'+code+'" | node --allow-natives-syntax',function(err,stdout,stderr) {
var status = statusCodes[stdout];
cb(err || stderr || (status[0] === 'ERR' && status[1]),status);
});
});
t.test('V8 compiler', async t => {
const d = await execAsync('echo "'+code+'" | node --allow-natives-syntax');
t.same(statusCodes[d][0],'OK');
});

@@ -1,8 +0,10 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('optional argument',function() {
const shouldErr = () => { throw 'Should error'; };
var logic = {
t.test('optional argument', {autoend: true}, t => {
const Logic = {
data : function() { return Promise.delay(1000,5); },

@@ -14,40 +16,22 @@ passthrough : function(_optional) { return _optional; },

describe('not supplied',function() {
return it('should return undefined',function() {
clues(Object.create(logic),'passthrough')
.then(function(d) {
assert.deepEqual(d,undefined);
});
});
const facts = () => Object.create(Logic);
t.test('not supplied', async t => {
t.same(await clues(facts,'passthrough'),undefined,'returns undefined');
});
describe('with internal default',function() {
it('should use the internal default',function() {
return clues(Object.create(logic),'internalize')
.then(function(d) {
assert.equal(d,7);
});
});
t.test('internal default', async t => {
t.same(await clues(facts,'internalize'),7,'returns the default');
});
describe('with a set fact',function() {
it('should return the right value',function() {
return clues(Object.create(logic),'passthrough',{optional:10})
.then(function(d) {
assert.equal(d,10);
});
});
t.test('with a set global', async t => {
t.same(await clues(facts,'passthrough',{optional:10}),10,'returns global');
});
describe('with a working function',function() {
it('should return the function results',function() {
return clues(Object.create(logic),'optional_data')
.then(function(d) {
assert.equal(d,5);
});
});
t.test('with a working function', async t => {
t.same(await clues(facts,'optional_data'),5,'returns value');
});
describe('as an error',function() {
var logic2 = {
t.test('as an error', async t => {
const logic2 = {
error : function() { throw '#Error'; },

@@ -59,30 +43,19 @@ optional : function(_error) { return _error; },

it('should return undefined, if optional',function() {
return clues(Object.create(logic2),'optional')
.then(function(d) {
assert.equal(d,undefined);
});
});
const facts2 = () => Object.create(logic2);
it('should raise error if non-optonal',function() {
return clues(Object.create(logic2),'regular')
.then(function() {
throw 'Should error';
},function(e) {
assert.equal(e.message,'#Error');
});
});
let d = await clues(facts2,'optional');
t.same(d,undefined,'_ => undefined');
it('should return the error as an object when prefix is two underscores',function() {
return clues(Object.create(logic2),'showerror')
.then(function(e) {
assert.equal(e.error,true);
assert.equal(e.message,'#Error');
assert.equal(e.fullref,'showerror.error');
assert.equal(e.ref,'error');
});
});
d = await clues(facts2,'regular').then(shouldErr,Object);
t.same(d.message,'#Error','raises error if not optional');
d = await clues(facts2,'showerror');
t.same(d.error,true);
t.same(d.message,'#Error');
t.same(d.fullref,'showerror(error');
t.same(d.ref,'error');
});
});

@@ -1,10 +0,10 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
function shouldErr() { throw 'Should throw an error'; }
describe('private functions',function() {
var logic = {
M1 : function() { return Promise.delay(100,10); },
t.test('private functions', {autoend: true}, t => {
const Logic = {
M1 : function($private) { return Promise.delay(100,10); },
M2 : function $private() { return Promise.delay(20,300); },

@@ -19,46 +19,40 @@ M3 : ['M1','M2',function private(M1,M2) { return M1+M2; }],

var facts = Object.create(logic);
t.test('before resolution', {autoend: true}, t => {
t.test('private regular function', async t => {
const facts = Object.create(Logic);
const e = await clues(facts,'M2').catch(Object);
t.same(e.message,'M2 not defined','errors as not defined');
t.same(facts.M2,Logic.M2,'fn is not run');
});
describe('before resolution ',function() {
it('regular fn not disclosed',function() {
return clues(facts,'M2')
.then(shouldErr,function(e) {
assert.equal(e.message,'M2 not defined');
assert.equal(facts.M2,logic.M2);
});
t.test('private array function', async t => {
const facts = Object.create(Logic);
const e = await clues(facts,'M3').catch(Object);
t.same(e.message,'M3 not defined','errors as not defined')
t.same(facts.M3, Logic.M3,'fn is not run');
});
it('array defined fn not disclosed',function() {
return clues(facts,'M3')
.then(shouldErr,function(e) {
assert.equal(e.message,'M3 not defined');
assert.equal(facts.M3,logic.M3);
});
});
t.test('$private argument', async t => {
const facts = Object.create(Logic);
const e = await clues(facts,'M1').catch(Object);
t.same(e.message,'M1 not defined','errors as not defined')
t.same(facts.M1, Logic.M1,'fn is not run');
})
});
describe('after resolution',function() {
it('should be accessible indirectly',function() {
return clues(facts,'MTOP')
.then(function(d) {
assert.equal(d,380);
});
});
t.test('after resolution', async t => {
const facts = Object.create(Logic);
const MTOP = await clues(facts,'MTOP');
const M1 = await clues(facts,'M1').catch(Object);
const M2 = await clues(facts,'M2').catch(Object);
const M3 = await clues(facts,'M3').catch(Object);
it('regular fn promise not disclosed',function() {
return clues(facts,'M2')
.then(shouldErr,function(e) {
assert.equal(e.message,'M2 not defined');
assert.equal(facts.M2.value(),300);
});
});
it('array defined fn promise not disclosed',function() {
return clues(facts,'M3')
.then(shouldErr,function(e) {
assert.equal(e.message,'M3 not defined');
assert.equal(facts.M3.value(),310);
});
});
t.same(MTOP,380,'is available indirectly');
t.same(M1.message,'M1 not defined','private fn not available directly');
t.same(facts.M1.value(),10,'private fn promise has the resolved value');
t.same(M2.message,'M2 not defined','private fn not available directly');
t.same(facts.M2.value(),300,'private fn promise has the resolved value');
t.same(M3.message,'M3 not defined','private array - not defined');
t.same(facts.M3.value(),310,'private fn promise has the resolved value');
});
});

@@ -1,7 +0,6 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
var logic = {
const Logic = {
a : 41,

@@ -18,9 +17,4 @@ test : Promise.resolve(function(a) {

describe('When promise resolve to a function',function() {
it('is used when resolved',function() {
return clues(logic,'test')
.then(function(d) {
assert.equal(d,42);
});
});
});
t.test('when promise resolve to a function', async t => {
t.same(await clues(Logic,'test'),42,'function is evaluated');
});

@@ -1,156 +0,167 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('$property',function() {
var logic = {
a : 5,
simple : Object.create({
count : 0,
$property : function(ref) {
this.count+=1;
if (isNaN(ref)) throw 'NOT_A_NUMBER';
return +ref+2;
}
}),
funct : function(a) {
return {
t.test('$property', {autoend:true, jobs: 10}, t => {
t.test('in simple logic with $property', {autoend: true}, t => {
const facts = {
simple: {
count : 0,
$property : function(ref) {
this.count+=1;
return +ref+a;
if (isNaN(ref)) throw 'NOT_A_NUMBER';
return +ref+2;
}
};
},
nested : {
$property : function(ref) {
return { a : { b: { c: function() { return +ref+10; } } } };
}
},
shorthand : function $property(ref) {
return +ref * this.a;
},
concurrent : {
$property : function() {
return clues.Promise.delay(Math.random()*1000)
.then(function() {
return new Date();
});
}}
};
};
var facts = Object.create(logic);
t.test('asking for non-existent property', async t => {
const d = await clues(facts,'simple.1234');
t.same(d,1236,'returns the $property function value');
t.ok(facts.simple['1234'].isFulfilled(),'resolved promise is placed on logic');
t.same(facts.simple['1234'].value(),1236,'promise contains value');
t.same(facts.simple.count,1,'function is called once');
});
describe('in simple logic',function() {
it('runs $property function for non-existent property',function() {
return clues(facts,'simple.1234')
.then(function(d) {
assert.equal(d,1236);
assert(facts.simple['1234'].isFulfilled());
assert.equal(facts.simple['1234'].value(),1236);
assert.equal(facts.simple.count,1);
});
t.test('asking for a different property',async t => {
t.same(await clues(facts,'simple.1235'),1237,'returns new value');
t.same(facts.simple.count,2,'$property has now been called twice');
});
it('concurrent requests should return first response',function() {
var requests = Array.apply(null, {length: 10})
.map(function() {
return clues(facts,'concurrent.test');
});
return clues.Promise.all(requests)
.then(function(d) {
return d.map(function(e) {
assert.equal(e,d[0],'Return time should be the same');
});
});
t.test('results so far are cached', async t => {
const d = await Promise.all([ clues(facts,'simple.1234'), clues(facts,'simple.1235')]);
t.same(d[0],1236,'first is cached');
t.same(d[1],1237,'second is cached');
t.equal(facts.simple.count,2,'an $property is not run again');
});
it('runs $property again for a different property',function() {
return clues(facts,'simple.1235')
.then(function(d) {
assert.equal(d,1237);
assert.equal(facts.simple.count,2);
});
t.test('error raised in $property', async t => {
const e = await clues(facts,'simple.abc',{},'__test__').catch(Object);
t.same(e.message,'NOT_A_NUMBER','error message ok');
t.same(e.ref,'abc','ref is ok');
t.same(e.fullref,'simple.abc','fullref is ok');
t.same(e.caller,'__test__','caller is ok');
t.equal(facts.simple.abc.isRejected(),true,'rejected promise in logic');
t.equal(facts.simple.abc.reason().message,'NOT_A_NUMBER','rejected promise has error');
t.equal(facts.simple.count,3,'$property has now been called 3 times');
});
});
it('previous results are cached',function() {
return clues(facts,['simple.1234','simple.1235',function(a,b) {
assert.equal(a,1236);
assert.equal(b,1237);
assert.equal(facts.simple.count,2);
}]);
t.test('concurrent request', async t => {
const facts = {
concurrent : {
count: 0,
$property : function() {
return clues.Promise.delay(Math.random()*1000)
.then(() => {
this.count += 1;
return new Date();
});
}
}
};
const d = await Promise.map([...Array(10)],() => clues(facts,'concurrent.test'));
t.ok( d.every(e => e == d[0]),'should return the same value');
t.same(facts.concurrent.count,1,'and should not rerun the function');
});
t.test('$property inside a function', {autoend: true}, t => {
const facts = {
a : 5,
funct : a => ({
count : 0,
$property : function(ref) {
this.count+=1;
return +ref+a;
}
})
};
t.test('asking for non-existent property', async t => {
t.same( await clues(facts,'funct.1234'), 1239, 'returns value');
t.same(facts.funct.value().count,1,'$property is called once');
t.same( await clues(facts,'funct.1234'), 1239, 'previous results are cached');
t.same(facts.funct.value().count,1,'$an fn not run again for same property');
});
});
it('handles errors correctly',function() {
return clues(facts,'simple.abc',{},'__test__')
.then(function() {
throw 'Should Error';
},function(e) {
assert.equal(e.message,'NOT_A_NUMBER');
assert.equal(e.ref,'abc');
assert.equal(e.fullref,'simple.abc');
assert.equal(e.caller,'__test__');
assert.equal(facts.simple.abc.isRejected(),true);
assert.equal(facts.simple.abc.reason().message,'NOT_A_NUMBER');
assert.equal(facts.simple.count,3);
});
t.test('$property inside a nested structure', {autoend:true}, t => {
const facts = {
nested : {
$property : function(ref) {
return { a : { b: { c: function() { return +ref+10; } } } };
}
}
};
t.test('asking for non existing property nested', async t => {
t.same( await clues(facts,'nested.1234.a.b.c'),1244,'returns correct value');
t.ok(facts.nested['1234'].value().a.b.c.isFulfilled(),'registers fulfilled promise');
t.same(facts.nested['1234'].value().a.b.c.value(),1244,'promise registers value');
});
t.test('error raised in $property', async t => {
const e = await clues(facts,'nested.1234.a.b.d',{},'__test__').catch(Object);
t.same(e.ref,'d','ref ok');
t.same(e.fullref,'nested.1234.a.b.d','fullref ok');
t.same(e.caller,'__test__','caller ok');
});
});
describe('inside a function',function() {
it('runs $property function for non-existent property',function() {
return clues(facts,'funct.1234')
.then(function(d) {
assert.equal(d,1239);
assert.equal(facts.funct.value().count,1);
});
t.test('when function name is $property', {autoend: true}, t => {
const facts = {
a: 5,
shorthand : function $property(ref) {
return +ref * this.a;
}
};
t.test('asking for a property', async t => {
t.same( await clues(facts,'shorthand.2'),10,'returns right value');
t.same( facts.shorthand.value()['2'].value(),10,'stores promise with value');
});
it('caches previous results',function() {
return clues(facts,'funct.1234')
.then(function(d) {
assert.equal(d,1239);
assert.equal(facts.funct.value().count,1);
});
t.test('asking for another property', async t => {
t.same( await clues(facts,'shorthand.4'),20,'returns right value');
t.same( facts.shorthand.value()['4'].value(),20,'stores promise with value');
});
});
describe('inside a nested structure',function() {
it('runs $property',function() {
return clues(facts,'nested.1234.a.b.c')
.then(function(d) {
assert.equal(d,1244);
assert(facts.nested['1234'].value().a.b.c.isFulfilled());
assert(facts.nested['1234'].value(),1244);
t.test('when argument name is $property', {autoend: true}, t => {
const facts = {
a: 5,
as_argument: function ($property) {
return $property * this.a;
},
as_argument_es6 : a => $property => $property * a
};
t.test('regular function', async t => {
t.test('asking for a property', async t => {
t.same( await clues(facts,'as_argument.2'),10,'returns right value');
t.same( facts.as_argument.value()['2'].value(),10,'stores promise with value');
});
t.test('asking for another property', async t => {
t.same( await clues(facts,'as_argument.4'),20,'returns right value');
t.same( facts.as_argument.value()['4'].value(),20,'stores promise with value');
});
});
it('handles error correctly',function() {
return clues(facts,'nested.1234.a.b.d',{},'__test__')
.then(function() {
throw 'Should error';
},function(e) {
assert.equal(e.ref,'d');
assert.equal(e.fullref,'nested.1234.a.b.d');
assert.equal(e.caller,'__test__');
});
t.test('ES6 fat arrow', async t => {
t.test('asking for a property', async t => {
t.same( await clues(facts,'as_argument_es6.2'),10,'returns right value');
t.same( facts.as_argument_es6.value()['2'].value(),10,'stores promise with value');
});
t.test('asking for another property', async t => {
t.same( await clues(facts,'as_argument_es6.4'),20,'returns right value');
t.same( facts.as_argument_es6.value()['4'].value(),20,'stores promise with value');
});
});
});
describe('when function name is $property',function() {
it('acts as a shorthand for empty object with $property',function() {
return clues(facts,'shorthand.2')
.then(function(d) {
assert.equal(d,10);
assert.equal(facts.shorthand.value()['2'].value(),10);
return clues(facts,'shorthand.4');
})
.then(function(d) {
assert.equal(d,20);
assert.equal(facts.shorthand.value()['4'].value(),20);
assert.equal(facts.shorthand.value()['2'].value(),10);
});
});
});
});

@@ -1,7 +0,9 @@

var clues = require('../clues'),
assert = require('assert'),
Promise = require('bluebird');
const clues = require('../clues');
const Promise = require('bluebird');
const t = require('tap');
describe('In recursive logic',function() {
var logic = {
const shouldErr = () => { throw 'Should Error'; };
t.test('In recursive logic',{autoend: true}, t => {
const Logic = {
simple : {

@@ -23,11 +25,11 @@ value : 2,

e : {
f :12,
g : function(f) {
return 2*f;
},
h : function() {
return function(f) {
return f+2;
};
}
f :12,
g : function(f) {
return 2*f;
},
h : function() {
return function(f) {
return f+2;
};
}
}

@@ -42,96 +44,60 @@ }

var facts = Object.create(logic);
const facts = Object.create(Logic);
it('simple nesting works',function() {
return clues(facts,'simple.value')
.then(function(d) {
assert.equal(d,2);
});
t.test('simple nesting',async t => {
t.same( await clues(facts,'simple.value'),2,'works');
});
t.test('medium nesting works', async t => {
t.same( await clues(facts,'medium.bucket.value'),10,'works');
});
it('medium nesting works',function() {
return clues(facts,'medium.bucket.value')
.then(function(value) {
assert.equal(value,10);
});
t.test('ᐅ works as an alias for dot', async t => {
t.same( await clues(facts,'mediumᐅbucketᐅvalue'), 10, 'works');
});
t.test('complex nesting', async t => {
t.same( await clues(facts,'hard.a.b.c.d.id'),102,'works');
});
it('ᐅ works as an alias for dot',function() {
return clues(facts,'mediumᐅbucketᐅvalue')
.then(function(value) {
assert.equal(value,10);
});
t.test('ᐅ as an alias for dot',async t => {
t.same( await clues(facts,'hardᐅaᐅbᐅcᐅdᐅid'),102,'works');
});
describe('complex nesting',function() {
it('works', function() {
return clues(facts,'hard.a.b.c.d.id').then(function(value) {
assert.equal(value,102);
});
});
t.test('on returned functions',async t => {
t.same( await clues(facts,'hard.a.b.c.d.e.h'),14,'works');
});
it('ᐅ works as an alias for dot',function() {
return clues(facts,'hardᐅaᐅbᐅcᐅdᐅid')
.then(function(value) {
assert.equal(value,102);
});
});
t.test('works when fact repeats twice', async t => {
const d = await clues(facts,['hard.a.b.c.d.e','hard.a.b.c.d.e.f','hard.a.b.c.d.e.g',Array]);
t.same(d[1],12,'works');
t.same(d[2],24,'works');
});
it('works on returned functions',function() {
return clues(facts,'hard.a.b.c.d.e.h')
.then(function(h) {
assert.equal(h,14);
});
});
t.test('supports optional', async t => {
t.same( await clues(facts,['_hard.a.b.c.d.id',Number]),102,'works');
});
it('works when clue repeats twice',function() {
return clues(facts,['hard.a.b.c.d.e','hard.a.b.c.d.e.f','hard.a.b.c.d.e.g',function(a,b,c) {
assert.equal(b,12);
assert.equal(c,24);
}]);
});
it('supports optional',function() {
return clues(facts,['_hard.a.b.c.d.id',function(value) {
assert.equal(value,102);
}]);
});
it('can be resolved manually',function() {
return clues(facts,function(hard) {
return clues(hard.a.b,function(c) {
return clues(c.d,function(id) {
assert.equal(id,102);
});
});
t.test('path traversed manually', async t => {
const d = await clues(facts,hard => {
return clues(hard.a.b,c => {
return clues(c.d,'id');
});
});
t.same(d,102,'works');
});
it('bad path returns an error',function() {
return clues(facts,'hard.a.b.c.d.i.oo.oo')
.then(function() {
throw 'We should not arrive here';
},function(e) {
assert.equal(e.ref, 'i');
assert.equal(e.fullref, 'hard.a.b.c.d.i');
});
});
t.test('bad path returns an error', async t => {
const e = await clues(facts,'hard.a.b.c.d.i.oo.oo').then(shouldErr,Object);
t.same(e.message,'i not defined','errors');
t.same(e.ref,'i','ref ok');
t.same(e.fullref,'hard.a.b.c.d.i','fullref ok');
});
it('optional bad path returns undefined',function() {
return clues(facts,['_hard.a.b.e','simple.value',function(a,b) {
assert.equal(a,undefined);
assert.equal(b,2);
}]);
});
it('optional bad path returns undefined',function() {
return clues(facts,'hard.a.b.e')
.then(function() {
throw 'This function should return an error';
},function(e) {
assert.equal(e.message,'e not defined');
});
});
t.test('optional bad path returns undefined',async t => {
const d = await clues(facts,['_hard.a.b.e','simple.value',Array]);
t.same(d[0],undefined,'bad path is undefined');
t.same(d[1],2,'good path returns value');
});
});

@@ -1,59 +0,128 @@

var clues = require('../clues'),
assert = require('assert');
const clues = require('../clues');
const t = require('tap');
describe('$ as a first letter',function() {
t.test('$ as a first letter', {autoend:true}, t => {
var logic = {
const Logic = {
a : 10,
b : 11,
$logic_service : function(a) {
return a;
counter: {with_prep: 0, with_prep_arg: 0},
$logic_service : a => a,
$with_prep : function $prep(a,b,counter) {
counter.with_prep++;
let c = a + b;
return function $service(d) {
return c + d;
};
},
top : function(a) {
return {
$nested_service : function(b) {
return a+b;
}
$with_prep_arg : ($prep,a,b,counter) => {
counter.with_prep_arg++;
let c = a + b;
return function $service(d) {
return c + d;
};
}
},
top : a => ({ $nested_service : b => a + b })
};
var global = {
$global_service : function(b) {
return b;
}
};
const global = { $global_service : b => b };
describe('in logic',function() {
it ('should return a function',function() {
return clues(Object.create(logic),'$logic_service',Object.create(global))
.then(function($logic_service) {
assert.equal(typeof $logic_service,'function');
assert.equal($logic_service(20),20);
});
});
t.test('in logic', async t => {
const d = await clues(Object.create(Logic),'$logic_service',Object.create(global));
t.same(typeof d, 'function','returns a function');
t.same(d(20),20,'function works');
});
t.test('in nested logic', async t => {
const d = await clues(Object.create(Logic),'top.$nested_service',Object.create(global));
t.same(typeof d, 'function','returns a function');
t.same(d(20),30,'function works');
});
describe('in nested logic',function() {
it ('should return a function',function() {
return clues(Object.create(logic),'top.$nested_service',Object.create(global))
.then(function($nested_service) {
assert.equal(typeof $nested_service,'function');
assert.equal($nested_service(20),30);
});
});
t.test('in global', async t => {
await clues(Object.create(Logic),$global_service => {
t.same(typeof $global_service, 'function','returns a function');
t.same($global_service(20),20,'function works');
},Object.create(global));
});
describe('in global',function() {
it('should return a function',function() {
return clues(Object.create(logic),function($logic_service) {
assert.equal(typeof $logic_service,'function');
assert.equal($logic_service(20),20);
},Object.create(global));
t.test('with function called $prep', async t => {
t.test('prepares a service function', async t => {
const d = await clues(Object.create(Logic), '$with_prep', Object.create(global));
t.same(typeof d, 'function','returns a function');
t.same(d(5), 26);
});
t.test('prep solved only once', async t => {
let logic = Object.create(Logic);
logic.counter = {with_prep: 0, with_prep_arg: 0};
const d1 = await clues(logic, '$with_prep', Object.create(global));
const d2 = await clues(logic, '$with_prep', Object.create(global));
t.same(typeof d1, 'function','returns a function');
t.same(d1(5), 26);
t.same(logic.counter.with_prep, 1);
});
t.end();
});
});
t.test('with $prep as argument', async t => {
t.test('prepares a service function', async t => {
const d = await clues(Object.create(Logic), '$with_prep_arg', Object.create(global));
t.same(typeof d, 'function','returns a function');
t.same(d(5), 26);
});
t.test('prep solved only once', async t => {
let logic = Object.create(Logic);
logic.counter = {with_prep: 0, with_prep_arg: 0};
const d1 = await clues(logic, '$with_prep_arg', Object.create(global));
const d2 = await clues(logic, '$with_prep_arg', Object.create(global));
t.same(typeof d1, 'function','returns a function');
t.same(d1(5), 26);
t.same(logic.counter.with_prep_arg, 1);
});
t.end();
});
t.test('with $prep as an argument to a class function', async t => {
class Test {
constructor(a, counter) {
this.a = a;
this.counter = counter;
}
$multiply($prep,a,counter) {
counter.with_prep_arg++;
return function $service(d) {
return d * a;
};
}
}
const Logic = {
counter: {with_prep_arg: 0},
a: 2,
test: Test
};
t.test('prepares a service function', async t => {
const d = await clues(Object.create(Logic), 'test.$multiply', Object.create(global));
t.same(typeof d, 'function','returns a function');
t.same(d(5), 10);
});
t.test('prep solved only once', async t => {
let logic = Object.create(Logic);
logic.counter = {with_prep_arg: 0};
const d1 = await clues(logic, 'test.$multiply', Object.create(global));
const d2 = await clues(logic, 'test.$multiply', Object.create(global));
t.same(typeof d1, 'function','returns a function');
t.same(d1(5), 10);
t.same(logic.counter.with_prep_arg, 1);
});
t.end();
})
});

@@ -25,3 +25,4 @@ // This function sets given properties (each key is a full path) in a provided base object

function nextLevel() {
function nextLevel(o,base) {
base = base.slice();
var next = base.shift();

@@ -32,6 +33,10 @@ var original = o[next];

original = o[item];
if (original !== undefined)
o['original_'+item] = function private() {
if (original !== undefined) {
var isService = item[0] === '$';
var name = isService ? '$original_'+item.slice(1) : 'original_'+item;
Object.defineProperty(o,name,{writable: true, value: isService ? original : function $private() {
return original;
};
}});
}
if (value.error)

@@ -42,16 +47,16 @@ value = clues.Promise.reject(value);

else
o[item] = value;
Object.defineProperty(o,item,{value: value, enumerable: true, writable: true});
} else {
if (original !== undefined)
o[next] = function() {
return clues(o,original,$global,'set','set')
.then(d => {
o = d;
return clues.Promise.try(o ? nextLevel : Object).then( () => d);
var fn = function() {
return clues(this,original || [],$global,'set','set')
.then(d => {
return clues.Promise.resolve(d && nextLevel(d,base)).then( () => d);
});
};
Object.defineProperty(o,next,{value: fn, enumerable: true, writable: true});
}
}
return nextLevel();
return nextLevel(o,base);
})

@@ -58,0 +63,0 @@ .then(function() {

@@ -31,6 +31,7 @@ (function(self) {

function fetch() {
function fetch(options) {
var self = this,
buffer = '',
queue = self.$queue;
queue = self.$queue,
extraHandler = options.extraHandler;

@@ -75,3 +76,2 @@ delete self.$queue;

var key = m[1],value;
if (!self[key]) return;

@@ -83,2 +83,10 @@ try {

}
if (!self[key]) {
if (extraHandler && value && !value.error) {
extraHandler(key, value);
}
return;
}
if (value && value.error)

@@ -85,0 +93,0 @@ queue[key].reject(value);

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