Skip to content

Commit 3059697

Browse files
committed
feat: support path-aware MMKV deletion checks
1 parent 87f408e commit 3059697

12 files changed

Lines changed: 244 additions & 23 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ To check if an MMKV instance exists, use `existsMMKV(...)`:
241241
import { existsMMKV } from 'react-native-mmkv'
242242

243243
const exists = existsMMKV('my-instance')
244+
245+
// For instances created with a custom path:
246+
const existsAtPath = existsMMKV({
247+
id: 'my-instance',
248+
path: '/custom/mmkv/path',
249+
})
244250
```
245251

246252
### Delete an MMKV instance
@@ -251,6 +257,12 @@ To delete an MMKV instance, use `deleteMMKV(...)`:
251257
import { deleteMMKV } from 'react-native-mmkv'
252258

253259
const wasDeleted = deleteMMKV('my-instance')
260+
261+
// For instances created with a custom path:
262+
const wasDeletedAtPath = deleteMMKV({
263+
id: 'my-instance',
264+
path: '/custom/mmkv/path',
265+
})
254266
```
255267

256268
### Log Level

example/__tests__/MMKV.harness.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from 'react-native-harness';
88
import { Platform } from 'react-native';
99
import { MMKV, createMMKV, deleteMMKV, existsMMKV } from 'react-native-mmkv';
10+
import { getPlatformContext } from '../../packages/react-native-mmkv/src/getMMKVFactory';
1011

1112
const skipOnWeb = (reason: string): boolean => {
1213
if (Platform.OS === 'web') {
@@ -20,6 +21,12 @@ const waitForNextTick = async () => {
2021
await new Promise<void>((resolve) => setTimeout(resolve, 0));
2122
};
2223

24+
const getNativeCustomPath = (name: string): string | undefined => {
25+
if (skipOnWeb('custom paths are not supported on Web')) return undefined;
26+
const baseDirectory = getPlatformContext().getBaseDirectory();
27+
return `${baseDirectory}/${name}`;
28+
};
29+
2330
describe('MMKV Core Functionality', () => {
2431
let storage: MMKV;
2532

@@ -1186,6 +1193,27 @@ describe('Deleting instances and checking if they exist', () => {
11861193
const exists = existsMMKV('some-non-existing-instance');
11871194
expect(exists).toStrictEqual(false);
11881195
});
1196+
1197+
it('should accept an instance location object', () => {
1198+
const id = `some-location-instance-${Date.now()}`;
1199+
const storage = createMMKV({ id });
1200+
storage.set('value', 'stored');
1201+
1202+
const exists = existsMMKV({ id });
1203+
expect(exists).toStrictEqual(true);
1204+
});
1205+
1206+
it('should check custom path instances with their path', () => {
1207+
const id = `some-path-instance-${Date.now()}`;
1208+
const path = getNativeCustomPath(`path-aware-exists-${Date.now()}`);
1209+
if (path == null) return;
1210+
1211+
const storage = createMMKV({ id, path });
1212+
storage.set('value', 'stored');
1213+
1214+
expect(existsMMKV(id)).toStrictEqual(false);
1215+
expect(existsMMKV({ id, path })).toStrictEqual(true);
1216+
});
11891217
});
11901218

11911219
describe('Deleting an instance', () => {
@@ -1214,6 +1242,36 @@ describe('Deleting instances and checking if they exist', () => {
12141242
const wasDeleted = deleteMMKV('some-non-existing-instance');
12151243
expect(wasDeleted).toStrictEqual(false);
12161244
});
1245+
1246+
it('should accept an instance location object', () => {
1247+
const id = `some-location-instance-${Date.now()}`;
1248+
const storage = createMMKV({ id });
1249+
storage.set('value', 'stored');
1250+
1251+
const wasDeleted = deleteMMKV({ id });
1252+
expect(wasDeleted).toStrictEqual(true);
1253+
expect(existsMMKV({ id })).toStrictEqual(false);
1254+
});
1255+
1256+
it('should delete custom path instances with their path', () => {
1257+
const id = `some-path-instance-${Date.now()}`;
1258+
const path = getNativeCustomPath(`path-aware-delete-${Date.now()}`);
1259+
if (path == null) return;
1260+
1261+
const storage = createMMKV({ id, path });
1262+
storage.set('value', 'stored');
1263+
1264+
expect(existsMMKV({ id, path })).toStrictEqual(true);
1265+
expect(deleteMMKV({ id, path })).toStrictEqual(true);
1266+
expect(existsMMKV({ id, path })).toStrictEqual(false);
1267+
});
1268+
1269+
it('should reject custom paths on Web', () => {
1270+
if (Platform.OS !== 'web') return;
1271+
1272+
expect(() => existsMMKV({ id: 'some-instance', path: '/tmp' })).toThrow();
1273+
expect(() => deleteMMKV({ id: 'some-instance', path: '/tmp' })).toThrow();
1274+
});
12171275
});
12181276
});
12191277

packages/react-native-mmkv/cpp/HybridMMKVFactory.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ std::shared_ptr<HybridMMKVSpec> HybridMMKVFactory::createMMKV(const Configuratio
2626
return std::make_shared<HybridMMKV>(configuration);
2727
}
2828

29-
bool HybridMMKVFactory::deleteMMKV(const std::string& id) {
30-
return MMKV::removeStorage(id);
29+
bool HybridMMKVFactory::deleteMMKV(const MMKVInstanceLocation& location) {
30+
std::string rootPath = location.path.value_or("");
31+
std::string* rootPathPtr = rootPath.empty() ? nullptr : &rootPath;
32+
return MMKV::removeStorage(location.id, rootPathPtr);
3133
}
3234

33-
bool HybridMMKVFactory::existsMMKV(const std::string& id) {
34-
return MMKV::checkExist(id);
35+
bool HybridMMKVFactory::existsMMKV(const MMKVInstanceLocation& location) {
36+
std::string rootPath = location.path.value_or("");
37+
std::string* rootPathPtr = rootPath.empty() ? nullptr : &rootPath;
38+
return MMKV::checkExist(location.id, rootPathPtr);
3539
}
3640

3741
} // namespace margelo::nitro::mmkv

packages/react-native-mmkv/cpp/HybridMMKVFactory.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ class HybridMMKVFactory final : public HybridMMKVFactorySpec {
2020
void initializeMMKV(const std::string& rootPath) override;
2121

2222
std::shared_ptr<HybridMMKVSpec> createMMKV(const Configuration& configuration) override;
23-
bool deleteMMKV(const std::string& id) override;
24-
bool existsMMKV(const std::string& id) override;
23+
bool deleteMMKV(const MMKVInstanceLocation& location) override;
24+
bool existsMMKV(const MMKVInstanceLocation& location) override;
2525
};
2626

2727
} // namespace margelo::nitro::mmkv

packages/react-native-mmkv/nitrogen/generated/shared/c++/HybridMMKVFactorySpec.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
namespace margelo::nitro::mmkv { class HybridMMKVSpec; }
1818
// Forward declaration of `Configuration` to properly resolve imports.
1919
namespace margelo::nitro::mmkv { struct Configuration; }
20+
// Forward declaration of `MMKVInstanceLocation` to properly resolve imports.
21+
namespace margelo::nitro::mmkv { struct MMKVInstanceLocation; }
2022

2123
#include <string>
2224
#include <memory>
2325
#include "HybridMMKVSpec.hpp"
2426
#include "Configuration.hpp"
27+
#include "MMKVInstanceLocation.hpp"
2528

2629
namespace margelo::nitro::mmkv {
2730

@@ -56,8 +59,8 @@ namespace margelo::nitro::mmkv {
5659
// Methods
5760
virtual void initializeMMKV(const std::string& rootPath) = 0;
5861
virtual std::shared_ptr<HybridMMKVSpec> createMMKV(const Configuration& configuration) = 0;
59-
virtual bool deleteMMKV(const std::string& id) = 0;
60-
virtual bool existsMMKV(const std::string& id) = 0;
62+
virtual bool deleteMMKV(const MMKVInstanceLocation& location) = 0;
63+
virtual bool existsMMKV(const MMKVInstanceLocation& location) = 0;
6164

6265
protected:
6366
// Hybrid Setup
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
///
2+
/// MMKVInstanceLocation.hpp
3+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4+
/// https://github.com/mrousavy/nitro
5+
/// Copyright © Marc Rousavy @ Margelo
6+
///
7+
8+
#pragma once
9+
10+
#if __has_include(<NitroModules/JSIConverter.hpp>)
11+
#include <NitroModules/JSIConverter.hpp>
12+
#else
13+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14+
#endif
15+
#if __has_include(<NitroModules/NitroDefines.hpp>)
16+
#include <NitroModules/NitroDefines.hpp>
17+
#else
18+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19+
#endif
20+
#if __has_include(<NitroModules/JSIHelpers.hpp>)
21+
#include <NitroModules/JSIHelpers.hpp>
22+
#else
23+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24+
#endif
25+
#if __has_include(<NitroModules/PropNameIDCache.hpp>)
26+
#include <NitroModules/PropNameIDCache.hpp>
27+
#else
28+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
29+
#endif
30+
31+
32+
33+
#include <string>
34+
#include <optional>
35+
36+
namespace margelo::nitro::mmkv {
37+
38+
/**
39+
* A struct which can be represented as a JavaScript object (MMKVInstanceLocation).
40+
*/
41+
struct MMKVInstanceLocation final {
42+
public:
43+
std::string id SWIFT_PRIVATE;
44+
std::optional<std::string> path SWIFT_PRIVATE;
45+
46+
public:
47+
MMKVInstanceLocation() = default;
48+
explicit MMKVInstanceLocation(std::string id, std::optional<std::string> path): id(id), path(path) {}
49+
50+
public:
51+
friend bool operator==(const MMKVInstanceLocation& lhs, const MMKVInstanceLocation& rhs) = default;
52+
};
53+
54+
} // namespace margelo::nitro::mmkv
55+
56+
namespace margelo::nitro {
57+
58+
// C++ MMKVInstanceLocation <> JS MMKVInstanceLocation (object)
59+
template <>
60+
struct JSIConverter<margelo::nitro::mmkv::MMKVInstanceLocation> final {
61+
static inline margelo::nitro::mmkv::MMKVInstanceLocation fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
62+
jsi::Object obj = arg.asObject(runtime);
63+
return margelo::nitro::mmkv::MMKVInstanceLocation(
64+
JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "id"))),
65+
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "path")))
66+
);
67+
}
68+
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::mmkv::MMKVInstanceLocation& arg) {
69+
jsi::Object obj(runtime);
70+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "id"), JSIConverter<std::string>::toJSI(runtime, arg.id));
71+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "path"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.path));
72+
return obj;
73+
}
74+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
75+
if (!value.isObject()) {
76+
return false;
77+
}
78+
jsi::Object obj = value.getObject(runtime);
79+
if (!nitro::isPlainObject(runtime, obj)) {
80+
return false;
81+
}
82+
if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "id")))) return false;
83+
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "path")))) return false;
84+
return true;
85+
}
86+
};
87+
88+
} // namespace margelo::nitro
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
import type { MMKVInstanceLocation } from '../specs/MMKVFactory.nitro'
12
import { getMMKVFactory } from '../getMMKVFactory'
23
import { isTest } from '../isTest'
34

4-
export function deleteMMKV(id: string): boolean {
5+
export function deleteMMKV(id: string): boolean
6+
export function deleteMMKV(location: MMKVInstanceLocation): boolean
7+
export function deleteMMKV(
8+
idOrLocation: string | MMKVInstanceLocation
9+
): boolean {
510
if (isTest()) {
611
return true
712
}
813

914
const factory = getMMKVFactory()
10-
return factory.deleteMMKV(id)
15+
const location =
16+
typeof idOrLocation === 'string' ? { id: idOrLocation } : idOrLocation
17+
return factory.deleteMMKV(location)
1118
}

packages/react-native-mmkv/src/deleteMMKV/deleteMMKV.web.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
import type { MMKVInstanceLocation } from '../specs/MMKVFactory.nitro'
12
import {
23
getLocalStorage,
34
LOCAL_STORAGE_KEY_WILDCARD,
45
} from '../web/getLocalStorage'
56

6-
export function deleteMMKV(id: string): boolean {
7+
export function deleteMMKV(id: string): boolean
8+
export function deleteMMKV(location: MMKVInstanceLocation): boolean
9+
export function deleteMMKV(
10+
idOrLocation: string | MMKVInstanceLocation
11+
): boolean {
12+
const location =
13+
typeof idOrLocation === 'string' ? { id: idOrLocation } : idOrLocation
14+
if (location.path != null) {
15+
throw new Error("MMKV: 'path' is not supported on Web!")
16+
}
17+
718
const storage = getLocalStorage()
8-
const prefix = id + LOCAL_STORAGE_KEY_WILDCARD
19+
const prefix = location.id + LOCAL_STORAGE_KEY_WILDCARD
920
let wasRemoved = false
1021

1122
const keys = Object.keys(storage)
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
import type { MMKVInstanceLocation } from '../specs/MMKVFactory.nitro'
12
import { getMMKVFactory } from '../getMMKVFactory'
23
import { isTest } from '../isTest'
34

4-
export function existsMMKV(id: string): boolean {
5+
export function existsMMKV(id: string): boolean
6+
export function existsMMKV(location: MMKVInstanceLocation): boolean
7+
export function existsMMKV(
8+
idOrLocation: string | MMKVInstanceLocation
9+
): boolean {
510
if (isTest()) {
611
return true
712
}
813

914
const factory = getMMKVFactory()
10-
return factory.existsMMKV(id)
15+
const location =
16+
typeof idOrLocation === 'string' ? { id: idOrLocation } : idOrLocation
17+
return factory.existsMMKV(location)
1118
}
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
import type { MMKVInstanceLocation } from '../specs/MMKVFactory.nitro'
12
import {
23
getLocalStorage,
34
LOCAL_STORAGE_KEY_WILDCARD,
45
} from '../web/getLocalStorage'
56

6-
export function existsMMKV(id: string): boolean {
7+
export function existsMMKV(id: string): boolean
8+
export function existsMMKV(location: MMKVInstanceLocation): boolean
9+
export function existsMMKV(
10+
idOrLocation: string | MMKVInstanceLocation
11+
): boolean {
12+
const location =
13+
typeof idOrLocation === 'string' ? { id: idOrLocation } : idOrLocation
14+
if (location.path != null) {
15+
throw new Error("MMKV: 'path' is not supported on Web!")
16+
}
17+
718
const storage = getLocalStorage()
8-
const prefix = id + LOCAL_STORAGE_KEY_WILDCARD
19+
const prefix = location.id + LOCAL_STORAGE_KEY_WILDCARD
920
const keys = Object.keys(storage)
1021
return keys.some((k) => k.startsWith(prefix))
1122
}

0 commit comments

Comments
 (0)