Skip to content

Commit c1339ed

Browse files
authored
Merge pull request #29 from XbyOrange/v1.4.0
V1.4.0
2 parents 54cabf2 + 881a5af commit c1339ed

9 files changed

+460
-66
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1414
## [TO BE DEPRECATED]
1515
- 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.
1616

17+
## [1.4.0] - 2019-10-14
18+
### Added
19+
- Selectors can now return an array of sources.
20+
- Selectors can now return sources defined as objects containing `query` and/or `catch` property.
21+
22+
### Fixed
23+
- Fix Sonar code smell.
24+
1725
## [1.3.0] - 2019-10-14
1826
### Added
1927
- 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.

docs/selector/selectors-returning-sources.md

+72-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
## Selectors returning another source
22

3-
Selectors can return another source. Then, the returned source will be called with same method and parameters than the Selector was.
3+
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.
44

5-
Cache listeners will be added too to this returned Selector, so, if returned source cache is cleaned, the Selector cache will be cleaned too.
5+
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.
66

77
```js
88

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

5151
```
52+
53+
Selectors can return an array of sources:
54+
55+
```js
56+
57+
import { Api } from "@xbyorange/mercury-api";
58+
59+
import { authorsOrigin } from "./authors";
60+
import { booksOrigin } from "./books";
61+
62+
const authorBooksData = new Selector(
63+
{
64+
source: authorsOrigin,
65+
query: queriedId => ({
66+
urlParams: {
67+
id: queriedId
68+
}
69+
})
70+
},
71+
authorDetails => {
72+
return authorDetails.booksIds.map(bookId => booksOrigin.query({
73+
urlParams: {
74+
id: bookId
75+
}
76+
}))
77+
}
78+
);
79+
80+
// Call to api "n" times for recovering data of all books of author with id "foo-id"
81+
const authorBooksData = await authorBooksData.query("foo-id").read();
82+
83+
```
84+
85+
Returned sources can be defined as objects containing query callback or catch functions as well:
86+
87+
```js
88+
89+
import { Api } from "@xbyorange/mercury-api";
90+
91+
import { authorsOrigin } from "./authors";
92+
import { booksOrigin } from "./books";
93+
94+
const authorBooksData = new Selector(
95+
{
96+
source: authorsOrigin,
97+
query: queriedId => ({
98+
urlParams: {
99+
id: queriedId
100+
}
101+
})
102+
},
103+
authorDetails => {
104+
return authorDetails.booksIds.map(bookId => ({
105+
source: booksOrigin,
106+
query: () => ({
107+
urlParams: {
108+
id: bookId
109+
}
110+
}),
111+
catch: () => Promise.resolve({
112+
title: "Error recovering book title"
113+
})
114+
}))
115+
}
116+
);
117+
118+
const authorBooksData = await authorBooksData.query("foo-id").read();
119+
120+
```
121+

docs/selector/sources-error-handling.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Sources error handling
22

3-
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.
3+
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.
44

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

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@xbyorange/mercury",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"description": "Mercury. Reactive CRUD data layer",
55
"keywords": [
66
"reactive",

src/Selector.js

+21-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
CREATE_METHOD,
88
UPDATE_METHOD,
99
DELETE_METHOD,
10-
seemsToBeSelectorOptions
10+
seemsToBeSelectorOptions,
11+
areSources
1112
} from "./helpers";
1213

1314
export class Selector extends Origin {
@@ -40,9 +41,9 @@ export class Selector extends Origin {
4041
const catches = [];
4142
sourcesOfLevel.forEach(source => {
4243
if (Array.isArray(source)) {
43-
const testObjects = getTestObjects(source);
44-
queries.push(testObjects.queries);
45-
catches.push(testObjects.catches);
44+
const childTestObjects = getTestObjects(source);
45+
queries.push(childTestObjects.queries);
46+
catches.push(childTestObjects.catches);
4647
} else {
4748
const isSourceObject = !!source.source;
4849
sourceIds.push(isSourceObject ? source.source._id : source._id);
@@ -74,8 +75,6 @@ export class Selector extends Origin {
7475
_readAllSourcesAndDispatch(query, extraParams, methodToDispatch) {
7576
const sourcesResults = [];
7677
const sources = [];
77-
let selectorResult;
78-
let selectorResultIsSource;
7978
const cleanQuery = once(() => {
8079
this.clean(query);
8180
});
@@ -96,9 +95,9 @@ export class Selector extends Origin {
9695
return source[READ_METHOD].dispatch().catch(error => {
9796
if (hasToCatch) {
9897
const catchResult = sourceToRead.catch(error, query);
99-
if (catchResult._isSource) {
98+
if (areSources(catchResult)) {
10099
sources.push(catchResult);
101-
return catchResult[READ_METHOD].dispatch();
100+
return readSource(catchResult);
102101
}
103102
return catchResult;
104103
}
@@ -121,21 +120,27 @@ export class Selector extends Origin {
121120
sources.forEach(source => {
122121
source.onceClean(cleanQuery);
123122
});
124-
if (selectorResultIsSource) {
125-
selectorResult.onceClean(cleanQuery);
126-
}
127123
};
128124

129125
return readSourceIndex(0)
130126
.then(result => {
131-
selectorResult = result;
132-
selectorResultIsSource = selectorResult && selectorResult._isSource;
127+
const selectorResult = result;
128+
const selectorResultIsSource = areSources(selectorResult);
133129
if (methodToDispatch !== READ_METHOD && !selectorResultIsSource) {
134130
return Promise.reject(new Error("CUD methods can be used only when returning sources"));
135131
}
136-
return selectorResultIsSource
137-
? selectorResult[methodToDispatch].dispatch(extraParams)
138-
: Promise.resolve(selectorResult);
132+
if (selectorResultIsSource) {
133+
if (methodToDispatch === READ_METHOD) {
134+
return readSource(selectorResult);
135+
}
136+
if (Array.isArray(selectorResult)) {
137+
return Promise.all(
138+
selectorResult.map(source => source[methodToDispatch].dispatch(extraParams))
139+
);
140+
}
141+
return selectorResult[methodToDispatch].dispatch(extraParams);
142+
}
143+
return Promise.resolve(selectorResult);
139144
})
140145
.then(result => {
141146
addCleanQueryListeners();

src/helpers.js

+17
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,20 @@ export const seemsToBeSelectorOptions = defaultValueOrOptions => {
8080
defaultValueOrOptions.hasOwnProperty("uuid")
8181
);
8282
};
83+
84+
export const isSource = objectToCheck => {
85+
return (
86+
objectToCheck &&
87+
(objectToCheck._isSource === true || (objectToCheck.source && objectToCheck.source._isSource))
88+
);
89+
};
90+
91+
export const areSources = arrayToCheck => {
92+
let allAreSources = true;
93+
ensureArray(arrayToCheck).forEach(arrayElement => {
94+
if (!isSource(arrayElement)) {
95+
allAreSources = false;
96+
}
97+
});
98+
return allAreSources;
99+
};

test/Selector.cache.js

+50-45
Original file line numberDiff line numberDiff line change
@@ -258,60 +258,65 @@ test.describe("Selector cache", () => {
258258
});
259259
});
260260

261-
test.describe("when returns another source", () => {
262-
test.beforeEach(() => {
263-
testSelector = new Selector(
264-
testOrigin,
265-
testOrigin2,
266-
(originResult, origin2Result, query) => {
267-
spies.testSelector(query);
268-
return testOriginSelector.query(query);
269-
}
270-
);
271-
});
261+
const testDynamicSelectors = (description, selectorCallback) => {
262+
test.describe(description, () => {
263+
test.beforeEach(() => {
264+
testSelector = new Selector(testOrigin, testOrigin2, selectorCallback);
265+
});
272266

273-
test.describe("when no query is passed", () => {
274-
test.it("should clean cache when source cache is cleaned", () => {
275-
return testSelector.read().then(() => {
276-
testOriginSelector.clean();
267+
test.describe("when no query is passed", () => {
268+
test.it("should clean cache when source cache is cleaned", () => {
277269
return testSelector.read().then(() => {
278-
return test.expect(spies.testSelector.callCount).to.equal(2);
270+
testOriginSelector.clean();
271+
return testSelector.read().then(() => {
272+
return test.expect(spies.testSelector.callCount).to.equal(2);
273+
});
279274
});
280275
});
281276
});
282-
});
283277

284-
test.describe("when query is passed", () => {
285-
test.it("should clean cache when source cache is cleaned with same query", () => {
286-
const FOO_QUERY = "foo-query";
287-
return testSelector
288-
.query(FOO_QUERY)
289-
.read()
290-
.then(() => {
291-
testOriginSelector.query(FOO_QUERY).clean();
292-
return testSelector
293-
.query(FOO_QUERY)
294-
.read()
295-
.then(() => {
296-
return test.expect(spies.testSelector.callCount).to.equal(2);
297-
});
298-
});
299-
});
278+
test.describe("when query is passed", () => {
279+
test.it("should clean cache when source cache is cleaned with same query", () => {
280+
const FOO_QUERY = "foo-query";
281+
return testSelector
282+
.query(FOO_QUERY)
283+
.read()
284+
.then(() => {
285+
testOriginSelector.query(FOO_QUERY).clean();
286+
return testSelector
287+
.query(FOO_QUERY)
288+
.read()
289+
.then(() => {
290+
return test.expect(spies.testSelector.callCount).to.equal(2);
291+
});
292+
});
293+
});
300294

301-
test.it("should not clean cache when source cache is cleaned with another query", () => {
302-
const FOO_QUERY = "foo-query";
303-
return testSelector
304-
.query(FOO_QUERY)
305-
.read()
306-
.then(() => {
307-
testOriginSelector.query("foo").clean();
308-
return testSelector
309-
.query(FOO_QUERY)
310-
.read()
311-
.then(checkHasBeenCalledOnce);
312-
});
295+
test.it("should not clean cache when source cache is cleaned with another query", () => {
296+
const FOO_QUERY = "foo-query";
297+
return testSelector
298+
.query(FOO_QUERY)
299+
.read()
300+
.then(() => {
301+
testOriginSelector.query("foo").clean();
302+
return testSelector
303+
.query(FOO_QUERY)
304+
.read()
305+
.then(checkHasBeenCalledOnce);
306+
});
307+
});
313308
});
314309
});
310+
};
311+
312+
testDynamicSelectors("when returns another source", (originResult, origin2Result, query) => {
313+
spies.testSelector(query);
314+
return testOriginSelector.query(query);
315+
});
316+
317+
testDynamicSelectors("when returns multiple sources", (originResult, origin2Result, query) => {
318+
spies.testSelector(query);
319+
return [testOriginSelector.query(query), testOriginSelector];
315320
});
316321

317322
test.describe("cud methods", () => {

0 commit comments

Comments
 (0)