react-native-mmkv 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NitroMmkv.podspec CHANGED
@@ -24,13 +24,20 @@ Pod::Spec.new do |s|
24
24
 
25
25
  # Add MMKV Core dependency
26
26
  s.compiler_flags = '-x objective-c++'
27
- s.dependency 'MMKVCore', '2.3.0'
27
+ s.dependency 'MMKVCore', '2.4.0'
28
+
29
+ # Optionally configure MMKV log level via Podfile ($MMKVLogLevel) or env var (MMKV_LOG_LEVEL)
30
+ mmkv_log_level = $MMKVLogLevel || ENV['MMKV_LOG_LEVEL']
31
+ gcc_preprocessor_defs = "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
32
+ if mmkv_log_level != nil && mmkv_log_level.to_s != ""
33
+ gcc_preprocessor_defs += " MMKV_LOG_LEVEL=#{mmkv_log_level}"
34
+ end
28
35
 
29
36
  # TODO: Remove when no one uses RN 0.79 anymore
30
37
  # Add support for React Native 0.79 or below
31
38
  s.pod_target_xcconfig = {
32
39
  "HEADER_SEARCH_PATHS" => ["${PODS_ROOT}/RCT-Folly"],
33
- "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES",
40
+ "GCC_PREPROCESSOR_DEFINITIONS" => gcc_preprocessor_defs,
34
41
  "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1"
35
42
  }
36
43
 
@@ -5,6 +5,11 @@ set (PACKAGE_NAME NitroMmkv)
5
5
  set (CMAKE_VERBOSE_MAKEFILE ON)
6
6
  set (CMAKE_CXX_STANDARD 20)
7
7
 
8
+ # Optionally configure MMKV log level (passed from Gradle as -DMMKV_LOG_LEVEL=<0-4>)
9
+ if(DEFINED MMKV_LOG_LEVEL)
10
+ add_definitions(-DMMKV_LOG_LEVEL=${MMKV_LOG_LEVEL})
11
+ endif()
12
+
8
13
  # Find all C++ files (shared and platform specifics)
9
14
  file(GLOB_RECURSE shared_files RELATIVE ${CMAKE_SOURCE_DIR}
10
15
  "../cpp/**.cpp"
@@ -5,7 +5,7 @@ buildscript {
5
5
  }
6
6
 
7
7
  dependencies {
8
- classpath "com.android.tools.build:gradle:9.0.0"
8
+ classpath "com.android.tools.build:gradle:9.1.0"
9
9
  }
10
10
  }
11
11
 
@@ -49,7 +49,12 @@ android {
49
49
  externalNativeBuild {
50
50
  cmake {
51
51
  cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
52
- arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
52
+ def mmkvLogLevel = rootProject.hasProperty("MMKV_logLevel") ? rootProject.property("MMKV_logLevel") : null
53
+ if (mmkvLogLevel != null && mmkvLogLevel != "") {
54
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DMMKV_LOG_LEVEL=${mmkvLogLevel}"
55
+ } else {
56
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
57
+ }
53
58
  abiFilters (*reactNativeArchitectures())
54
59
 
55
60
  buildTypes {
@@ -140,6 +145,6 @@ dependencies {
140
145
  implementation project(":react-native-nitro-modules")
141
146
 
142
147
  // Add a dependency on mmkv core (this ships a C++ prefab)
143
- implementation "io.github.zhongwuzw:mmkv:2.3.0"
148
+ implementation "io.github.zhongwuzw:mmkv:2.4.0"
144
149
  }
145
150
 
@@ -14,31 +14,32 @@
14
14
  namespace margelo::nitro::mmkv {
15
15
 
16
16
  HybridMMKV::HybridMMKV(const Configuration& config) : HybridObject(TAG) {
17
- std::string path = config.path.has_value() ? config.path.value() : "";
18
- std::string encryptionKey = config.encryptionKey.has_value() ? config.encryptionKey.value() : "";
17
+ MMKVMode mmkvMode = getMMKVMode(config);
18
+ if (config.readOnly.value_or(false)) {
19
+ mmkvMode = mmkvMode | MMKVMode::MMKV_READ_ONLY;
20
+ }
21
+ bool useAes256Encryption = config.encryptionType.value_or(EncryptionType::AES_128) == EncryptionType::AES_256;
22
+ std::string encryptionKey = config.encryptionKey.value_or("");
23
+ std::string* encryptionKeyPtr = encryptionKey.size() > 0 ? &encryptionKey : nullptr;
24
+ std::string rootPath = config.path.value_or("");
25
+ std::string* rootPathPtr = rootPath.size() > 0 ? &rootPath : nullptr;
26
+ bool compareBeforeSet = config.compareBeforeSet.value_or(false);
27
+
28
+ MMKVConfig mmkvConfig{.mode = mmkvMode,
29
+ .aes256 = useAes256Encryption,
30
+ .cryptKey = encryptionKeyPtr,
31
+ .rootPath = rootPathPtr,
32
+ .enableCompareBeforeSet = compareBeforeSet};
33
+
19
34
  bool hasEncryptionKey = encryptionKey.size() > 0;
20
- Logger::log(LogLevel::Info, TAG, "Creating MMKV instance \"%s\"... (Path: %s, Encrypted: %s)", config.id.c_str(), path.c_str(),
35
+ Logger::log(LogLevel::Info, TAG, "Creating MMKV instance \"%s\"... (Path: %s, Encrypted: %s)", config.id.c_str(), rootPath.c_str(),
21
36
  hasEncryptionKey ? "true" : "false");
22
37
 
23
- std::string* pathPtr = path.size() > 0 ? &path : nullptr;
24
- std::string* encryptionKeyPtr = encryptionKey.size() > 0 ? &encryptionKey : nullptr;
25
- size_t defaultExpectedCapacity = 0;
26
- bool useAes256Encryption = config.encryptionType.has_value() && config.encryptionType.value() == EncryptionType::AES_256;
27
- MMKVMode mode = getMMKVMode(config);
28
- if (config.readOnly.has_value() && config.readOnly.value()) {
29
- Logger::log(LogLevel::Info, TAG, "Instance is read-only!");
30
- mode = mode | ::mmkv::MMKV_READ_ONLY;
31
- }
32
-
33
- #ifdef __APPLE__
34
- instance = MMKV::mmkvWithID(config.id, mode, encryptionKeyPtr, pathPtr, defaultExpectedCapacity, useAes256Encryption);
35
- #else
36
- instance = MMKV::mmkvWithID(config.id, DEFAULT_MMAP_SIZE, mode, encryptionKeyPtr, pathPtr, defaultExpectedCapacity, useAes256Encryption);
37
- #endif
38
+ instance = MMKV::mmkvWithID(config.id, mmkvConfig);
38
39
 
39
40
  if (instance == nullptr) [[unlikely]] {
40
41
  // Check if instanceId is invalid
41
- if (config.id.empty()) [[unlikely]] {
42
+ if (config.id.empty()) {
42
43
  throw std::runtime_error("Failed to create MMKV instance! `id` cannot be empty!");
43
44
  }
44
45
 
@@ -57,7 +58,7 @@ HybridMMKV::HybridMMKV(const Configuration& config) : HybridObject(TAG) {
57
58
  }
58
59
 
59
60
  // Check if path is maybe invalid
60
- if (path.empty()) [[unlikely]] {
61
+ if (rootPath.empty()) [[unlikely]] {
61
62
  throw std::runtime_error("Failed to create MMKV instance! `path` cannot be empty!");
62
63
  }
63
64
 
@@ -103,25 +104,25 @@ void HybridMMKV::set(const std::string& key, const std::variant<bool, std::share
103
104
  }
104
105
 
105
106
  // Pattern-match each potential value in std::variant
106
- bool didSet = std::visit(overloaded{[&](bool b) {
107
- // boolean
108
- return instance->set(b, key);
109
- },
110
- [&](const std::shared_ptr<ArrayBuffer>& buf) {
111
- // ArrayBuffer
112
- MMBuffer buffer(buf->data(), buf->size(), MMBufferCopyFlag::MMBufferNoCopy);
113
- return instance->set(std::move(buffer), key);
114
- },
115
- [&](const std::string& string) {
116
- // string
117
- return instance->set(string, key);
118
- },
119
- [&](double number) {
120
- // number
121
- return instance->set(number, key);
122
- }},
123
- value);
124
- if (!didSet) {
107
+ bool successful = std::visit(overloaded{[&](bool b) {
108
+ // boolean
109
+ return instance->set(b, key);
110
+ },
111
+ [&](const std::shared_ptr<ArrayBuffer>& buf) {
112
+ // ArrayBuffer
113
+ MMBuffer buffer(buf->data(), buf->size(), MMBufferCopyFlag::MMBufferNoCopy);
114
+ return instance->set(std::move(buffer), key);
115
+ },
116
+ [&](const std::string& string) {
117
+ // string
118
+ return instance->set(string, key);
119
+ },
120
+ [&](double number) {
121
+ // number
122
+ return instance->set(number, key);
123
+ }},
124
+ value);
125
+ if (!successful) [[unlikely]] {
125
126
  throw std::runtime_error("Failed to set value for key \"" + key + "\"!");
126
127
  }
127
128
 
@@ -161,14 +162,7 @@ std::optional<double> HybridMMKV::getNumber(const std::string& key) {
161
162
 
162
163
  std::optional<std::shared_ptr<ArrayBuffer>> HybridMMKV::getBuffer(const std::string& key) {
163
164
  MMBuffer result;
164
- #ifdef __APPLE__
165
- // iOS: Convert std::string to NSString* for MMKVCore pod compatibility
166
- bool hasValue = instance->getBytes(@(key.c_str()), result);
167
- #else
168
- // Android/other platforms: Use std::string directly (converts to
169
- // std::string_view)
170
165
  bool hasValue = instance->getBytes(key, result);
171
- #endif
172
166
  if (hasValue) {
173
167
  return std::make_shared<ManagedMMBuffer>(std::move(result));
174
168
  } else {
@@ -220,7 +214,7 @@ void HybridMMKV::encrypt(const std::string& key, std::optional<EncryptionType> e
220
214
 
221
215
  void HybridMMKV::decrypt() {
222
216
  bool successful = instance->reKey("");
223
- if (!successful) {
217
+ if (!successful) [[unlikely]] {
224
218
  throw std::runtime_error("Failed to decrypt MMKV instance!");
225
219
  }
226
220
  }
@@ -233,7 +227,7 @@ void HybridMMKV::trim() {
233
227
  Listener HybridMMKV::addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) {
234
228
  // Add listener
235
229
  auto mmkvID = instance->mmapID();
236
- auto listenerID = MMKVValueChangedListenerRegistry::addListener(instance->mmapID(), onValueChanged);
230
+ auto listenerID = MMKVValueChangedListenerRegistry::addListener(mmkvID, onValueChanged);
237
231
 
238
232
  return Listener([=]() {
239
233
  // remove()
@@ -250,14 +244,13 @@ MMKVMode HybridMMKV::getMMKVMode(const Configuration& config) {
250
244
  return ::mmkv::MMKV_SINGLE_PROCESS;
251
245
  case Mode::MULTI_PROCESS:
252
246
  return ::mmkv::MMKV_MULTI_PROCESS;
253
- default:
254
- [[unlikely]] throw std::runtime_error("Invalid MMKV Mode value!");
255
247
  }
248
+ throw std::runtime_error("Invalid MMKV Mode value!");
256
249
  }
257
250
 
258
251
  double HybridMMKV::importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) {
259
252
  auto hybridMMKV = std::dynamic_pointer_cast<HybridMMKV>(other);
260
- if (hybridMMKV == nullptr) {
253
+ if (hybridMMKV == nullptr) [[unlikely]] {
261
254
  throw std::runtime_error("The given `MMKV` instance is not of type `HybridMMKV`!");
262
255
  }
263
256
 
@@ -18,11 +18,7 @@ std::string HybridMMKVFactory::getDefaultMMKVInstanceId() {
18
18
  void HybridMMKVFactory::initializeMMKV(const std::string& rootPath) {
19
19
  Logger::log(LogLevel::Info, TAG, "Initializing MMKV with rootPath=%s", rootPath.c_str());
20
20
 
21
- #ifdef NITRO_DEBUG
22
- MMKVLogLevel logLevel = ::mmkv::MMKVLogDebug;
23
- #else
24
- MMKVLogLevel logLevel = ::mmkv::MMKVLogWarning;
25
- #endif
21
+ MMKVLogLevel logLevel = static_cast<MMKVLogLevel>(MMKV_LOG_LEVEL);
26
22
  MMKV::initializeMMKV(rootPath, logLevel);
27
23
  }
28
24
 
package/cpp/MMKVTypes.hpp CHANGED
@@ -48,3 +48,15 @@ constexpr auto MMKV_READ_ONLY = ::MMKVMode::MMKV_READ_ONLY;
48
48
  */
49
49
 
50
50
  using namespace mmkv;
51
+
52
+ // Default MMKV log level if not configured at build time
53
+ #ifndef MMKV_LOG_LEVEL
54
+ #ifdef NITRO_DEBUG
55
+ #define MMKV_LOG_LEVEL 0
56
+ #else
57
+ #define MMKV_LOG_LEVEL 3
58
+ #endif
59
+ #endif
60
+ #if MMKV_LOG_LEVEL < 0 || MMKV_LOG_LEVEL > 4
61
+ #error "MMKV_LOG_LEVEL must be between 0 (Debug) and 4 (None)"
62
+ #endif
@@ -15,7 +15,7 @@ namespace margelo::nitro::mmkv {
15
15
  /**
16
16
  An ArrayBuffer subclass that manages MMBuffer memory (by ownership).
17
17
  */
18
- class ManagedMMBuffer : public ArrayBuffer {
18
+ class ManagedMMBuffer final : public ArrayBuffer {
19
19
  public:
20
20
  explicit ManagedMMBuffer(MMBuffer&& buffer) : _buffer(std::move(buffer)) {}
21
21
 
@@ -31,7 +31,7 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
31
31
  return this.byteSize;
32
32
  },
33
33
  get byteSize() {
34
- // esimate - assumes UTF8
34
+ // estimate - assumes UTF8
35
35
  return JSON.stringify(getLocalStorage()).length;
36
36
  },
37
37
  isReadOnly: false,
@@ -18,7 +18,7 @@ export function createMockMMKV(config = { id: 'mmkv.default' }) {
18
18
  return this.byteSize;
19
19
  },
20
20
  get byteSize() {
21
- // esimate - assumes UTF8
21
+ // estimate - assumes UTF8
22
22
  return JSON.stringify(storage).length;
23
23
  },
24
24
  isReadOnly: false,
@@ -99,7 +99,7 @@ export interface MMKV extends HybridObject<{
99
99
  recrypt(key: string | undefined): void;
100
100
  /**
101
101
  * Encrypts the data in this MMKV instance with the
102
- * given {@linkcode key} and {@linkcode ecnryptionType}.
102
+ * given {@linkcode key} and {@linkcode encryptionType}.
103
103
  *
104
104
  * If this MMKV instance is already encrypted ({@linkcode isEncrypted}),
105
105
  * it will re-encrypt the data.
@@ -83,6 +83,14 @@ export interface Configuration {
83
83
  * @default false
84
84
  */
85
85
  readOnly?: boolean;
86
+ /**
87
+ * If `true`, MMKV will internally compare a value for equality before writing to
88
+ * disk, and if the new value is equal to what is already stored, it will skip
89
+ * the file write.
90
+ * Treat this as an optional performance optimization.
91
+ * @default false
92
+ */
93
+ compareBeforeSet?: boolean;
86
94
  }
87
95
  export interface MMKVFactory extends HybridObject<{
88
96
  ios: 'c++';
@@ -51,10 +51,11 @@ namespace margelo::nitro::mmkv {
51
51
  std::optional<EncryptionType> encryptionType SWIFT_PRIVATE;
52
52
  std::optional<Mode> mode SWIFT_PRIVATE;
53
53
  std::optional<bool> readOnly SWIFT_PRIVATE;
54
+ std::optional<bool> compareBeforeSet SWIFT_PRIVATE;
54
55
 
55
56
  public:
56
57
  Configuration() = default;
57
- explicit Configuration(std::string id, std::optional<std::string> path, std::optional<std::string> encryptionKey, std::optional<EncryptionType> encryptionType, std::optional<Mode> mode, std::optional<bool> readOnly): id(id), path(path), encryptionKey(encryptionKey), encryptionType(encryptionType), mode(mode), readOnly(readOnly) {}
58
+ explicit Configuration(std::string id, std::optional<std::string> path, std::optional<std::string> encryptionKey, std::optional<EncryptionType> encryptionType, std::optional<Mode> mode, std::optional<bool> readOnly, std::optional<bool> compareBeforeSet): id(id), path(path), encryptionKey(encryptionKey), encryptionType(encryptionType), mode(mode), readOnly(readOnly), compareBeforeSet(compareBeforeSet) {}
58
59
 
59
60
  public:
60
61
  friend bool operator==(const Configuration& lhs, const Configuration& rhs) = default;
@@ -75,7 +76,8 @@ namespace margelo::nitro {
75
76
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "encryptionKey"))),
76
77
  JSIConverter<std::optional<margelo::nitro::mmkv::EncryptionType>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "encryptionType"))),
77
78
  JSIConverter<std::optional<margelo::nitro::mmkv::Mode>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "mode"))),
78
- JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "readOnly")))
79
+ JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "readOnly"))),
80
+ JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compareBeforeSet")))
79
81
  );
80
82
  }
81
83
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::mmkv::Configuration& arg) {
@@ -86,6 +88,7 @@ namespace margelo::nitro {
86
88
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "encryptionType"), JSIConverter<std::optional<margelo::nitro::mmkv::EncryptionType>>::toJSI(runtime, arg.encryptionType));
87
89
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "mode"), JSIConverter<std::optional<margelo::nitro::mmkv::Mode>>::toJSI(runtime, arg.mode));
88
90
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "readOnly"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.readOnly));
91
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "compareBeforeSet"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.compareBeforeSet));
89
92
  return obj;
90
93
  }
91
94
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -102,6 +105,7 @@ namespace margelo::nitro {
102
105
  if (!JSIConverter<std::optional<margelo::nitro::mmkv::EncryptionType>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "encryptionType")))) return false;
103
106
  if (!JSIConverter<std::optional<margelo::nitro::mmkv::Mode>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "mode")))) return false;
104
107
  if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "readOnly")))) return false;
108
+ if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compareBeforeSet")))) return false;
105
109
  return true;
106
110
  }
107
111
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mmkv",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "⚡️ The fastest key/value storage for React Native.",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -46,7 +46,7 @@ export function createMMKV(
46
46
  return this.byteSize
47
47
  },
48
48
  get byteSize(): number {
49
- // esimate - assumes UTF8
49
+ // estimate - assumes UTF8
50
50
  return JSON.stringify(getLocalStorage()).length
51
51
  },
52
52
  isReadOnly: false,
@@ -25,7 +25,7 @@ export function createMockMMKV(
25
25
  return this.byteSize
26
26
  },
27
27
  get byteSize(): number {
28
- // esimate - assumes UTF8
28
+ // estimate - assumes UTF8
29
29
  return JSON.stringify(storage).length
30
30
  },
31
31
  isReadOnly: false,
@@ -98,7 +98,7 @@ export interface MMKV extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
98
98
  recrypt(key: string | undefined): void
99
99
  /**
100
100
  * Encrypts the data in this MMKV instance with the
101
- * given {@linkcode key} and {@linkcode ecnryptionType}.
101
+ * given {@linkcode key} and {@linkcode encryptionType}.
102
102
  *
103
103
  * If this MMKV instance is already encrypted ({@linkcode isEncrypted}),
104
104
  * it will re-encrypt the data.
@@ -86,6 +86,14 @@ export interface Configuration {
86
86
  * @default false
87
87
  */
88
88
  readOnly?: boolean
89
+ /**
90
+ * If `true`, MMKV will internally compare a value for equality before writing to
91
+ * disk, and if the new value is equal to what is already stored, it will skip
92
+ * the file write.
93
+ * Treat this as an optional performance optimization.
94
+ * @default false
95
+ */
96
+ compareBeforeSet?: boolean
89
97
  }
90
98
 
91
99
  export interface MMKVFactory