Skip to content

Commit 957d4b6

Browse files
authored
Merge pull request #16 from Exilz/3.0
v3 | The plugin is now called APIpeline and works on browser / node / web 🚀
2 parents 4603f41 + 5081733 commit 957d4b6

26 files changed

+608
-303
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
node_modules/
22
*.log
3-
3+
demos/web/.next
44
yarn.lock
5+
demos/web/package-lock.json

.npmignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
demo/
1+
demos/

README.md

+101-52
Large diffs are not rendered by default.
File renamed without changes.

demo/App.js renamed to demos/react-native/App.js

+7-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import React, { Component, PropTypes } from 'react';
2-
import { View, Text, TouchableOpacity, ScrollView, StyleSheet } from 'react-native';
3-
import OfflineFirstAPI from 'react-native-offline-api';
4-
5-
const NativeModules = require('NativeModules');
1+
import React, { Component } from 'react';
2+
import { View, Text, TouchableOpacity, ScrollView, StyleSheet, AsyncStorage } from 'react-native';
3+
import APIpeline from 'apipeline';
64

75
const API_OPTIONS = {
6+
fetchMethod: fetch, // use react native's fetch
87
domains: { default: 'https://icanhazdadjoke.com' },
98
prefixes: { default: '' },
109
middlewares: [setHeadersMiddleware],
@@ -25,7 +24,7 @@ const API_SERVICES = {
2524
}
2625
};
2726

28-
const api = new OfflineFirstAPI(API_OPTIONS, API_SERVICES);
27+
const api = new APIpeline(API_OPTIONS, API_SERVICES, AsyncStorage);
2928

3029
async function setHeadersMiddleware () {
3130
// This doesn't need a middleware, it's just an example
@@ -43,9 +42,6 @@ export default class Demo extends Component {
4342
}
4443

4544
async _fetchRandomJoke () {
46-
// api.get('random', { disableCache: true });
47-
// return;
48-
// api.get('random', { disableCache: true });
4945
try {
5046
this.setState({ randomJokeStatus: 1, searchJokeStatus: 0, fetchStart: Date.now() });
5147
const req = await api.get('random');
@@ -95,7 +91,7 @@ export default class Demo extends Component {
9591
get description () {
9692
return (
9793
<ScrollView style={styles.descriptionContainer}>
98-
<Text style={styles.desc}>This demo showcases a very basic usage of the plugin. It uses the default cache driver (AsyncStorage).</Text>
94+
<Text style={styles.desc}>This demo showcases a very basic usage of the plugin. It uses react native's default cache driver (AsyncStorage).</Text>
9995
<Text style={styles.desc}>Two endpoints are registered as services (random & search). Both are using the GET method.</Text>
10096
<Text style={styles.desc}>You can see how fast the cache resolution is once your request has already been fired and when its cached data is fresh...</Text>
10197
<Text style={styles.desc}>Restart this application with your phone in offline mode to see how easy it is to make your app offline-first !</Text>
@@ -158,7 +154,6 @@ export default class Demo extends Component {
158154
<Text>Looking for dad jokes...</Text>
159155
);
160156
} else if (searchJokeStatus === 2 && searchedJokes ) {
161-
console.log('SEARCHED JOKES', searchedJokes)
162157
content = searchedJokes.map((result, index) => {
163158
const { id, joke, timestamp } = result;
164159
return (
@@ -185,7 +180,7 @@ export default class Demo extends Component {
185180
render () {
186181
return (
187182
<View style={styles.container}>
188-
<Text style={styles.title}>react-native-offline-api</Text>
183+
<Text style={styles.title}>APIpeline</Text>
189184
{ this.description }
190185
{ this.btns }
191186
{ this.randomJoke }

demos/react-native/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# react-native demo
2+
3+
This demo uses [expo](https://expo.io/). Check out their documentation to try out the plugin.

demo/app.json renamed to demos/react-native/app.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"expo": {
3-
"name": "react-native-offline-api demo",
4-
"description": "Basic demonstration of react-native-offline-api features",
5-
"slug": "react-native-offline-api-demo",
3+
"name": "apipeline demo",
4+
"description": "Basic demonstration of APIpeline features",
5+
"slug": "apipeline-demo",
66
"privacy": "public",
77
"sdkVersion": "25.0.0",
88
"platforms": ["ios", "android"],
File renamed without changes.
File renamed without changes.
File renamed without changes.

demo/package.json renamed to demos/react-native/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
"expo": "^25.0.0",
66
"react": "16.2.0",
77
"react-native": "https://github.com/expo/react-native/archive/sdk-25.0.0.tar.gz",
8-
"react-native-offline-api": "^2.3.0"
8+
"apipeline": "3.0.0"
99
}
1010
}

demos/web/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Browser / node.js demo
2+
3+
This demo uses [next.js](https://nextjs.org/). Simply run `npm install` and `npm run dev` and visit `localhost:3000` to try it out.

demos/web/package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "apipeline-demo",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"dev": "next",
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"author": "",
11+
"license": "ISC",
12+
"dependencies": {
13+
"isomorphic-unfetch": "^2.0.0",
14+
"localforage": "^1.7.2",
15+
"next": "^6.1.1",
16+
"react": "^16.4.1",
17+
"react-dom": "^16.4.1",
18+
"apipeline": "3.0.0",
19+
"unfetch": "3.0.0"
20+
}
21+
}

demos/web/pages/index.js

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import React, { Component } from 'react';
2+
import Link from 'next/link';
3+
import api from '../services/index';
4+
5+
export default class Demo extends Component {
6+
7+
constructor (props) {
8+
super(props);
9+
this.state = {};
10+
this._fetchRandomJoke = this._fetchRandomJoke.bind(this);
11+
this._searchJoke = this._searchJoke.bind(this);
12+
}
13+
14+
static async getInitialProps () {
15+
const randomJokeFromServer = await api.get('random');
16+
console.info('randomJokeFromServer', randomJokeFromServer);
17+
return { randomJokeFromServer };
18+
}
19+
20+
async _fetchRandomJoke () {
21+
try {
22+
this.setState({ randomJokeStatus: 1, searchJokeStatus: 0, fetchStart: Date.now() });
23+
const req = await api.get('random');
24+
25+
if (req.status === 200) {
26+
this.setState({ randomJokeStatus: 2, randomJoke: req, fetchEnd: Date.now() });
27+
} else {
28+
this._onError();
29+
}
30+
} catch (err) {
31+
this._onError(err);
32+
}
33+
}
34+
35+
async _searchJoke (term) {
36+
try {
37+
this.setState({ searchJokeStatus: 1, randomJokeStatus: 0, fetchStart: Date.now() });
38+
const req = await api.get('search', { queryParameters: { term } });
39+
40+
if (req.status === 200 && req.results && req.results.length) {
41+
this.setState({ searchJokeStatus: 2, searchedJokes: req.results, fetchEnd: Date.now() });
42+
} else {
43+
this._onError();
44+
}
45+
} catch (err) {
46+
this._onError(err);
47+
}
48+
}
49+
50+
_clearCache () {
51+
api.clearCache();
52+
}
53+
54+
_onError (err) {
55+
this.setState({ searchJokeStatus: 3 });
56+
err && console.warn(err);
57+
}
58+
59+
btn (func, label) {
60+
return (
61+
<div onClick={func} style={{ alignSelf: 'center', justifyContent: 'center', height: '50px' }}>
62+
<span style={{ textAlign: 'center', fontSize: '16px', color: 'blue', textDecoration: 'underline' }}>{ label }</span>
63+
</div>
64+
);
65+
}
66+
67+
get description () {
68+
return (
69+
<div>
70+
<p>This demo showcases a very basic usage of the plugin on the browser. It uses <a href={'https://github.com/localForage/localForage'}>localforage</a> as its cache driver.</p>
71+
<p>Two endpoints are registered as services (random & search). Both are using the GET method.</p>
72+
<p>You can see how fast the cache resolution is once your request has already been fired and when its cached data is fresh...</p>
73+
<p>Check out the logs in the console to see how easily you've implemented a caching logic in your web application !</p>
74+
</div>
75+
);
76+
}
77+
78+
get fetchedFromServer () {
79+
return (
80+
<div>
81+
<hr />
82+
<h2>Fetch from the server</h2>
83+
<p>You can use <em>APIpeline</em> both on the browser and the server. The following data has been fetched before the page was actually displayed.</p>
84+
<p>To see how you can set up the wrapper in an isomorphic environement, check out <em>services/index.js</em> or the documentation.</p>
85+
<pre>{ JSON.stringify(this.props.randomJokeFromServer) }</pre>
86+
</div>
87+
);
88+
}
89+
90+
get btns () {
91+
return (
92+
<div style={{ }}>
93+
<hr />
94+
<h2>Fetch from the client</h2>
95+
{ this.btn(this._fetchRandomJoke, 'Fetch a random dad joke \n (cache expires after 5sec)') }
96+
{ this.btn(() => { this._searchJoke('dogs') }, "Look for a joke about dogs \n (default 5' expiration)") }
97+
{ this.btn(this._clearCache, 'Clear cache') }
98+
</div>
99+
);
100+
}
101+
102+
get randomJoke () {
103+
const { randomJokeStatus, randomJoke, fetchEnd, fetchStart } = this.state;
104+
105+
if (!randomJokeStatus) {
106+
return false;
107+
}
108+
let content;
109+
if (randomJokeStatus === 1) {
110+
content = (
111+
<span>Loading the dadliest dad joke...</span>
112+
);
113+
} else if (randomJokeStatus === 2 && randomJoke ) {
114+
const { id, joke, timestamp } = randomJoke;
115+
content = (
116+
<div style={{ }}>
117+
<p>Joke id : { id}</p>
118+
<p>Joke fetched in { fetchEnd - fetchStart } ms</p>
119+
<p>Joke parsed at date : { timestamp } (added to response through responseMiddleware)</p>
120+
<p>{ joke }</p>
121+
<p>Raw data :</p>
122+
<pre>{ JSON.stringify(randomJoke) }</pre>
123+
</div>
124+
);
125+
} else if (randomJokeStatus === 3) {
126+
content = (
127+
<span>Error while fetching a dad joke :(</span>
128+
);
129+
}
130+
return content;
131+
}
132+
133+
get searchedJokes () {
134+
const { searchJokeStatus, searchedJokes, fetchEnd, fetchStart } = this.state;
135+
136+
if (!searchJokeStatus) {
137+
return false;
138+
}
139+
let content;
140+
if (searchJokeStatus === 1) {
141+
content = (
142+
<span>Looking for dad jokes...</span>
143+
);
144+
} else if (searchJokeStatus === 2 && searchedJokes ) {
145+
content = searchedJokes.map((result, index) => {
146+
const { id, joke, timestamp } = result;
147+
return (
148+
<div style={{ }} key={`key-${index}`}>
149+
<p>Joke id : { id}</p>
150+
<p>Joke fetched in { fetchEnd - fetchStart } ms</p>
151+
<p>Joke parsed at date : { timestamp } (added to response through responseMiddleware)</p>
152+
<p>{ joke }</p>
153+
</div>
154+
);
155+
});
156+
} else if (searchJokeStatus === 3) {
157+
content = (
158+
<span>Error while looking for dad jokes :(</span>
159+
);
160+
}
161+
return (
162+
<div style={{ }}>
163+
{ content }
164+
<p>Raw data :</p>
165+
<pre>{ JSON.stringify(searchedJokes) }</pre>
166+
</div>
167+
);
168+
}
169+
170+
render () {
171+
return (
172+
<div>
173+
<h1>APIpeline</h1>
174+
{ this.description }
175+
{ this.fetchedFromServer }
176+
{ this.btns }
177+
{ this.randomJoke }
178+
{ this.searchedJokes }
179+
</div>
180+
);
181+
}
182+
}

demos/web/services/index.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import APIpeline from 'apipeline';
2+
import clientFetch from 'unfetch';
3+
import serverFetch from 'isomorphic-unfetch';
4+
import localforage from 'localforage';
5+
6+
const isServer = typeof window === 'undefined';
7+
8+
const API_OPTIONS = {
9+
fetchMethod: isServer ? serverFetch : clientFetch,
10+
domains: { default: 'https://icanhazdadjoke.com' },
11+
prefixes: { default: '' },
12+
printNetworkRequests: !isServer,
13+
debugAPI: !isServer,
14+
disableCache: false,
15+
middlewares: [() => ({
16+
headers: {
17+
'User-Agent': 'apipeline (https://github.com/Exilz/apipeline)',
18+
'Accept': 'application/json'
19+
}
20+
})]
21+
};
22+
23+
const API_SERVICES = {
24+
random: { path: '', expiration: 5 * 1000, responseMiddleware: (res) => ({ ...res, timestamp: Date.now() }) },
25+
search: {
26+
path: 'search',
27+
responseMiddleware: (res) => (
28+
{
29+
...res,
30+
results: res.results.map((result) => ({ ...result, timestamp: Date.now()} ))
31+
}
32+
)
33+
}
34+
};
35+
36+
const api = isServer ?
37+
new APIpeline(API_OPTIONS, API_SERVICES) :
38+
new APIpeline(API_OPTIONS, API_SERVICES, localforage);
39+
40+
export default api;

dist/drivers/sqlite.js

+3-25
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ exports.default = function (SQLite, options) { return __awaiter(_this, void 0, v
5454
_a.label = 1;
5555
case 1:
5656
_a.trys.push([1, 3, , 4]);
57-
return [4 /*yield*/, SQLite.openDatabase(__assign({ name: 'offlineapi.db', location: 'default' }, (options.openDatabaseOptions || {})))];
57+
return [4 /*yield*/, SQLite.openDatabase(__assign({ name: 'apipeline.db', location: 'default' }, (options.openDatabaseOptions || {})))];
5858
case 2:
5959
db = _a.sent();
6060
db.transaction(function (tx) {
@@ -63,12 +63,11 @@ exports.default = function (SQLite, options) { return __awaiter(_this, void 0, v
6363
return [2 /*return*/, {
6464
getItem: getItem(db),
6565
setItem: setItem(db),
66-
removeItem: removeItem(db),
67-
multiRemove: multiRemove(db)
66+
removeItem: removeItem(db)
6867
}];
6968
case 3:
7069
err_1 = _a.sent();
71-
throw new Error("react-native-offline-api : Cannot open SQLite database : " + err_1 + ". Check your SQLite configuration.");
70+
throw new Error("APIPeline : Cannot open SQLite database : " + err_1 + ". Check your SQLite configuration.");
7271
case 4: return [2 /*return*/];
7372
}
7473
});
@@ -120,24 +119,3 @@ function removeItem(db) {
120119
});
121120
};
122121
}
123-
function multiRemove(db) {
124-
return function (keys) {
125-
return new Promise(function (resolve, reject) {
126-
// This implmementation is not the most efficient, must delete using
127-
// WHERE id IN (...,...) doesn't seem to be working at the moment.
128-
db.transaction(function (tx) {
129-
var promises = [];
130-
keys.forEach(function (key) {
131-
promises.push(tx.executeSql('DELETE FROM cache WHERE id=?', [key]));
132-
});
133-
Promise.all(promises)
134-
.then(function () {
135-
return resolve();
136-
})
137-
.catch(function (err) {
138-
return reject(err);
139-
});
140-
});
141-
});
142-
};
143-
}

0 commit comments

Comments
 (0)