Skip to content

Commit 9c609a0

Browse files
authored
Merge pull request #234 from OP-Engineering/oscar/key-value-storage
Oscar/key value storage
2 parents 1cdda73 + 83acbd1 commit 9c609a0

File tree

7 files changed

+233
-48
lines changed

7 files changed

+233
-48
lines changed

docs/docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 8
2+
sidebar_position: 9
33
---
44

55
# API Reference

docs/docs/key_value_storage.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
sidebar_position: 8
3+
---
4+
5+
# Key-Value Storage
6+
7+
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.
8+
9+
```ts
10+
import { Storage } from '@op-engineering/op-sqlite';
11+
12+
// Storage is backed by it's own database
13+
// You can set the location like any other op-sqlite database
14+
const storage = new Storage({
15+
location: 'storage', // Optional, see location param on normal databases
16+
encryptionKey: 'myEncryptionKey', // Optional, only used when used against SQLCipher
17+
});
18+
19+
const item = storage.getItemSync('foo');
20+
21+
const item2 = await storage.getItem('foo');
22+
23+
await storage.setItem('foo', 'bar');
24+
25+
storage.setItemSync('foo', 'bar');
26+
27+
const allKeys = storage.getAllKeys();
28+
29+
// Clears the internal table
30+
storage.clear();
31+
```

example/ios/Podfile.lock

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,69 +1785,69 @@ SPEC CHECKSUMS:
17851785
GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
17861786
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
17871787
hermes-engine: 06a9c6900587420b90accc394199527c64259db4
1788-
op-sqlite: ed6d12bc6ff23accfea5ba6b7db717d3958852ce
1788+
op-sqlite: 4f4f7845e0208b1a6dfa6ac3db7dcab039c109b7
17891789
RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648
17901790
RCTDeprecation: fb7d408617e25d7f537940000d766d60149c5fea
17911791
RCTRequired: 9aaf0ffcc1f41f0c671af863970ef25c422a9920
17921792
RCTTypeSafety: e9a6e7d48184646eb0610295b74c0dd02768cbb2
17931793
React: fffb3cf1b0d7aee03c4eb4952b2d58783615e9fa
17941794
React-callinvoker: 3c6ecc0315d42924e01b3ddc25cf2e49d33da169
1795-
React-Core: d2143ba58d0c8563cf397f96f699c6069eba951c
1796-
React-CoreModules: b3cbc5e3090a8c23116c0c7dd8998e0637e29619
1797-
React-cxxreact: 68fb9193582c4a411ce99d0b23f7b3d8da1c2e4a
1795+
React-Core: 1a5ddefb00dd72644171dd39bb4bbcd7849c70f0
1796+
React-CoreModules: 8de64f712fe272ed08f37aaf64633ddf793e70d3
1797+
React-cxxreact: e204185e1da1c843fec2bbb10bcc5b5800355dfa
17981798
React-debug: 297ed67868a76e8384669ea9b5c65c5d9d9d15d9
1799-
React-defaultsnativemodule: 9726dafb3b20bb49f9eac5993418aaa7ddb6a80d
1800-
React-domnativemodule: ff049da74cb1be08b7cd71cdbc7bb5b335e04d8e
1801-
React-Fabric: 2e33816098a5a29d2f4ae7eb2de3cfbc361b6922
1802-
React-FabricComponents: bb2d6b89321bf79653ae3d4ec890ba7cb9fe51c8
1803-
React-FabricImage: 019a5e834378e460ef39bf19cb506fd36491ae74
1799+
React-defaultsnativemodule: e698063aa99c75546abc7f1c18072b4d753831d8
1800+
React-domnativemodule: bd989e5b531401d419fc598e9cc09ee843d8c2bf
1801+
React-Fabric: 925fbb4d56a3c3ef9c12366f43357a913291fdc7
1802+
React-FabricComponents: e598e6f635699237db45e017cbe230d9094915fa
1803+
React-FabricImage: ace285e38358f01aa89a5974f5f803db72a2bb9d
18041804
React-featureflags: cb3dca1c74ba813f2e578c8c635989d01d14739f
1805-
React-featureflagsnativemodule: 4a1eaf7a29e48ddd60bce9a2f4c4ef74dc3b9e53
1806-
React-graphics: e626f3b24227a3a8323ed89476c8f0927c0264c7
1807-
React-hermes: 63678d262d94835f986fa2fac1c835188f14160b
1808-
React-idlecallbacksnativemodule: 7a25d2bff611677bbc2eab428e7bfd02f7418b42
1809-
React-ImageManager: 223709133aa644bc1e74d354308cf2ed4c9d0f00
1810-
React-jserrorhandler: 212d88de95b23965fdff91c1a20da30e29cdfbbb
1811-
React-jsi: d189a2a826fe6700ea1194e1c2b15535d06c8d75
1812-
React-jsiexecutor: b75a12d37f2bf84f74b5c05131afdef243cfc69d
1813-
React-jsinspector: c3402468ae1fbca79e3d8cc11e7a0fc2c8ffafb1
1814-
React-jsitracing: 1f46c2ec0c5ace3fe959b1aa0f8535ef1c021161
1815-
React-logger: 697873f06b8ba436e3cddf28018ab4741e8071b6
1816-
React-Mapbuffer: c174e11bdea12dce07df8669d6c0dc97eb0c7706
1817-
React-microtasksnativemodule: 8a80099ad7391f4e13a48b12796d96680f120dc6
1818-
react-native-http-bridge-refurbished: e2e45508ec1573999ace69a0b880eee8f0e5bab2
1819-
react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162
1805+
React-featureflagsnativemodule: 8fe6e6279a0ead0735749724e6ecd8e03f3893ca
1806+
React-graphics: f7d97c8bcc5f1568fb840b6d8940af0ae89b387c
1807+
React-hermes: a12bf33d9915dbe2dcde5b6b781faab6684883fb
1808+
React-idlecallbacksnativemodule: 4dfe6da504ae4f7792132ba164c00ae192aa4a57
1809+
React-ImageManager: 28861af68262a45e585eca5491d05cd963ab0071
1810+
React-jserrorhandler: 15bea720b272a2e78b7731df122dbfa6e27b65aa
1811+
React-jsi: 217274301608d7fa529bd275c73020b55cf39361
1812+
React-jsiexecutor: 1bcbc63a8c1d698b35c9fb521ee87aa48a3702d2
1813+
React-jsinspector: 1a3345f90762b3ba2d0ab3ff5f91022487b2ed38
1814+
React-jsitracing: 46adf5fbb769aa673145b5c57ed7cd4b7cd08e1c
1815+
React-logger: ae95f0effa7e1791bd6f7283caddca323d4fbc1e
1816+
React-Mapbuffer: 7eb5d69e1154e7743487ef0c8d7261e5b59afb32
1817+
React-microtasksnativemodule: 01dd998649ff5f8814846b7eee84c4d57f5d3671
1818+
react-native-http-bridge-refurbished: 1bd13b32a8e62abe61bab809c26e2dcf21256ed7
1819+
react-native-restart: 0bc732f4461709022a742bb29bcccf6bbc5b4863
18201820
React-nativeconfig: f7ab6c152e780b99a8c17448f2d99cf5f69a2311
1821-
React-NativeModulesApple: 70600f7edfc2c2a01e39ab13a20fd59f4c60df0b
1822-
React-perflogger: ceb97dd4e5ca6ff20eebb5a6f9e00312dcdea872
1823-
React-performancetimeline: e39f038509c2a6b2ddb85087ba7cb8bd9caf977d
1821+
React-NativeModulesApple: 9aeb901b9bfcc9235e912445fb3cf4780a99baf4
1822+
React-perflogger: 16e049953d21b37e9871ddf0b02f414e12ff14ba
1823+
React-performancetimeline: 00d156ec43d1110a2e7dacb168a7ac95a81eccc7
18241824
React-RCTActionSheet: a4388035260b01ac38d3647da0433b0455da9bae
1825-
React-RCTAnimation: 84117cb3521c40e95a4edfeab1c1cb159bc9a7c3
1826-
React-RCTAppDelegate: df039dffb7adbc2e4a8ce951d1b2842f1846f43e
1827-
React-RCTBlob: 947cbb49842c9141e2b21f719e83e9197a06e453
1828-
React-RCTFabric: 8f8afe72401ddfca2bd8b488d2d9eb0deee0b4bf
1829-
React-RCTImage: 367a7dcca1d37b04e28918c025a0101494fb2a19
1830-
React-RCTLinking: b9dc797e49683a98ee4f703f1f01ec2bd69ceb7f
1831-
React-RCTNetwork: 16e92fb59b9cd1e1175ecb2e90aa9e06e82db7a3
1832-
React-RCTSettings: 20a1c3316956fae137d8178b4c23b7a1d56674cc
1833-
React-RCTText: 59d8792076b6010f7305f2558d868025004e108b
1834-
React-RCTVibration: 597d5aba0212d709ec79d12e76285c3d94dc0658
1825+
React-RCTAnimation: 9cc9e88ec5f94d573d3b5d5d9702f47774d8603c
1826+
React-RCTAppDelegate: b8ca6a50167b71d67c477985597429485f39f964
1827+
React-RCTBlob: f879b05cf702dd4099054c3c3bf05bd4757de701
1828+
React-RCTFabric: 69ac989ccf18904cd6ad79d364cbd50343f125f3
1829+
React-RCTImage: 8fc2b137d17fb8756cdba38d74f4d40fb9499dee
1830+
React-RCTLinking: e691e89d8658aaa772c59084a45a96e8c9ef8df1
1831+
React-RCTNetwork: 749cb659702c3faf3efecfcb982150be0f2c834a
1832+
React-RCTSettings: 60c431627d37e6d996e0f61a9e84df8e41d898cb
1833+
React-RCTText: 74cc248bf8d2f6d07beb6196aa4c7055b3eb1a51
1834+
React-RCTVibration: 81ff3704c7ed66a99e2670167252fd0e9a10980b
18351835
React-rendererconsistency: 42f182fe910ad6c9b449cc62adae8d0eaba76f0a
1836-
React-rendererdebug: f36daf9f79831c8785215048fad4ef6453834430
1836+
React-rendererdebug: b11083c452ed6f2a03029a9105d0d9ab7d9af1c8
18371837
React-rncore: 85ed76036ff56e2e9c369155027cbbd84db86006
1838-
React-RuntimeApple: 6ca44fc23bb00474f9387c0709f23d4dade79800
1839-
React-RuntimeCore: b4d723e516e2e24616eb72de5b41a68b0736cc02
1838+
React-RuntimeApple: 3154e09ccb48d81dcbb13f986a5313686c1d6983
1839+
React-RuntimeCore: 985985d121db1fde5387d4dfedae78e13a5e317d
18401840
React-runtimeexecutor: 10fae9492194097c99f6e34cedbb42a308922d32
1841-
React-RuntimeHermes: 93437bfc028ba48122276e2748c7cd0f9bbcdb40
1842-
React-runtimescheduler: 72bbb4bd4774a0f4f9a7e84dbf133213197a0828
1841+
React-RuntimeHermes: 3984572bc295675360849b07ab2608bfbd8db35d
1842+
React-runtimescheduler: 215d21fbcb922aa469c6adcf5a729e2769d210e4
18431843
React-timing: 1050c6fa44c327f2d7538e10c548fdf521fabdb8
1844-
React-utils: 541c6cca08f32597d4183f00e83eef2ed20d4c54
1845-
ReactCodegen: daa13d9e48c9bdb1daac4bd694b9dd54e06681df
1846-
ReactCommon: a6b87a7591591f7a52d9c0fec3aa05e0620d5dd3
1847-
RNShare: e1721a8818a3bf111ed686ed5d8c1dc76b91c8ad
1844+
React-utils: f584a494ac233c7857bab176416b0c49cb4037ba
1845+
ReactCodegen: 3a68408bf68d0957abcd13d610f76420005c1d91
1846+
ReactCommon: 5809a8ee421b7219221a475b78180f8f34b5c5ec
1847+
RNShare: 49a61c3177b54c3303a77bdd2a54ba2803092d6d
18481848
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
18491849
Yoga: fcc198acd4a55599b3468cfb6ebc526baff5f06e
18501850

18511851
PODFILE CHECKSUM: 737501a1bf4136f64686144938c4ffc7b1cb39b0
18521852

1853-
COCOAPODS: 1.15.2
1853+
COCOAPODS: 1.16.2

example/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {blobTests, dbSetupTests, queriesTests, runTests} from './tests/index';
2626
import {preparedStatementsTests} from './tests/preparedStatements.spec';
2727
import {reactiveTests} from './tests/reactive.spec';
2828
import {tokenizerTests} from './tests/tokenizer.spec';
29+
import {storageTests} from './tests/storage.spec';
2930

3031
export default function App() {
3132
const [times, setTimes] = useState<number[]>([]);
@@ -46,6 +47,7 @@ export default function App() {
4647
constantsTests,
4748
reactiveTests,
4849
tokenizerTests,
50+
storageTests,
4951
)
5052
.then(results => {
5153
setServerResults(results as any);

example/src/tests/storage.spec.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {Storage} from '@op-engineering/op-sqlite';
2+
import chai from 'chai';
3+
import {afterEach, beforeEach, describe, it} from './MochaRNAdapter';
4+
5+
const expect = chai.expect;
6+
7+
export function storageTests() {
8+
let storage: Storage;
9+
10+
describe('Queries tests', () => {
11+
beforeEach(async () => {
12+
storage = new Storage({encryptionKey: 'test'});
13+
});
14+
15+
afterEach(() => {});
16+
17+
it('Can set and get sync', async () => {
18+
storage.setItemSync('foo', 'bar');
19+
const res = storage.getItemSync('foo');
20+
expect(res).to.equal('bar');
21+
});
22+
23+
it('Can set and get async', async () => {
24+
await storage.setItem('quack', 'bark');
25+
const res = await storage.getItem('quack');
26+
expect(res).to.equal('bark');
27+
});
28+
29+
it('can remove item sync', async () => {
30+
storage.setItemSync('foo', 'bar');
31+
storage.removeItemSync('foo');
32+
const res = storage.getItemSync('foo');
33+
expect(res).to.equal(undefined);
34+
});
35+
36+
it('can remove item async', async () => {
37+
await storage.setItem('quack', 'bark');
38+
await storage.removeItem('quack');
39+
const res = await storage.getItem('quack');
40+
expect(res).to.equal(undefined);
41+
});
42+
43+
it('can clear', async () => {
44+
await storage.setItem('quack', 'bark');
45+
await storage.setItem('quack2', 'bark');
46+
await storage.clear();
47+
const res = await storage.getItem('quack');
48+
expect(res).to.equal(undefined);
49+
});
50+
51+
it('can clear sync', async () => {
52+
storage.setItemSync('quack', 'bark');
53+
storage.setItemSync('quack2', 'bark');
54+
storage.clearSync();
55+
const res = storage.getItemSync('quack');
56+
expect(res).to.equal(undefined);
57+
});
58+
59+
it('can get all keys', async () => {
60+
await storage.setItem('quack', 'bark');
61+
await storage.setItem('quack2', 'bark');
62+
const keys = storage.getAllKeys();
63+
expect(keys).to.deep.equal(['quack', 'quack2']);
64+
});
65+
});
66+
}

src/Storage.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { open, type DB } from '.';
2+
3+
type StorageOptions = {
4+
location?: string;
5+
encryptionKey?: string;
6+
};
7+
8+
/**
9+
* Creates a new async-storage api compatible instance.
10+
* The encryption key is only used when compiled against the SQLCipher version of op-sqlite.
11+
*/
12+
export class Storage {
13+
private db: DB;
14+
15+
constructor(options: StorageOptions) {
16+
this.db = open({ ...options, name: '__opsqlite_storage' });
17+
this.db.executeSync('PRAGMA mmap_size=268435456');
18+
this.db.executeSync(
19+
'CREATE TABLE IF NOT EXISTS storage (key TEXT PRIMARY KEY, value TEXT) WITHOUT ROWID'
20+
);
21+
}
22+
23+
async getItem(key: string): Promise<string | undefined> {
24+
const result = await this.db.execute(
25+
'SELECT value FROM storage WHERE key = ?',
26+
[key]
27+
);
28+
29+
let value = result.rows[0]?.value;
30+
if (typeof value !== 'undefined' && typeof value !== 'string') {
31+
throw new Error('Value must be a string or undefined');
32+
}
33+
return value;
34+
}
35+
36+
getItemSync(key: string): string | undefined {
37+
const result = this.db.executeSync(
38+
'SELECT value FROM storage WHERE key = ?',
39+
[key]
40+
);
41+
42+
let value = result.rows[0]?.value;
43+
if (typeof value !== 'undefined' && typeof value !== 'string') {
44+
throw new Error('Value must be a string or undefined');
45+
}
46+
47+
return value;
48+
}
49+
50+
async setItem(key: string, value: string) {
51+
await this.db.execute(
52+
'INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)',
53+
[key, value.toString()]
54+
);
55+
}
56+
57+
setItemSync(key: string, value: string) {
58+
this.db.executeSync(
59+
'INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)',
60+
[key, value.toString()]
61+
);
62+
}
63+
64+
async removeItem(key: string) {
65+
await this.db.execute('DELETE FROM storage WHERE key = ?', [key]);
66+
}
67+
68+
removeItemSync(key: string) {
69+
this.db.executeSync('DELETE FROM storage WHERE key = ?', [key]);
70+
}
71+
72+
async clear() {
73+
await this.db.execute('DELETE FROM storage');
74+
}
75+
76+
clearSync() {
77+
this.db.executeSync('DELETE FROM storage');
78+
}
79+
80+
getAllKeys() {
81+
return this.db
82+
.executeSync('SELECT key FROM storage')
83+
.rows.map((row: any) => row.key);
84+
}
85+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NativeModules, Platform } from 'react-native';
2+
export { Storage } from './Storage';
23

34
export type Scalar =
45
| string

0 commit comments

Comments
 (0)