Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed typo: performPostRequst -> performPostRequest #32

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
20319e9
Apply dedup before and after plugins if need be
LFDM Apr 26, 2017
ae0ba4a
Remove hook from EntityStore
LFDM Apr 30, 2017
ab77604
Pass notify fn to all operations
LFDM Apr 30, 2017
2209db7
Normalize fn name for change objects
LFDM Apr 30, 2017
1d7887b
Update build syntax
LFDM Apr 30, 2017
fbb5607
More expressive spec naming
LFDM Apr 30, 2017
dfc98a1
Trigger notify on READ operations
LFDM Apr 30, 2017
0f367e0
Better spec organization
LFDM Apr 30, 2017
489cac8
Use return values, not done callback in READ specs
LFDM Apr 30, 2017
4f7fb59
Add specs for notify calls on READ
LFDM Apr 30, 2017
bb0c531
Add notify spec for update
LFDM Apr 30, 2017
2e0389a
Use returned promise instead of done callback
LFDM Apr 30, 2017
b42526d
Add notification specs for DELETE
LFDM Apr 30, 2017
08b321d
Add notification specs for NO_OPERATION
LFDM Apr 30, 2017
b2e3caf
Add notification on CREATE
LFDM Apr 30, 2017
5fbcb78
Remove changeObject type, rely on operation
LFDM Apr 30, 2017
a3024b5
Update documentation of change object
LFDM Apr 30, 2017
e95859c
Update version for testing
LFDM Apr 30, 2017
0d45fc2
Trigger notification on NO_OPERTATION
LFDM Apr 30, 2017
27a2aab
Update docs again
LFDM Apr 30, 2017
bed673d
Add spec for addChangeListener deregistration
LFDM Apr 30, 2017
aa7d9e3
More branches to test
LFDM Apr 30, 2017
2fa0ec5
Merge pull request #26 from ladda-js/feat/dedup-and-change-hook
LFDM May 1, 2017
02a07a4
Update package.json: new homepage URL
timurc May 15, 2017
7801197
Update readme: redirected links to new pages
timurc May 15, 2017
b6b1ddd
Merge pull request #29 from timurc/master
petercrona May 17, 2017
cb8ccb1
Merge pull request #28 from timurc/patch-1
petercrona May 17, 2017
361d9e2
remove bound prefix from functions when creating keys
petercrona May 26, 2017
d96c201
Merge pull request #30 from ladda-js/fix-bound-issue
petercrona May 26, 2017
d36f87b
bump version
petercrona May 26, 2017
ac05047
bump version
petercrona May 26, 2017
82bd692
Make sure we are allowed to write to the name property of our apiFns
LFDM May 29, 2017
c02502c
Do NOT rely on the name property of functions, choose own property
LFDM May 29, 2017
07bd2d2
Add info about fnName to docs
LFDM May 29, 2017
987185b
Do not allow users to specify the fnName through an annotation
LFDM May 29, 2017
ddfc0ec
Fixed typo: performPostRequst -> performPostRequest
drdla Jul 19, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ The easiest way to get a glimpse of what Ladda can do is checking out our [demos

# Get Started

Check out the [guide](/docs/GettingStarted.md) for getting started. In addition, you can have a look in the [examples folder](https://github.com/petercrona/ladda/tree/master/examples). These are standalone examples where you only need to follow the README.md to setup the project. There is an addtional minimal example, where you can find everything in one file, that you can clone and run: Check out [ladda-example-mini-project](https://github.com/petercrona/ladda-example-mini-project) ([code](https://github.com/petercrona/ladda-example-mini-project/blob/master/script.js)).
Check out the [guide](/docs/GettingStarted.md) for getting started. In addition, you can have a look in the [examples folder](https://github.com/ladda-js/ladda/tree/master/examples). These are standalone examples where you only need to follow the README.md to setup the project. There is an addtional minimal example, where you can find everything in one file, that you can clone and run: Check out [ladda-example-mini-project](https://github.com/petercrona/ladda-example-mini-project) ([code](https://github.com/petercrona/ladda-example-mini-project/blob/master/script.js)).

# Documentation

The documentation gives you an [exhaustive overview of Ladda](https://petercrona.gitbooks.io/ladda/content/).
The documentation gives you an [exhaustive overview of Ladda](https://www.ladda.io/).

# Why Use Ladda?

Expand All @@ -35,7 +35,7 @@ Ladda is a lightweight library and comes with no additional dependencies. The li

## Quality

Ladda has a high test coverage (**100%** line coverage) with tests constantly being added. And yes, we know that high test coverage is a "feel good" number, our focus is still on meaningful and good tests. It has a reasonably simple architecture and often tries to stay [tacit](https://www.youtube.com/watch?v=seVSlKazsNk&feature=youtu.be) and concise by taking inspiration from [functional programming](https://drboolean.gitbooks.io/mostly-adequate-guide/content/). We urge you to check out the [source code](https://github.com/petercrona/ladda/tree/master/src). You can help us to improve it further or just enjoy reading functional JavaScript.
Ladda has a high test coverage (**100%** line coverage) with tests constantly being added. And yes, we know that high test coverage is a "feel good" number, our focus is still on meaningful and good tests. It has a reasonably simple architecture and often tries to stay [tacit](https://www.youtube.com/watch?v=seVSlKazsNk&feature=youtu.be) and concise by taking inspiration from [functional programming](https://drboolean.gitbooks.io/mostly-adequate-guide/content/). We urge you to check out the [source code](https://github.com/ladda-js/ladda/tree/master/src). You can help us to improve it further or just enjoy reading functional JavaScript.

## Standalone

Expand Down
34 changes: 19 additions & 15 deletions docs/advanced/Plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,20 @@ following shape:

```javascript
{
type: 'UPDATE' | 'REMOVE',
operation: 'CREATE' | 'READ' | 'UPDATE' | 'DELETE' | 'NO_OPERATION',
entity: EntityName,
entities: EntityValue[]
apiFn: ApiFunctionName,
args: Any[] | null
values: EntityValue[],
}
```

At this point in time there is no difference made between adding new
EntityValues and updating already present ones: Both events lead to a
change of the type `UPDATE`.
The `entities` field is guaranteed to be a list of EntityValues, even if
a change only affects a single entity.
It provides all information about which call triggered a change,
including the arguments array.

The `values` field is guaranteed to be a list of EntityValues, even if
a change only affects a single entity. The only expection are
`NO_OPERATION` operations, which will always return `null` here.

`addChangeListener` returns a deregistration function. Call it to stop
listening for changes.
Expand Down Expand Up @@ -209,7 +212,8 @@ with two fields:
applied and an additional `name` property is present to identify it.
- `fn` is the original __ApiFunction__ we want to act on. It has all
meta data attached, that was defined in the build configuration,
including defaults.
including defaults. In addition Ladda's `build` function also added the
property `fnName`, so that we can easily identify it.

With this comprehensive information we can easily add additional
behavior to an ApiFunction.
Expand All @@ -228,14 +232,14 @@ export const logger = (pluginConfig = {}) => {

return ({ entity, fn }) => {
return (...args) => {
console.log(`Ladda: Calling ${entity.name}.${fn.name} with args`, args);
console.log(`Ladda: Calling ${entity.name}.${fn.fnName} with args`, args);
return fn(...args).then(
(res) => {
console.log(`Ladda: Resolved ${entity.name}.${fn.name} with`, res);
console.log(`Ladda: Resolved ${entity.name}.${fn.fnName} with`, res);
return res;
},
(err) => {
console.log(`Ladda: Rejected ${entity.name}.${fn.name} with`, err)
console.log(`Ladda: Rejected ${entity.name}.${fn.fnName} with`, err)
return Promise.reject(err);
}
);
Expand All @@ -249,7 +253,7 @@ We issue a first log statement immediately when the function is invoked
and print out the arguments we received. By using the entity
configuration we got passed in and the meta data of the ApiFunction we
can produce a nice string to reveal which function just got called:
`${entity.name}.${fn.name}`. This could for example produce
`${entity.name}.${fn.fnName}`. This could for example produce
something like `user.getAll`.

We then use Promise chaining to intercept the result of our original
Expand Down Expand Up @@ -282,14 +286,14 @@ export const logger = (pluginConfig = {}) => {

return ({ entity, fn }) => {
return (...args) => {
console.log(`Ladda: Calling ${entity.name}.${fn.name} with args`, args);
console.log(`Ladda: Calling ${entity.name}.${fn.fnName} with args`, args);
return fn(...args).then(
(res) => {
console.log(`Ladda: Resolved ${entity.name}.${fn.name} with`, res);
console.log(`Ladda: Resolved ${entity.name}.${fn.fnName} with`, res);
return res;
},
(err) => {
console.log(`Ladda: Rejected ${entity.name}.${fn.name} with`, err)
console.log(`Ladda: Rejected ${entity.name}.${fn.fnName} with`, err)
return Promise.reject(err);
}
);
Expand Down
2 changes: 1 addition & 1 deletion docs/basics/Operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ An example of how a function of operation CREATE might look is:
```javascript
createUser.operation = 'CREATE';
function createUser(user) {
return performPostRequst('/api/user', user);
return performPostRequest('/api/user', user);
}
```

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ladda-cache",
"version": "0.2.1",
"version": "0.2.7",
"description": "Data fetching layer with support for caching",
"main": "dist/bundle.js",
"dependencies": {
Expand Down Expand Up @@ -43,5 +43,5 @@
"type": "git",
"url": "https://github.com/ladda-js/ladda.git"
},
"homepage": "https://github.com/ladda-js/ladda"
"homepage": "https://www.ladda.io/"
}
38 changes: 23 additions & 15 deletions src/builder.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {mapObject, mapValues, compose, toObject, reduce, toPairs,
prop, filterObject, isEqual, not, curry, copyFunction
import {map, mapObject, mapValues, compose, toObject, reduce, fromPairs,
toPairs, prop, filterObject, isEqual, not, curry, copyFunction
} from 'ladda-fp';

import {cachePlugin} from './plugins/cache';
Expand All @@ -22,13 +22,6 @@ const KNOWN_STATICS = {
arity: true
};

const setFnName = curry((name, fn) => {
Object.defineProperty(fn, 'name', { writable: true });
fn.name = name;
Object.defineProperty(fn, 'name', { writable: false });
return fn;
});

const hoistMetaData = (a, b) => {
const keys = Object.getOwnPropertyNames(a);
for (let i = keys.length - 1; i >= 0; i--) {
Expand All @@ -37,7 +30,6 @@ const hoistMetaData = (a, b) => {
b[k] = a[k];
}
}
setFnName(a.name, b);
return b;
};

Expand All @@ -52,9 +44,7 @@ export const mapApiFunctions = (fn, entityConfigs) => {
// containing a "bound" prefix.
(apiM, [apiFnName, apiFn]) => {
const getFn = compose(prop(apiFnName), prop('api'));
const nextFn = hoistMetaData(getFn(entity), fn({ entity, fn: apiFn }));
setFnName(apiFnName, nextFn);
apiM[apiFnName] = nextFn;
apiM[apiFnName] = hoistMetaData(getFn(entity), fn({ entity, fn: apiFn }));
return apiM;
},
{},
Expand Down Expand Up @@ -100,9 +90,21 @@ const setApiConfigDefaults = ec => {
return copy;
};

const setFnName = ([name, apiFn]) => {
apiFn.fnName = name;
return [name, apiFn];
};

const mapApi = compose(
fromPairs,
map(setFnName),
toPairs,
mapValues(setDefaults)
);

return {
...ec,
api: ec.api ? mapValues(setDefaults, ec.api) : ec.api
api: ec.api ? mapApi(ec.api) : ec.api
};
};

Expand All @@ -127,6 +129,12 @@ const applyPlugin = curry((addChangeListener, config, entityConfigs, plugin) =>
return mapApiFunctions(pluginDecorator, entityConfigs);
});

const createPluginList = (core, plugins) => {
return plugins.length ?
[core, dedupPlugin, ...plugins, dedupPlugin] :
[core, dedupPlugin];
};

// Config -> Api
export const build = (c, ps = []) => {
const config = getGlobalConfig(c);
Expand All @@ -136,5 +144,5 @@ export const build = (c, ps = []) => {
const applyPlugin_ = applyPlugin(listenerStore.addChangeListener, config);
const applyPlugins = reduce(applyPlugin_, entityConfigs);
const createApi = compose(toApi, applyPlugins);
return createApi([cachePlugin(listenerStore.onChange), ...ps, dedupPlugin]);
return createApi(createPluginList(cachePlugin(listenerStore.onChange), ps));
};
98 changes: 89 additions & 9 deletions src/builder.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ getUsers.operation = 'READ';
const deleteUser = () => Promise.resolve();
deleteUser.operation = 'DELETE';

const noopUser = () => Promise.resolve(['a', 'b']);
noopUser.operation = 'NO_OPERATION';

const config = () => ({
user: {
ttl: 300,
api: {
getUsers,
deleteUser
deleteUser,
noopUser
},
invalidates: ['alles']
},
Expand Down Expand Up @@ -139,7 +143,7 @@ describe('builder', () => {
const pName = pConfig.name;
pluginTracker[pName] = {};
return curry(({ config: c, entityConfigs }, { fn }) => {
pluginTracker[pName][fn.name] = true;
pluginTracker[pName][fn.fnName] = true;
return fn;
});
};
Expand All @@ -152,6 +156,23 @@ describe('builder', () => {
.then(() => done());
});

it('applies dedup before and after the plugins, if there are any', () => {
const getAll = sinon.stub().returns(Promise.resolve([]));
getAll.operation = 'READ';
const conf = { test: { api: { getAll } } };
const plugin = () => ({ fn }) => () => {
fn();
fn();
return fn();
};
const api = build(conf, [plugin]);
api.test.getAll();
api.test.getAll();
return api.test.getAll().then(() => {
expect(getAll).to.have.been.calledOnce;
});
});

describe('change listener', () => {
it('exposes Ladda\'s listener/onChange interface to plugins', () => {
const plugin = ({ addChangeListener }) => {
Expand All @@ -162,22 +183,81 @@ describe('builder', () => {
build(config(), [plugin]);
});

it('allows plugins to add a listener, which gets notified on all cache changes', () => {
it('returns a deregistration fn', () => {
const spy = sinon.spy();

const plugin = ({ addChangeListener }) => {
addChangeListener(spy);
const deregister = addChangeListener(spy);
deregister();
return ({ fn }) => fn;
};

const api = build(config(), [plugin]);

return api.user.getUsers().then(() => {
expect(spy).to.have.been.calledOnce;
const changeObject = spy.args[0][0];
expect(changeObject.entity).to.equal('user');
expect(changeObject.type).to.equal('UPDATE');
expect(changeObject.entities).to.deep.equal(users);
expect(spy).not.to.have.been.called;
});
});

it('can call deregistration fn several times without harm', () => {
const spy = sinon.spy();

const plugin = ({ addChangeListener }) => {
const deregister = addChangeListener(spy);
deregister();
deregister();
deregister();
return ({ fn }) => fn;
};

const api = build(config(), [plugin]);

return api.user.getUsers().then(() => {
expect(spy).not.to.have.been.called;
});
});

describe('allows plugins to add a listener, which gets notified on all cache changes', () => {
it('on READ operations', () => {
const spy = sinon.spy();

const plugin = ({ addChangeListener }) => {
addChangeListener(spy);
return ({ fn }) => fn;
};

const api = build(config(), [plugin]);

return api.user.getUsers().then(() => {
expect(spy).to.have.been.calledOnce;
const changeObject = spy.args[0][0];
expect(changeObject.entity).to.equal('user');
expect(changeObject.apiFn).to.equal('getUsers');
expect(changeObject.operation).to.equal('READ');
expect(changeObject.values).to.deep.equal(users);
expect(changeObject.args).to.deep.equal([]);
});
});

it('on NO_OPERATION operations', () => {
const spy = sinon.spy();

const plugin = ({ addChangeListener }) => {
addChangeListener(spy);
return ({ fn }) => fn;
};

const api = build(config(), [plugin]);

return api.user.noopUser('x').then(() => {
expect(spy).to.have.been.calledOnce;
const changeObject = spy.args[0][0];
expect(changeObject.entity).to.equal('user');
expect(changeObject.apiFn).to.equal('noopUser');
expect(changeObject.operation).to.equal('NO_OPERATION');
expect(changeObject.values).to.deep.equal(null);
expect(changeObject.args).to.deep.equal(['x']);
});
});
});

Expand Down
Loading