Skip to content

Commit

Permalink
Merge pull request #234 from OP-Engineering/oscar/key-value-storage
Browse files Browse the repository at this point in the history
Oscar/key value storage
  • Loading branch information
ospfranco authored Jan 25, 2025
2 parents 1cdda73 + 83acbd1 commit 9c609a0
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 48 deletions.
2 changes: 1 addition & 1 deletion docs/docs/api.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 8
sidebar_position: 9
---

# API Reference
Expand Down
31 changes: 31 additions & 0 deletions docs/docs/key_value_storage.md
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();
```
94 changes: 47 additions & 47 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1785,69 +1785,69 @@ SPEC CHECKSUMS:
GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
hermes-engine: 06a9c6900587420b90accc394199527c64259db4
op-sqlite: ed6d12bc6ff23accfea5ba6b7db717d3958852ce
op-sqlite: 4f4f7845e0208b1a6dfa6ac3db7dcab039c109b7
RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648
RCTDeprecation: fb7d408617e25d7f537940000d766d60149c5fea
RCTRequired: 9aaf0ffcc1f41f0c671af863970ef25c422a9920
RCTTypeSafety: e9a6e7d48184646eb0610295b74c0dd02768cbb2
React: fffb3cf1b0d7aee03c4eb4952b2d58783615e9fa
React-callinvoker: 3c6ecc0315d42924e01b3ddc25cf2e49d33da169
React-Core: d2143ba58d0c8563cf397f96f699c6069eba951c
React-CoreModules: b3cbc5e3090a8c23116c0c7dd8998e0637e29619
React-cxxreact: 68fb9193582c4a411ce99d0b23f7b3d8da1c2e4a
React-Core: 1a5ddefb00dd72644171dd39bb4bbcd7849c70f0
React-CoreModules: 8de64f712fe272ed08f37aaf64633ddf793e70d3
React-cxxreact: e204185e1da1c843fec2bbb10bcc5b5800355dfa
React-debug: 297ed67868a76e8384669ea9b5c65c5d9d9d15d9
React-defaultsnativemodule: 9726dafb3b20bb49f9eac5993418aaa7ddb6a80d
React-domnativemodule: ff049da74cb1be08b7cd71cdbc7bb5b335e04d8e
React-Fabric: 2e33816098a5a29d2f4ae7eb2de3cfbc361b6922
React-FabricComponents: bb2d6b89321bf79653ae3d4ec890ba7cb9fe51c8
React-FabricImage: 019a5e834378e460ef39bf19cb506fd36491ae74
React-defaultsnativemodule: e698063aa99c75546abc7f1c18072b4d753831d8
React-domnativemodule: bd989e5b531401d419fc598e9cc09ee843d8c2bf
React-Fabric: 925fbb4d56a3c3ef9c12366f43357a913291fdc7
React-FabricComponents: e598e6f635699237db45e017cbe230d9094915fa
React-FabricImage: ace285e38358f01aa89a5974f5f803db72a2bb9d
React-featureflags: cb3dca1c74ba813f2e578c8c635989d01d14739f
React-featureflagsnativemodule: 4a1eaf7a29e48ddd60bce9a2f4c4ef74dc3b9e53
React-graphics: e626f3b24227a3a8323ed89476c8f0927c0264c7
React-hermes: 63678d262d94835f986fa2fac1c835188f14160b
React-idlecallbacksnativemodule: 7a25d2bff611677bbc2eab428e7bfd02f7418b42
React-ImageManager: 223709133aa644bc1e74d354308cf2ed4c9d0f00
React-jserrorhandler: 212d88de95b23965fdff91c1a20da30e29cdfbbb
React-jsi: d189a2a826fe6700ea1194e1c2b15535d06c8d75
React-jsiexecutor: b75a12d37f2bf84f74b5c05131afdef243cfc69d
React-jsinspector: c3402468ae1fbca79e3d8cc11e7a0fc2c8ffafb1
React-jsitracing: 1f46c2ec0c5ace3fe959b1aa0f8535ef1c021161
React-logger: 697873f06b8ba436e3cddf28018ab4741e8071b6
React-Mapbuffer: c174e11bdea12dce07df8669d6c0dc97eb0c7706
React-microtasksnativemodule: 8a80099ad7391f4e13a48b12796d96680f120dc6
react-native-http-bridge-refurbished: e2e45508ec1573999ace69a0b880eee8f0e5bab2
react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162
React-featureflagsnativemodule: 8fe6e6279a0ead0735749724e6ecd8e03f3893ca
React-graphics: f7d97c8bcc5f1568fb840b6d8940af0ae89b387c
React-hermes: a12bf33d9915dbe2dcde5b6b781faab6684883fb
React-idlecallbacksnativemodule: 4dfe6da504ae4f7792132ba164c00ae192aa4a57
React-ImageManager: 28861af68262a45e585eca5491d05cd963ab0071
React-jserrorhandler: 15bea720b272a2e78b7731df122dbfa6e27b65aa
React-jsi: 217274301608d7fa529bd275c73020b55cf39361
React-jsiexecutor: 1bcbc63a8c1d698b35c9fb521ee87aa48a3702d2
React-jsinspector: 1a3345f90762b3ba2d0ab3ff5f91022487b2ed38
React-jsitracing: 46adf5fbb769aa673145b5c57ed7cd4b7cd08e1c
React-logger: ae95f0effa7e1791bd6f7283caddca323d4fbc1e
React-Mapbuffer: 7eb5d69e1154e7743487ef0c8d7261e5b59afb32
React-microtasksnativemodule: 01dd998649ff5f8814846b7eee84c4d57f5d3671
react-native-http-bridge-refurbished: 1bd13b32a8e62abe61bab809c26e2dcf21256ed7
react-native-restart: 0bc732f4461709022a742bb29bcccf6bbc5b4863
React-nativeconfig: f7ab6c152e780b99a8c17448f2d99cf5f69a2311
React-NativeModulesApple: 70600f7edfc2c2a01e39ab13a20fd59f4c60df0b
React-perflogger: ceb97dd4e5ca6ff20eebb5a6f9e00312dcdea872
React-performancetimeline: e39f038509c2a6b2ddb85087ba7cb8bd9caf977d
React-NativeModulesApple: 9aeb901b9bfcc9235e912445fb3cf4780a99baf4
React-perflogger: 16e049953d21b37e9871ddf0b02f414e12ff14ba
React-performancetimeline: 00d156ec43d1110a2e7dacb168a7ac95a81eccc7
React-RCTActionSheet: a4388035260b01ac38d3647da0433b0455da9bae
React-RCTAnimation: 84117cb3521c40e95a4edfeab1c1cb159bc9a7c3
React-RCTAppDelegate: df039dffb7adbc2e4a8ce951d1b2842f1846f43e
React-RCTBlob: 947cbb49842c9141e2b21f719e83e9197a06e453
React-RCTFabric: 8f8afe72401ddfca2bd8b488d2d9eb0deee0b4bf
React-RCTImage: 367a7dcca1d37b04e28918c025a0101494fb2a19
React-RCTLinking: b9dc797e49683a98ee4f703f1f01ec2bd69ceb7f
React-RCTNetwork: 16e92fb59b9cd1e1175ecb2e90aa9e06e82db7a3
React-RCTSettings: 20a1c3316956fae137d8178b4c23b7a1d56674cc
React-RCTText: 59d8792076b6010f7305f2558d868025004e108b
React-RCTVibration: 597d5aba0212d709ec79d12e76285c3d94dc0658
React-RCTAnimation: 9cc9e88ec5f94d573d3b5d5d9702f47774d8603c
React-RCTAppDelegate: b8ca6a50167b71d67c477985597429485f39f964
React-RCTBlob: f879b05cf702dd4099054c3c3bf05bd4757de701
React-RCTFabric: 69ac989ccf18904cd6ad79d364cbd50343f125f3
React-RCTImage: 8fc2b137d17fb8756cdba38d74f4d40fb9499dee
React-RCTLinking: e691e89d8658aaa772c59084a45a96e8c9ef8df1
React-RCTNetwork: 749cb659702c3faf3efecfcb982150be0f2c834a
React-RCTSettings: 60c431627d37e6d996e0f61a9e84df8e41d898cb
React-RCTText: 74cc248bf8d2f6d07beb6196aa4c7055b3eb1a51
React-RCTVibration: 81ff3704c7ed66a99e2670167252fd0e9a10980b
React-rendererconsistency: 42f182fe910ad6c9b449cc62adae8d0eaba76f0a
React-rendererdebug: f36daf9f79831c8785215048fad4ef6453834430
React-rendererdebug: b11083c452ed6f2a03029a9105d0d9ab7d9af1c8
React-rncore: 85ed76036ff56e2e9c369155027cbbd84db86006
React-RuntimeApple: 6ca44fc23bb00474f9387c0709f23d4dade79800
React-RuntimeCore: b4d723e516e2e24616eb72de5b41a68b0736cc02
React-RuntimeApple: 3154e09ccb48d81dcbb13f986a5313686c1d6983
React-RuntimeCore: 985985d121db1fde5387d4dfedae78e13a5e317d
React-runtimeexecutor: 10fae9492194097c99f6e34cedbb42a308922d32
React-RuntimeHermes: 93437bfc028ba48122276e2748c7cd0f9bbcdb40
React-runtimescheduler: 72bbb4bd4774a0f4f9a7e84dbf133213197a0828
React-RuntimeHermes: 3984572bc295675360849b07ab2608bfbd8db35d
React-runtimescheduler: 215d21fbcb922aa469c6adcf5a729e2769d210e4
React-timing: 1050c6fa44c327f2d7538e10c548fdf521fabdb8
React-utils: 541c6cca08f32597d4183f00e83eef2ed20d4c54
ReactCodegen: daa13d9e48c9bdb1daac4bd694b9dd54e06681df
ReactCommon: a6b87a7591591f7a52d9c0fec3aa05e0620d5dd3
RNShare: e1721a8818a3bf111ed686ed5d8c1dc76b91c8ad
React-utils: f584a494ac233c7857bab176416b0c49cb4037ba
ReactCodegen: 3a68408bf68d0957abcd13d610f76420005c1d91
ReactCommon: 5809a8ee421b7219221a475b78180f8f34b5c5ec
RNShare: 49a61c3177b54c3303a77bdd2a54ba2803092d6d
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: fcc198acd4a55599b3468cfb6ebc526baff5f06e

PODFILE CHECKSUM: 737501a1bf4136f64686144938c4ffc7b1cb39b0

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {blobTests, dbSetupTests, queriesTests, runTests} from './tests/index';
import {preparedStatementsTests} from './tests/preparedStatements.spec';
import {reactiveTests} from './tests/reactive.spec';
import {tokenizerTests} from './tests/tokenizer.spec';
import {storageTests} from './tests/storage.spec';

export default function App() {
const [times, setTimes] = useState<number[]>([]);
Expand All @@ -46,6 +47,7 @@ export default function App() {
constantsTests,
reactiveTests,
tokenizerTests,
storageTests,
)
.then(results => {
setServerResults(results as any);
Expand Down
66 changes: 66 additions & 0 deletions example/src/tests/storage.spec.ts
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']);
});
});
}
85 changes: 85 additions & 0 deletions src/Storage.ts
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);
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NativeModules, Platform } from 'react-native';
export { Storage } from './Storage';

export type Scalar =
| string
Expand Down

0 comments on commit 9c609a0

Please sign in to comment.