-
-
Notifications
You must be signed in to change notification settings - Fork 47
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 #234 from OP-Engineering/oscar/key-value-storage
Oscar/key value storage
- Loading branch information
Showing
7 changed files
with
233 additions
and
48 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
--- | ||
sidebar_position: 8 | ||
sidebar_position: 9 | ||
--- | ||
|
||
# API Reference | ||
|
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,31 @@ | ||
--- | ||
sidebar_position: 8 | ||
--- | ||
|
||
# Key-Value Storage | ||
|
||
OP-SQLite provides a simple key-value storage API compatible with react-native-async-storage. It should be much faster than async-storage (whilst slower than MMKV) but comes with the convenience of not having to add one more dependency to your app. For convenience it also has sync versions of the methods. If you use SQLCipher the data inside will also be encrypted. | ||
|
||
```ts | ||
import { Storage } from '@op-engineering/op-sqlite'; | ||
|
||
// Storage is backed by it's own database | ||
// You can set the location like any other op-sqlite database | ||
const storage = new Storage({ | ||
location: 'storage', // Optional, see location param on normal databases | ||
encryptionKey: 'myEncryptionKey', // Optional, only used when used against SQLCipher | ||
}); | ||
|
||
const item = storage.getItemSync('foo'); | ||
|
||
const item2 = await storage.getItem('foo'); | ||
|
||
await storage.setItem('foo', 'bar'); | ||
|
||
storage.setItemSync('foo', 'bar'); | ||
|
||
const allKeys = storage.getAllKeys(); | ||
|
||
// Clears the internal table | ||
storage.clear(); | ||
``` |
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,66 @@ | ||
import {Storage} from '@op-engineering/op-sqlite'; | ||
import chai from 'chai'; | ||
import {afterEach, beforeEach, describe, it} from './MochaRNAdapter'; | ||
|
||
const expect = chai.expect; | ||
|
||
export function storageTests() { | ||
let storage: Storage; | ||
|
||
describe('Queries tests', () => { | ||
beforeEach(async () => { | ||
storage = new Storage({encryptionKey: 'test'}); | ||
}); | ||
|
||
afterEach(() => {}); | ||
|
||
it('Can set and get sync', async () => { | ||
storage.setItemSync('foo', 'bar'); | ||
const res = storage.getItemSync('foo'); | ||
expect(res).to.equal('bar'); | ||
}); | ||
|
||
it('Can set and get async', async () => { | ||
await storage.setItem('quack', 'bark'); | ||
const res = await storage.getItem('quack'); | ||
expect(res).to.equal('bark'); | ||
}); | ||
|
||
it('can remove item sync', async () => { | ||
storage.setItemSync('foo', 'bar'); | ||
storage.removeItemSync('foo'); | ||
const res = storage.getItemSync('foo'); | ||
expect(res).to.equal(undefined); | ||
}); | ||
|
||
it('can remove item async', async () => { | ||
await storage.setItem('quack', 'bark'); | ||
await storage.removeItem('quack'); | ||
const res = await storage.getItem('quack'); | ||
expect(res).to.equal(undefined); | ||
}); | ||
|
||
it('can clear', async () => { | ||
await storage.setItem('quack', 'bark'); | ||
await storage.setItem('quack2', 'bark'); | ||
await storage.clear(); | ||
const res = await storage.getItem('quack'); | ||
expect(res).to.equal(undefined); | ||
}); | ||
|
||
it('can clear sync', async () => { | ||
storage.setItemSync('quack', 'bark'); | ||
storage.setItemSync('quack2', 'bark'); | ||
storage.clearSync(); | ||
const res = storage.getItemSync('quack'); | ||
expect(res).to.equal(undefined); | ||
}); | ||
|
||
it('can get all keys', async () => { | ||
await storage.setItem('quack', 'bark'); | ||
await storage.setItem('quack2', 'bark'); | ||
const keys = storage.getAllKeys(); | ||
expect(keys).to.deep.equal(['quack', 'quack2']); | ||
}); | ||
}); | ||
} |
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,85 @@ | ||
import { open, type DB } from '.'; | ||
|
||
type StorageOptions = { | ||
location?: string; | ||
encryptionKey?: string; | ||
}; | ||
|
||
/** | ||
* Creates a new async-storage api compatible instance. | ||
* The encryption key is only used when compiled against the SQLCipher version of op-sqlite. | ||
*/ | ||
export class Storage { | ||
private db: DB; | ||
|
||
constructor(options: StorageOptions) { | ||
this.db = open({ ...options, name: '__opsqlite_storage' }); | ||
this.db.executeSync('PRAGMA mmap_size=268435456'); | ||
this.db.executeSync( | ||
'CREATE TABLE IF NOT EXISTS storage (key TEXT PRIMARY KEY, value TEXT) WITHOUT ROWID' | ||
); | ||
} | ||
|
||
async getItem(key: string): Promise<string | undefined> { | ||
const result = await this.db.execute( | ||
'SELECT value FROM storage WHERE key = ?', | ||
[key] | ||
); | ||
|
||
let value = result.rows[0]?.value; | ||
if (typeof value !== 'undefined' && typeof value !== 'string') { | ||
throw new Error('Value must be a string or undefined'); | ||
} | ||
return value; | ||
} | ||
|
||
getItemSync(key: string): string | undefined { | ||
const result = this.db.executeSync( | ||
'SELECT value FROM storage WHERE key = ?', | ||
[key] | ||
); | ||
|
||
let value = result.rows[0]?.value; | ||
if (typeof value !== 'undefined' && typeof value !== 'string') { | ||
throw new Error('Value must be a string or undefined'); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
async setItem(key: string, value: string) { | ||
await this.db.execute( | ||
'INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)', | ||
[key, value.toString()] | ||
); | ||
} | ||
|
||
setItemSync(key: string, value: string) { | ||
this.db.executeSync( | ||
'INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)', | ||
[key, value.toString()] | ||
); | ||
} | ||
|
||
async removeItem(key: string) { | ||
await this.db.execute('DELETE FROM storage WHERE key = ?', [key]); | ||
} | ||
|
||
removeItemSync(key: string) { | ||
this.db.executeSync('DELETE FROM storage WHERE key = ?', [key]); | ||
} | ||
|
||
async clear() { | ||
await this.db.execute('DELETE FROM storage'); | ||
} | ||
|
||
clearSync() { | ||
this.db.executeSync('DELETE FROM storage'); | ||
} | ||
|
||
getAllKeys() { | ||
return this.db | ||
.executeSync('SELECT key FROM storage') | ||
.rows.map((row: any) => row.key); | ||
} | ||
} |
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