-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from Exilz/2.2.0
v2.2.0
- Loading branch information
Showing
13 changed files
with
405 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
"use strict"; | ||
var __assign = (this && this.__assign) || Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [0, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var _this = this; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = function (SQLite, options) { return __awaiter(_this, void 0, void 0, function () { | ||
var db, err_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
SQLite.DEBUG(options.debug || false); | ||
SQLite.enablePromise(true); | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, SQLite.openDatabase(__assign({ name: 'offlineapi.db', location: 'default' }, (options.openDatabaseOptions || {})))]; | ||
case 2: | ||
db = _a.sent(); | ||
db.transaction(function (tx) { | ||
tx.executeSql('CREATE TABLE IF NOT EXISTS cache (id TEXT PRIMARY KEY NOT NULL, value TEXT);'); | ||
}); | ||
return [2 /*return*/, { | ||
getItem: getItem(db), | ||
setItem: setItem(db), | ||
removeItem: removeItem(db), | ||
multiRemove: multiRemove(db) | ||
}]; | ||
case 3: | ||
err_1 = _a.sent(); | ||
throw new Error("react-native-offline-api : Cannot open SQLite database : " + err_1 + ". Check your SQLite configuration."); | ||
case 4: return [2 /*return*/]; | ||
} | ||
}); | ||
}); }; | ||
function getItem(db) { | ||
return function (key) { | ||
return new Promise(function (resolve, reject) { | ||
db.transaction(function (tx) { | ||
tx.executeSql('SELECT * FROM cache WHERE id=?', [key]) | ||
.then(function (res) { | ||
var results = res[1]; | ||
var item = results.rows.item(0); | ||
return resolve(item && item.value || null); | ||
}) | ||
.catch(function (err) { | ||
return reject(err); | ||
}); | ||
}); | ||
}); | ||
}; | ||
} | ||
function setItem(db) { | ||
return function (key, value) { | ||
return new Promise(function (resolve, reject) { | ||
db.transaction(function (tx) { | ||
tx.executeSql('INSERT OR REPLACE INTO cache VALUES (?,?)', [key, value]) | ||
.then(function () { | ||
return resolve(); | ||
}). | ||
catch(function (err) { | ||
return reject(err); | ||
}); | ||
}); | ||
}); | ||
}; | ||
} | ||
function removeItem(db) { | ||
return function (key) { | ||
return new Promise(function (resolve, reject) { | ||
db.transaction(function (tx) { | ||
tx.executeSql('DELETE FROM cache WHERE id=?', [key]) | ||
.then(function () { | ||
return resolve(); | ||
}) | ||
.catch(function (err) { | ||
return reject(err); | ||
}); | ||
}); | ||
}); | ||
}; | ||
} | ||
function multiRemove(db) { | ||
return function (keys) { | ||
return new Promise(function (resolve, reject) { | ||
// This implmementation is not the most efficient, must delete using | ||
// WHERE id IN (...,...) doesn't seem to be working at the moment. | ||
db.transaction(function (tx) { | ||
var promises = []; | ||
keys.forEach(function (key) { | ||
promises.push(tx.executeSql('DELETE FROM cache WHERE id=?', [key])); | ||
}); | ||
Promise.all(promises) | ||
.then(function () { | ||
return resolve(); | ||
}) | ||
.catch(function (err) { | ||
return reject(err); | ||
}); | ||
}); | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Additional documentation | ||
|
||
* [Limiting the size of your cache](cache-size.md) | ||
* [Using your own driver or SQLite for caching](custom-drivers.md) | ||
* [Middlewares documentation](middlewares.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Limiting the size of your cache | ||
|
||
If you fear your cache will keep growing, you have some options to make sure it doesn't get too big. | ||
|
||
First, you can use the `clearCache` method to empty all stored data, or just a service's items. You might want to implement a button in your interface to give your users the ability to clear it whenever they want if they feel like their app is starting to take too much space. | ||
|
||
The other solution would be to use the capping option. If you set `capServices` to true in your [API options](#api-options), or `capService` in your [service options](#services-options), the wrapper will make sure it never stores more items that the amount you configured in `capLimit`. This is a good way to restrict the size of stored data for sensitive services, while leaving some of them uncapped. Capping is disabled by default. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Using your own driver for caching | ||
|
||
This wrapper has been written with the goal of **being storage-agnostic**. This means that by default, it will make use of react-native's `AsyncStorage` API, but feel free to write your own driver and use anything you want, like the amazing [realm](https://github.com/realm/realm-js). | ||
|
||
Your custom driver must implement these 3 methods that are promises. | ||
|
||
* `getItem(key: string): Promise<any>;` | ||
* `setItem(key: string, value: string): Promise<void>;` | ||
* `removeItem(key: string): Promise<void>;` | ||
* `multiRemove(keys: string[]): Promise<void>;` | ||
|
||
## SQLite Driver | ||
|
||
As of `2.2.0`, an SQLite driver is baked-in with the module. Install SQLite in your project by [following these instructions](https://github.com/andpor/react-native-sqlite-storage) and set it as your custom driver like this : | ||
|
||
```javascript | ||
import OfflineFirstAPI, { drivers } from 'react-native-offline-api'; | ||
import SQLite from 'react-native-sqlite-storage'; | ||
|
||
// ... | ||
|
||
const api = new OfflineFirstAPI(API_OPTIONS, API_SERVICES); | ||
|
||
drivers.sqliteDriver(SQLite, { debug: false }).then((driver) => { | ||
api.setCacheDriver(driver); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Middlewares | ||
|
||
Just like for the other request options, **you can provide middlewares at the global level in your API options, at the service's definition level, or in the `options` parameter of the `fetch` method.** | ||
|
||
You must provide an **array of promises**, like so : `(serviceDefinition: IAPIService, paths: IMiddlewarePaths, options: IFetchOptions) => any;`, please [take a look at the types](#types) to know more. You don't necessarily need to write asynchronous code in them, but they all must be promises. | ||
|
||
Anything you will resolve in those promises will be merged into your request's options ! | ||
|
||
Here's a barebone example : | ||
|
||
```javascript | ||
const API_OPTIONS = { | ||
// ... all your api options | ||
middlewares: [exampleMiddleware], | ||
}; | ||
|
||
async function exampleMiddleware (serviceDefinition, serviceOptions) { | ||
// This will be printed everytime you call a service | ||
console.log('You just fired a request for the path ' + serviceDefinition.path); | ||
} | ||
``` | ||
|
||
You can even make API calls in your middlewares. For instance, you might want to make sure the user is logged in into your API, or you might want to refresh its authentication token once in a while. Like so : | ||
|
||
```javascript | ||
const API_OPTIONS = { | ||
// ... all your api options | ||
middlewares: [authMiddleware] | ||
} | ||
|
||
async function authMiddleware (serviceDefinition, serviceOptions) { | ||
if (authToken && !tokenExpired) { | ||
// Our token is up-to-date, add it to the headers of our request | ||
return { headers: { 'X-Auth-Token': authToken } }; | ||
} | ||
// Token is missing or outdated, let's fetch a new one | ||
try { | ||
// Assuming our login service's method is already set to 'POST' | ||
const authData = await api.fetch( | ||
'login', | ||
// the 'fetcthOptions' key allows us to use any of react-native's fetch method options | ||
// here, the body of our post request | ||
{ fetchOptions: { body: 'username=user&password=password' } } | ||
); | ||
// Store our new authentication token and add it to the headers of our request | ||
authToken = authData.authToken; | ||
tokenExpired = false; | ||
return { headers: { 'X-Auth-Token': authData.authToken } }; | ||
} catch (err) { | ||
throw new Error(`Couldn't auth to API, ${err}`); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.