Skip to content

Commit

Permalink
Merge pull request #29 from XbyOrange/v1.4.0
Browse files Browse the repository at this point in the history
V1.4.0
  • Loading branch information
javierbrea authored Oct 14, 2019
2 parents 54cabf2 + 881a5af commit c1339ed
Show file tree
Hide file tree
Showing 9 changed files with 460 additions and 66 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [TO BE DEPRECATED]
- Last argument of Selectors will stop being assigned as "defaultValue". To define default value, it will be mandatory to pass an options object as last argument, containing a "defaultValue" property.

## [1.4.0] - 2019-10-14
### Added
- Selectors can now return an array of sources.
- Selectors can now return sources defined as objects containing `query` and/or `catch` property.

### Fixed
- Fix Sonar code smell.

## [1.3.0] - 2019-10-14
### Added
- defaultValue argument in Origin Constructor now can be a function. It will be called to obtain the defaultValue, passing to it the current query as argument.
Expand Down
74 changes: 72 additions & 2 deletions docs/selector/selectors-returning-sources.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
## Selectors returning another source

Selectors can return another source. Then, the returned source will be called with same method and parameters than the Selector was.
Selectors can return another source or array of sources. Then, the returned sources will be called with same method and parameters than the Selector was.

Cache listeners will be added too to this returned Selector, so, if returned source cache is cleaned, the Selector cache will be cleaned too.
Cache listeners will be added too to this returned Selector, so, if any of the returned sources cache is cleaned, the Selector cache will be cleaned too.

```js

Expand Down Expand Up @@ -49,3 +49,73 @@ await bookDetails.query("foo-id").update({
});

```

Selectors can return an array of sources:

```js

import { Api } from "@xbyorange/mercury-api";

import { authorsOrigin } from "./authors";
import { booksOrigin } from "./books";

const authorBooksData = new Selector(
{
source: authorsOrigin,
query: queriedId => ({
urlParams: {
id: queriedId
}
})
},
authorDetails => {
return authorDetails.booksIds.map(bookId => booksOrigin.query({
urlParams: {
id: bookId
}
}))
}
);

// Call to api "n" times for recovering data of all books of author with id "foo-id"
const authorBooksData = await authorBooksData.query("foo-id").read();

```

Returned sources can be defined as objects containing query callback or catch functions as well:

```js

import { Api } from "@xbyorange/mercury-api";

import { authorsOrigin } from "./authors";
import { booksOrigin } from "./books";

const authorBooksData = new Selector(
{
source: authorsOrigin,
query: queriedId => ({
urlParams: {
id: queriedId
}
})
},
authorDetails => {
return authorDetails.booksIds.map(bookId => ({
source: booksOrigin,
query: () => ({
urlParams: {
id: bookId
}
}),
catch: () => Promise.resolve({
title: "Error recovering book title"
})
}))
}
);

const authorBooksData = await authorBooksData.query("foo-id").read();

```

2 changes: 1 addition & 1 deletion docs/selector/sources-error-handling.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Sources error handling

Dependant sources of a Selector can return an error. Then, the full Selector will not be resolved. You can catch those source errors and transform them into a data of your convenience, or even delegate or "retry" that source into another source.
Dependant sources of a Selector can return an error. Then, the full Selector will not be resolved. You can catch those source errors and transform them into a data of your convenience, or even delegate or "retry" that source into another source or array of sources.

Use the `catch` property of a custom source to catch his errors:

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xbyorange/mercury",
"version": "1.3.0",
"version": "1.4.0",
"description": "Mercury. Reactive CRUD data layer",
"keywords": [
"reactive",
Expand Down
37 changes: 21 additions & 16 deletions src/Selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
CREATE_METHOD,
UPDATE_METHOD,
DELETE_METHOD,
seemsToBeSelectorOptions
seemsToBeSelectorOptions,
areSources
} from "./helpers";

export class Selector extends Origin {
Expand Down Expand Up @@ -40,9 +41,9 @@ export class Selector extends Origin {
const catches = [];
sourcesOfLevel.forEach(source => {
if (Array.isArray(source)) {
const testObjects = getTestObjects(source);
queries.push(testObjects.queries);
catches.push(testObjects.catches);
const childTestObjects = getTestObjects(source);
queries.push(childTestObjects.queries);
catches.push(childTestObjects.catches);
} else {
const isSourceObject = !!source.source;
sourceIds.push(isSourceObject ? source.source._id : source._id);
Expand Down Expand Up @@ -74,8 +75,6 @@ export class Selector extends Origin {
_readAllSourcesAndDispatch(query, extraParams, methodToDispatch) {
const sourcesResults = [];
const sources = [];
let selectorResult;
let selectorResultIsSource;
const cleanQuery = once(() => {
this.clean(query);
});
Expand All @@ -96,9 +95,9 @@ export class Selector extends Origin {
return source[READ_METHOD].dispatch().catch(error => {
if (hasToCatch) {
const catchResult = sourceToRead.catch(error, query);
if (catchResult._isSource) {
if (areSources(catchResult)) {
sources.push(catchResult);
return catchResult[READ_METHOD].dispatch();
return readSource(catchResult);
}
return catchResult;
}
Expand All @@ -121,21 +120,27 @@ export class Selector extends Origin {
sources.forEach(source => {
source.onceClean(cleanQuery);
});
if (selectorResultIsSource) {
selectorResult.onceClean(cleanQuery);
}
};

return readSourceIndex(0)
.then(result => {
selectorResult = result;
selectorResultIsSource = selectorResult && selectorResult._isSource;
const selectorResult = result;
const selectorResultIsSource = areSources(selectorResult);
if (methodToDispatch !== READ_METHOD && !selectorResultIsSource) {
return Promise.reject(new Error("CUD methods can be used only when returning sources"));
}
return selectorResultIsSource
? selectorResult[methodToDispatch].dispatch(extraParams)
: Promise.resolve(selectorResult);
if (selectorResultIsSource) {
if (methodToDispatch === READ_METHOD) {
return readSource(selectorResult);
}
if (Array.isArray(selectorResult)) {
return Promise.all(
selectorResult.map(source => source[methodToDispatch].dispatch(extraParams))
);
}
return selectorResult[methodToDispatch].dispatch(extraParams);
}
return Promise.resolve(selectorResult);
})
.then(result => {
addCleanQueryListeners();
Expand Down
17 changes: 17 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,20 @@ export const seemsToBeSelectorOptions = defaultValueOrOptions => {
defaultValueOrOptions.hasOwnProperty("uuid")
);
};

export const isSource = objectToCheck => {
return (
objectToCheck &&
(objectToCheck._isSource === true || (objectToCheck.source && objectToCheck.source._isSource))
);
};

export const areSources = arrayToCheck => {
let allAreSources = true;
ensureArray(arrayToCheck).forEach(arrayElement => {
if (!isSource(arrayElement)) {
allAreSources = false;
}
});
return allAreSources;
};
95 changes: 50 additions & 45 deletions test/Selector.cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,60 +258,65 @@ test.describe("Selector cache", () => {
});
});

test.describe("when returns another source", () => {
test.beforeEach(() => {
testSelector = new Selector(
testOrigin,
testOrigin2,
(originResult, origin2Result, query) => {
spies.testSelector(query);
return testOriginSelector.query(query);
}
);
});
const testDynamicSelectors = (description, selectorCallback) => {
test.describe(description, () => {
test.beforeEach(() => {
testSelector = new Selector(testOrigin, testOrigin2, selectorCallback);
});

test.describe("when no query is passed", () => {
test.it("should clean cache when source cache is cleaned", () => {
return testSelector.read().then(() => {
testOriginSelector.clean();
test.describe("when no query is passed", () => {
test.it("should clean cache when source cache is cleaned", () => {
return testSelector.read().then(() => {
return test.expect(spies.testSelector.callCount).to.equal(2);
testOriginSelector.clean();
return testSelector.read().then(() => {
return test.expect(spies.testSelector.callCount).to.equal(2);
});
});
});
});
});

test.describe("when query is passed", () => {
test.it("should clean cache when source cache is cleaned with same query", () => {
const FOO_QUERY = "foo-query";
return testSelector
.query(FOO_QUERY)
.read()
.then(() => {
testOriginSelector.query(FOO_QUERY).clean();
return testSelector
.query(FOO_QUERY)
.read()
.then(() => {
return test.expect(spies.testSelector.callCount).to.equal(2);
});
});
});
test.describe("when query is passed", () => {
test.it("should clean cache when source cache is cleaned with same query", () => {
const FOO_QUERY = "foo-query";
return testSelector
.query(FOO_QUERY)
.read()
.then(() => {
testOriginSelector.query(FOO_QUERY).clean();
return testSelector
.query(FOO_QUERY)
.read()
.then(() => {
return test.expect(spies.testSelector.callCount).to.equal(2);
});
});
});

test.it("should not clean cache when source cache is cleaned with another query", () => {
const FOO_QUERY = "foo-query";
return testSelector
.query(FOO_QUERY)
.read()
.then(() => {
testOriginSelector.query("foo").clean();
return testSelector
.query(FOO_QUERY)
.read()
.then(checkHasBeenCalledOnce);
});
test.it("should not clean cache when source cache is cleaned with another query", () => {
const FOO_QUERY = "foo-query";
return testSelector
.query(FOO_QUERY)
.read()
.then(() => {
testOriginSelector.query("foo").clean();
return testSelector
.query(FOO_QUERY)
.read()
.then(checkHasBeenCalledOnce);
});
});
});
});
};

testDynamicSelectors("when returns another source", (originResult, origin2Result, query) => {
spies.testSelector(query);
return testOriginSelector.query(query);
});

testDynamicSelectors("when returns multiple sources", (originResult, origin2Result, query) => {
spies.testSelector(query);
return [testOriginSelector.query(query), testOriginSelector];
});

test.describe("cud methods", () => {
Expand Down
Loading

0 comments on commit c1339ed

Please sign in to comment.