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

chore(deps): update all deps and fix database schema #37

Merged
merged 1 commit into from
Jun 25, 2019
Merged
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
{
"presets": ["es2015", "stage-2"]
}
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["edge >= 15", "safari >= 9", "last 2 versions"]
}
}
]
]
}
4 changes: 2 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "unity"
}
"extends": "unity"
}
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
language: node_js
node_js:
- "6"
- "5"
- "4"
- "11"
cache:
directories:
- node_modules
61 changes: 28 additions & 33 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "unity-cache",
"version": "2.1.0",
"description": "Cache abstraction around localforage.",
"version": "2.2.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You updated node to major version. I think it should been updated to major.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node is used only in testing environment. I'm not sure about the major version.

"description": "Cache abstraction around Dexie.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"main": "lib/index.js",
"scripts": {
"build": "npm run test && npm run clean && ./node_modules/.bin/babel src --out-dir lib",
@@ -10,7 +10,7 @@
"commit": "./node_modules/.bin/git-cz",
"coverage:report": "./node_modules/.bin/nyc report",
"coverage:send": "./node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls",
"lint": "./node_modules/.bin/eslint --ignore-path=.gitignore --fix ./src",
"lint": "./node_modules/.bin/eslint --ignore-path=.gitignore --fix ./src ./test",
"lint-prod": "NODE_ENV='production' npm run lint",
"version": " ./node_modules/.bin/conventional-changelog -i CHANGELOG.md -s && git add CHANGELOG.md",
"prepublish": "npm run build",
@@ -36,40 +36,37 @@
},
"homepage": "https://github.com/auru/unity-cache#readme",
"keywords": [
"unity",
"cache",
"indexeddb",
"localforage",
"localstorage",
"dexie",
"storage",
"unity",
"websql"
"indexeddb"
],
"engines": {
"node": ">=4",
"npm": ">=3"
"node": ">=11",
"npm": ">=6"
},
"dependencies": {
"dexie": "^2.0.1"
"dexie": "^2.0.4"
},
"devDependencies": {
"ava": "^0.18.1",
"babel-cli": "^6.16.0",
"babel-core": "^6.17.0",
"babel-polyfill": "^6.16.0",
"babel-preset-es2015": "^6.16.0",
"babel-preset-stage-2": "^6.16.0",
"babel-register": "^6.16.3",
"browser-env": "^2.0.19",
"commitizen": "^2.8.6",
"conventional-changelog-cli": "^1.2.0",
"coveralls": "^2.11.14",
"cz-conventional-changelog": "^1.2.0",
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.5",
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@babel/register": "^7.4.4",
"ava": "^2.1.0",
"browser-env": "^3.2.6",
"commitizen": "^3.1.1",
"conventional-changelog-cli": "^2.0.21",
"coveralls": "^3.0.4",
"cz-conventional-changelog": "^2.1.0",
"eslint-config-unity": "^1.0.1",
"fake-indexeddb": "^1.0.8",
"husky": "^0.11.9",
"nyc": "^10.0.0",
"fake-indexeddb": "^2.1.1",
"husky": "^2.5.0",
"nyc": "^14.1.1",
"rimraf": "^2.5.4",
"sinon": "^2.0.0",
"sinon": "^7.3.2",
"validate-commit-msg": "^2.8.2"
},
"ava": {
@@ -79,15 +76,13 @@
"source": [
"src/**/*.js"
],
"concurrency": 4,
"failFast": false,
"tap": false,
"require": [
"./test/setup.js",
"babel-register",
"babel-polyfill"
"@babel/register",
"@babel/polyfill"
],
"babel": "inherit"
"failFast": false,
"concurrency": 4
},
"nyc": {
"include": [
79 changes: 45 additions & 34 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -2,25 +2,23 @@ import Dexie from 'dexie';
import UnityCacheError from './error';

const RE_BIN = /^\w+$/;
const EXPIRE_BIN = '___expire___';
const EXPIRE_GLUE = '::';
const DEFAULT_NAME = 'unity';
const DEFAULT_VERSION = 1;

const cacheInstance = {
config: {},
db: null
db: null,
config: {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's like a tree 🌲

};

function setCacheConfig(name, stores, version) {
stores = [].concat(stores, EXPIRE_BIN);

function initCacheConfig(stores = [], name = DEFAULT_NAME, version = DEFAULT_VERSION) {
stores = [].concat(stores);
stores = stores.reduce((result, storeName) => {
if (!RE_BIN.test(storeName)) {
throw new UnityCacheError(`Store names can only be alphanumeric, '${storeName}' given`);
}

result[storeName] = '&';
result[storeName] = '&key, value, expire';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove EXPIRE_BIN


return result;
}, {});

@@ -34,16 +32,18 @@ function setCacheConfig(name, stores, version) {
function initCacheStores() {
const { name, stores, version } = cacheInstance.config;

if (cacheInstance.db) {
closeDB();
}

cacheInstance.db = new Dexie(name);

if (!cacheInstance.db) {
/* istanbul ignore next: error new Dexie */
throw new UnityCacheError('Database is undefined or null');
}

cacheInstance.db
.version(version)
.stores(stores);
cacheInstance.db.version(version).stores(stores);
}

function errorHandlerWrapper(method) {
@@ -54,6 +54,8 @@ function errorHandlerWrapper(method) {
switch (e.name) {
case Dexie.errnames.Upgrade:
case Dexie.errnames.Version:
case Dexie.errnames.InvalidState:
case Dexie.errnames.QuotaExceeded:
await upgradeDB();
return null;

@@ -70,8 +72,20 @@ function errorHandlerWrapper(method) {
};
}

function closeDB() {
if (!cacheInstance.db) {
/* istanbul ignore next: db is not defined */
throw new UnityCacheError('Database is undefined or null');
}

if (cacheInstance.db.isOpen()) {
cacheInstance.db.close();
}
}

async function openDB() {
if (!cacheInstance.db) {
/* istanbul ignore next: db is not defined */
throw new UnityCacheError('Database is undefined or null');
}

@@ -88,6 +102,11 @@ async function openDB() {
}

async function upgradeDB() {
if (!cacheInstance.db) {
/* istanbul ignore next: db is not defined */
throw new UnityCacheError('Database is undefined or null');
}

return await deleteDB()
.then(() => {
initCacheStores();
@@ -100,10 +119,11 @@ async function upgradeDB() {

async function deleteDB() {
if (!cacheInstance.db) {
/* istanbul ignore next: db is not defined */
throw new UnityCacheError('Database is undefined or null');
}

cacheInstance.db.close();
closeDB();

return await cacheInstance.db
.delete()
@@ -113,30 +133,23 @@ async function deleteDB() {
});
}

function getExpireKey(store, key) {
return store + EXPIRE_GLUE + key;
}

async function get(store, key, validate = true) {
const { db } = cacheInstance;

const expired = await db[EXPIRE_BIN].get(getExpireKey(store, key));
const isValid = validate && db[store] ? expired > Date.now() : true;
const { value = null, expire = 0 } = await db[store].get(key) || {};
const isValid = validate ? expire > Date.now() : true;

if (!isValid) {
await db[EXPIRE_BIN].delete(getExpireKey(store, key));
await db[store].delete(key);
}

return isValid ? await db[store].get(key) : null;
return isValid ? value : null;
}

async function set(store, key, value, expire = Number.MAX_SAFE_INTEGER) {
async function set(store, key, value, ttl = Number.MAX_SAFE_INTEGER) {
const { db } = cacheInstance;
const expire = Date.now() + Number(ttl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number('fdfff') // NaN

I think it would be better to check its type before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to rewrite it to typescript. The check doesn't help at all.


return await Promise.all([
db[EXPIRE_BIN].put(Date.now() + Number(expire), getExpireKey(store, key)),
db[store].put(value, key)
]);
return await db[store].put({ key, value, expire });
}

async function remove(store, key) {
@@ -146,28 +159,26 @@ async function remove(store, key) {

const { db } = cacheInstance;

return await Promise.all([
db[EXPIRE_BIN].delete(getExpireKey(store, key)),
db[store].delete(key)
]);
return await db[store].delete(key);
}

async function drop(stores) {
const { db } = cacheInstance;

stores = [].concat(stores);

return await Promise.all(stores.map(store => db[store].clear()));
}

function createCache(stores, name = DEFAULT_NAME, version = DEFAULT_VERSION) {
setCacheConfig(name, stores, version);
function createCache(stores = [], name = DEFAULT_NAME, version = DEFAULT_VERSION) {
initCacheConfig(stores, name, version);
initCacheStores();

return {
get: errorHandlerWrapper(get),
set: errorHandlerWrapper(set),
remove: errorHandlerWrapper(remove),
drop: errorHandlerWrapper(drop)
drop: errorHandlerWrapper(drop),
remove: errorHandlerWrapper(remove)
};
}

66 changes: 47 additions & 19 deletions test/cache.spec.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
/* eslint-disable ava/no-only-test, ava/use-t-well */

import test from 'ava';
import createCache from '../src/'
import createCache from '../src';
import UnityCacheError from '../src/error';

test.beforeEach(t => {
t.context.cache = createCache(['store', 'store2', 'drop_store'], 'cache', 1);
t.context.cache = createCache([ 'store', 'store2', 'drop_store' ], 'cache', 1);
});

test('set/get val with default expiration period', async t => {
await t.context.cache.set('store', 'key-not-expired', 'val');

const cachedVal = await t.context.cache.get('store', 'key-not-expired');
t.is(cachedVal, 'val');
});

test('set/get val with set expiration period', async t => {
await t.context.cache.set('store', 'key2', 'val2', 1000);

const cachedVal = await t.context.cache.get('store', 'key2');
t.is(cachedVal, 'val2');
});

test('set/get expired val', async t => {
await t.context.cache.set('store', 'key-expired', 'val', 0);
const cachedVal = await t.context.cache.get('store', 'key-expired');

const cachedVal = await t.context.cache.get('store', 'key-expired');
t.is(cachedVal, null);
});

test('set/get expired val without validation', async t => {
await t.context.cache.set('store', 'key-expired-2', 'val', 0);

const cachedVal = await t.context.cache.get('store', 'key-expired-2', false);
t.is(cachedVal, 'val');
});
@@ -37,11 +42,11 @@ test('get non-existent val', async t => {
});

test('set val on non-existent store', async t => {
t.throws(t.context.cache.set('store-not-exist', 'key', 'val'), Error);
await t.throwsAsync(async () => await t.context.cache.set('store-not-exist', 'key', 'val'), Error);
});

test('get val on non-existent store', async t => {
t.throws(t.context.cache.get('store-not-exist', 'key', 'val'), Error);
await t.throwsAsync(async () => await t.context.cache.get('store-not-exist', 'key', 'val'), Error);
});

test('remove val', async t => {
@@ -50,44 +55,67 @@ test('remove val', async t => {
});

test('remove non-existent key', async t => {
t.notThrows(async () => await t.context.cache.remove('store', 'key-not-exist'));
await t.notThrowsAsync(async () => await t.context.cache.remove('store', 'key-not-exist'));
});

test('remove val on non-existent store', async t => {
t.throws(t.context.cache.remove('store-not-exist', 'key'), Error);
await t.throwsAsync(async () => await t.context.cache.remove('store-not-exist', 'key'), Error);
});

test('illegal store name', async t => {
t.throws(() => createCache(['with spaces']), UnityCacheError);
await t.throwsAsync(async () => await createCache([ 'with spaces' ]), UnityCacheError);
});

test('does not throw on cache params', async t => {
t.notThrows(() => createCache(['store'], 'test', 'test database', 'localStorageWrapper'));
await t.notThrowsAsync(async () => await createCache([ 'store' ], 'test', 'test database', 'localStorageWrapper'));
});

test('drop store', async t => {
t.notThrows(async () => await t.context.cache.drop('drop_store'));
await t.notThrowsAsync(async () => await t.context.cache.drop('drop_store'));
});

test('remove database', async t => {
const cache = createCache(['store'], 'test-1', 1);
t.notThrows(async () => await cache.remove());
const cache = createCache([ 'store' ], 'test-1', 1);
await t.notThrowsAsync(async () => await cache.remove());
});

test('upgrade handle', async t => {
const cache = createCache(['store'], 'test-2', 1);
const cache = createCache([ 'store' ], 'test-2', 1);
await cache.set('store', 'key', 'val');

const newCache = createCache(['store', 'other'], 'test-2', 2);
t.notThrows(async () => await newCache.set('store', 'key', 'val'));
const newCache = createCache([ 'store', 'other' ], 'test-2', 1);
await t.notThrowsAsync(async () => await newCache.set('store', 'key', 'val'));

const newCachedVal = await newCache.get('store', 'key');
t.is(newCachedVal, 'val');
});

test('upgrade handle when new version', async t => {
const cache = createCache(['store'], 'test-3', 2);
test('upgrade handle when new higher version', async t => {
const cache = createCache([ 'store' ], 'test-3', 1);
await cache.set('store', 'key', 'val');
await cache.remove();

const newCache = createCache(['store'], 'test-3', 1);
t.notThrows(async () => await newCache.set('store', 'key', 'val'));
const newCache = createCache([ 'store', 'other' ], 'test-3', 2);
await t.notThrowsAsync(async () => await newCache.set('store', 'key', 'val'));

const newCacheValue = await cache.get('store', 'key');
t.is(newCacheValue, 'val');
});

test('upgrade handle when new lower version', async t => {
let cache;
let cacheValue;

cache = createCache([ 'store' ], 'test-4', 2);
await cache.set('store', 'key', 'val');

cache = createCache([ 'store', 'other' ], 'test-4', 1);
await t.notThrowsAsync(async () => await cache.set('store', 'key', 'val'));

cacheValue = await cache.get('store', 'key');
t.is(cacheValue, null);

await t.notThrowsAsync(async () => await cache.set('store', 'key', 'val'));
cacheValue = await cache.get('store', 'key');
t.is(cacheValue, 'val');
});
10 changes: 5 additions & 5 deletions test/error.spec.js
Original file line number Diff line number Diff line change
@@ -6,17 +6,17 @@ test.beforeEach(t => {
});

test('instance of Error', t => {
t.true(t.context.error instanceof Error)
t.true(t.context.error instanceof Error);
});

test('instance of UnityCacheError', t => {
t.true(t.context.error instanceof UnityCacheError)
t.true(t.context.error instanceof UnityCacheError);
});

test('to string', t => {
t.is(t.context.error.toString(), 'UnityCacheError: Something something')
t.is(t.context.error.toString(), 'UnityCacheError: Something something');
});

test('empty message', t => {
t.is((new UnityCacheError()).toString(), 'UnityCacheError')
});
t.is((new UnityCacheError()).toString(), 'UnityCacheError');
});
6 changes: 4 additions & 2 deletions test/setup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
require('browser-env')(['window', 'document', 'navigator']);
require('@babel/polyfill');
require('browser-env')([ 'window', 'document', 'navigator' ]);

const indexedDB = require('fake-indexeddb');
const FDBKeyRange = require('fake-indexeddb/lib/FDBKeyRange');

window.Promise = global.Promise;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cause Dexie uses Promise from window.

window.indexedDB = indexedDB;
window.IDBKeyRange = FDBKeyRange;
window.IDBKeyRange = FDBKeyRange;