react-native-mmkv 4.3.0 → 4.3.2

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.
Files changed (45) hide show
  1. package/android/build.gradle +11 -12
  2. package/android/gradle.properties +2 -2
  3. package/android/src/main/java/com/margelo/nitro/mmkv/NitroMmkvPackage.java +2 -2
  4. package/cpp/HybridMMKV.cpp +24 -1
  5. package/cpp/HybridMMKV.hpp +5 -0
  6. package/lib/__tests__/contentChangedListener.test.d.ts +1 -0
  7. package/lib/__tests__/contentChangedListener.test.js +46 -0
  8. package/lib/addContentChangedListener/addContentChangedListener.d.ts +2 -0
  9. package/lib/addContentChangedListener/addContentChangedListener.js +27 -0
  10. package/lib/addContentChangedListener/addContentChangedListener.mock.d.ts +2 -0
  11. package/lib/addContentChangedListener/addContentChangedListener.mock.js +3 -0
  12. package/lib/addContentChangedListener/addContentChangedListener.web.d.ts +2 -0
  13. package/lib/addContentChangedListener/addContentChangedListener.web.js +3 -0
  14. package/lib/createMMKV/createMMKV.js +3 -0
  15. package/lib/createMMKV/createMMKV.web.js +13 -2
  16. package/lib/createMMKV/createMockMMKV.js +3 -0
  17. package/lib/hooks/useMMKV.js +5 -1
  18. package/lib/index.d.ts +1 -1
  19. package/lib/specs/MMKV.nitro.d.ts +5 -0
  20. package/lib/specs/MMKVFactory.nitro.d.ts +12 -0
  21. package/lib/web/createTextDecoder.d.ts +1 -0
  22. package/lib/web/createTextDecoder.js +16 -0
  23. package/nitro.json +12 -3
  24. package/nitrogen/generated/android/NitroMmkvOnLoad.cpp +2 -2
  25. package/nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.hpp +2 -2
  26. package/nitrogen/generated/ios/NitroMmkv+autolinking.rb +2 -0
  27. package/nitrogen/generated/shared/c++/Configuration.hpp +9 -2
  28. package/nitrogen/generated/shared/c++/HybridMMKVSpec.cpp +1 -0
  29. package/nitrogen/generated/shared/c++/HybridMMKVSpec.hpp +1 -0
  30. package/nitrogen/generated/shared/c++/RecoveryStrategy.hpp +76 -0
  31. package/package.json +16 -32
  32. package/src/__tests__/contentChangedListener.test.ts +61 -0
  33. package/src/addContentChangedListener/addContentChangedListener.mock.ts +5 -0
  34. package/src/addContentChangedListener/addContentChangedListener.ts +36 -0
  35. package/src/addContentChangedListener/addContentChangedListener.web.ts +5 -0
  36. package/src/createMMKV/createMMKV.ts +3 -0
  37. package/src/createMMKV/createMMKV.web.ts +12 -2
  38. package/src/createMMKV/createMockMMKV.ts +3 -0
  39. package/src/hooks/useMMKV.ts +5 -1
  40. package/src/index.ts +5 -1
  41. package/src/specs/MMKV.nitro.ts +5 -0
  42. package/src/specs/MMKVFactory.nitro.ts +17 -2
  43. package/src/specs/MMKVPlatformContext.nitro.ts +4 -2
  44. package/src/web/createTextDecoder.ts +15 -0
  45. package/src/web/createTextEncoder.ts +1 -1
@@ -5,7 +5,7 @@ buildscript {
5
5
  }
6
6
 
7
7
  dependencies {
8
- classpath "com.android.tools.build:gradle:9.1.0"
8
+ classpath "com.android.tools.build:gradle:9.2.1"
9
9
  }
10
10
  }
11
11
 
@@ -36,14 +36,14 @@ def getExtOrIntegerDefault(name) {
36
36
  }
37
37
 
38
38
  android {
39
- namespace "com.margelo.nitro.mmkv"
39
+ namespace = "com.margelo.nitro.mmkv"
40
40
 
41
- ndkVersion getExtOrDefault("ndkVersion")
42
- compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
41
+ ndkVersion = getExtOrDefault("ndkVersion")
42
+ compileSdkVersion = getExtOrIntegerDefault("compileSdkVersion")
43
43
 
44
44
  defaultConfig {
45
- minSdkVersion getExtOrIntegerDefault("minSdkVersion")
46
- targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
45
+ minSdkVersion = getExtOrIntegerDefault("minSdkVersion")
46
+ targetSdkVersion = getExtOrIntegerDefault("targetSdkVersion")
47
47
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
48
48
 
49
49
  externalNativeBuild {
@@ -71,7 +71,7 @@ android {
71
71
 
72
72
  externalNativeBuild {
73
73
  cmake {
74
- path "CMakeLists.txt"
74
+ path = file("CMakeLists.txt")
75
75
  }
76
76
  }
77
77
 
@@ -98,13 +98,13 @@ android {
98
98
  }
99
99
 
100
100
  buildFeatures {
101
- buildConfig true
102
- prefab true
101
+ buildConfig = true
102
+ prefab = true
103
103
  }
104
104
 
105
105
  buildTypes {
106
106
  release {
107
- minifyEnabled false
107
+ minifyEnabled = false
108
108
  }
109
109
  }
110
110
 
@@ -145,6 +145,5 @@ dependencies {
145
145
  implementation project(":react-native-nitro-modules")
146
146
 
147
147
  // Add a dependency on mmkv core (this ships a C++ prefab)
148
- implementation "io.github.zhongwuzw:mmkv:2.4.0"
148
+ implementation "io.github.zhongwuzw:mmkv:2.4.1"
149
149
  }
150
-
@@ -1,5 +1,5 @@
1
- NitroMmkv_kotlinVersion=2.1.20
1
+ NitroMmkv_kotlinVersion=2.3.21
2
2
  NitroMmkv_minSdkVersion=23
3
3
  NitroMmkv_targetSdkVersion=36
4
4
  NitroMmkv_compileSdkVersion=36
5
- NitroMmkv_ndkVersion=27.1.12297006
5
+ NitroMmkv_ndkVersion=29.0.14206865
@@ -7,13 +7,13 @@ import androidx.annotation.Nullable;
7
7
  import com.facebook.react.bridge.NativeModule;
8
8
  import com.facebook.react.bridge.ReactApplicationContext;
9
9
  import com.facebook.react.module.model.ReactModuleInfoProvider;
10
- import com.facebook.react.TurboReactPackage;
10
+ import com.facebook.react.BaseReactPackage;
11
11
  import com.margelo.nitro.core.HybridObject;
12
12
 
13
13
  import java.util.HashMap;
14
14
  import java.util.function.Supplier;
15
15
 
16
- public class NitroMmkvPackage extends TurboReactPackage {
16
+ public class NitroMmkvPackage extends BaseReactPackage {
17
17
  @Nullable
18
18
  @Override
19
19
  public NativeModule getModule(String name, ReactApplicationContext reactContext) {
@@ -29,7 +29,8 @@ HybridMMKV::HybridMMKV(const Configuration& config) : HybridObject(TAG) {
29
29
  .aes256 = useAes256Encryption,
30
30
  .cryptKey = encryptionKeyPtr,
31
31
  .rootPath = rootPathPtr,
32
- .enableCompareBeforeSet = compareBeforeSet};
32
+ .enableCompareBeforeSet = compareBeforeSet,
33
+ .recover = getRecoveryStrategy(config)};
33
34
 
34
35
  bool hasEncryptionKey = encryptionKey.size() > 0;
35
36
  Logger::log(LogLevel::Info, TAG, "Creating MMKV instance \"%s\"... (Path: %s, Encrypted: %s)", config.id.c_str(), rootPath.c_str(),
@@ -82,6 +83,10 @@ double HybridMMKV::getByteSize() {
82
83
  return instance->actualSize();
83
84
  }
84
85
 
86
+ size_t HybridMMKV::getExternalMemorySize() noexcept {
87
+ return instance != nullptr ? instance->actualSize() : 0;
88
+ }
89
+
85
90
  bool HybridMMKV::getIsReadOnly() {
86
91
  return instance->isReadOnly();
87
92
  }
@@ -224,6 +229,10 @@ void HybridMMKV::trim() {
224
229
  instance->clearMemoryCache();
225
230
  }
226
231
 
232
+ void HybridMMKV::checkContentChanged() {
233
+ instance->checkContentChanged();
234
+ }
235
+
227
236
  Listener HybridMMKV::addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) {
228
237
  // Add listener
229
238
  auto mmkvID = instance->mmapID();
@@ -248,6 +257,20 @@ MMKVMode HybridMMKV::getMMKVMode(const Configuration& config) {
248
257
  throw std::runtime_error("Invalid MMKV Mode value!");
249
258
  }
250
259
 
260
+ std::optional<MMKVRecoverStrategic> HybridMMKV::getRecoveryStrategy(const Configuration& config) {
261
+ if (!config.recoveryStrategy.has_value()) {
262
+ return std::nullopt;
263
+ }
264
+
265
+ switch (config.recoveryStrategy.value()) {
266
+ case RecoveryStrategy::DISCARD_ON_ERROR:
267
+ return MMKVRecoverStrategic::OnErrorDiscard;
268
+ case RecoveryStrategy::RECOVER_ON_ERROR:
269
+ return MMKVRecoverStrategic::OnErrorRecover;
270
+ }
271
+ throw std::runtime_error("Invalid MMKV RecoveryStrategy value!");
272
+ }
273
+
251
274
  double HybridMMKV::importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) {
252
275
  auto hybridMMKV = std::dynamic_pointer_cast<HybridMMKV>(other);
253
276
  if (hybridMMKV == nullptr) [[unlikely]] {
@@ -41,11 +41,16 @@ public:
41
41
  void encrypt(const std::string& key, std::optional<EncryptionType> encryptionType) override;
42
42
  void decrypt() override;
43
43
  void trim() override;
44
+ void checkContentChanged() override;
44
45
  Listener addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) override;
45
46
  double importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) override;
46
47
 
48
+ protected:
49
+ size_t getExternalMemorySize() noexcept override;
50
+
47
51
  private:
48
52
  static MMKVMode getMMKVMode(const Configuration& config);
53
+ static std::optional<MMKVRecoverStrategic> getRecoveryStrategy(const Configuration& config);
49
54
 
50
55
  private:
51
56
  MMKV* instance;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { AppState } from 'react-native';
2
+ import { createMockMMKV } from '../createMMKV/createMockMMKV';
3
+ import { addContentChangedListener } from '../addContentChangedListener/addContentChangedListener';
4
+ function mockAppStateChangeListener() {
5
+ const listeners = new Set();
6
+ jest
7
+ .spyOn(AppState, 'addEventListener')
8
+ .mockImplementation((type, listener) => {
9
+ if (type === 'change') {
10
+ listeners.add(listener);
11
+ }
12
+ return {
13
+ remove: () => {
14
+ listeners.delete(listener);
15
+ },
16
+ };
17
+ });
18
+ return {
19
+ emit: (state) => {
20
+ listeners.forEach((listener) => listener(state));
21
+ },
22
+ };
23
+ }
24
+ afterEach(() => {
25
+ jest.clearAllMocks();
26
+ jest.restoreAllMocks();
27
+ });
28
+ test('content changed listener checks content when app becomes active', () => {
29
+ const appState = mockAppStateChangeListener();
30
+ const storage = createMockMMKV({ id: 'content-changed-listener-test' });
31
+ const checkContentChanged = jest.spyOn(storage, 'checkContentChanged');
32
+ addContentChangedListener(storage);
33
+ appState.emit('background');
34
+ expect(checkContentChanged).not.toHaveBeenCalled();
35
+ appState.emit('active');
36
+ expect(checkContentChanged).toHaveBeenCalledTimes(1);
37
+ });
38
+ test('content changed listener registers once per instance', () => {
39
+ mockAppStateChangeListener();
40
+ const storage = createMockMMKV({
41
+ id: 'content-changed-listener-once-test',
42
+ });
43
+ addContentChangedListener(storage);
44
+ addContentChangedListener(storage);
45
+ expect(AppState.addEventListener).toHaveBeenCalledTimes(1);
46
+ });
@@ -0,0 +1,2 @@
1
+ import type { MMKV } from '../specs/MMKV.nitro';
2
+ export declare function addContentChangedListener(mmkv: MMKV): void;
@@ -0,0 +1,27 @@
1
+ import { AppState } from 'react-native';
2
+ const registeredInstances = new WeakSet();
3
+ export function addContentChangedListener(mmkv) {
4
+ if (registeredInstances.has(mmkv)) {
5
+ return;
6
+ }
7
+ registeredInstances.add(mmkv);
8
+ if (global.WeakRef != null && global.FinalizationRegistry != null) {
9
+ const weakMmkv = new WeakRef(mmkv);
10
+ const listener = AppState.addEventListener('change', (state) => {
11
+ if (state === 'active') {
12
+ weakMmkv.deref()?.checkContentChanged();
13
+ }
14
+ });
15
+ const finalization = new FinalizationRegistry((l) => {
16
+ l.remove();
17
+ });
18
+ finalization.register(mmkv, listener);
19
+ }
20
+ else {
21
+ AppState.addEventListener('change', (state) => {
22
+ if (state === 'active') {
23
+ mmkv.checkContentChanged();
24
+ }
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,2 @@
1
+ import type { MMKV } from '../specs/MMKV.nitro';
2
+ export declare function addContentChangedListener(_mmkv: MMKV): void;
@@ -0,0 +1,3 @@
1
+ export function addContentChangedListener(_mmkv) {
2
+ // no-op in mocked environments
3
+ }
@@ -0,0 +1,2 @@
1
+ import type { MMKV } from '../specs/MMKV.nitro';
2
+ export declare function addContentChangedListener(_mmkv: MMKV): void;
@@ -0,0 +1,3 @@
1
+ export function addContentChangedListener(_mmkv) {
2
+ // no-op on Web
3
+ }
@@ -1,5 +1,6 @@
1
1
  import { Platform } from 'react-native';
2
2
  import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener';
3
+ import { addContentChangedListener } from '../addContentChangedListener/addContentChangedListener';
3
4
  import { isTest } from '../isTest';
4
5
  import { createMockMMKV } from './createMockMMKV';
5
6
  import { getMMKVFactory, getPlatformContext } from '../getMMKVFactory';
@@ -26,5 +27,7 @@ export function createMMKV(configuration) {
26
27
  const mmkv = factory.createMMKV(config);
27
28
  // Add a hook that trims the storage when we get a memory warning
28
29
  addMemoryWarningListener(mmkv);
30
+ // Check for updates from app extensions/App Clips/background services when the app becomes active
31
+ addContentChangedListener(mmkv);
29
32
  return mmkv;
30
33
  }
@@ -1,3 +1,4 @@
1
+ import { createTextDecoder } from '../web/createTextDecoder';
1
2
  import { createTextEncoder } from '../web/createTextEncoder';
2
3
  import { getLocalStorage, LOCAL_STORAGE_KEY_WILDCARD, } from '../web/getLocalStorage';
3
4
  export function createMMKV(config = { id: 'mmkv.default' }) {
@@ -7,6 +8,7 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
7
8
  if (config.path != null) {
8
9
  throw new Error("MMKV: 'path' is not supported on Web!");
9
10
  }
11
+ const textDecoder = createTextDecoder();
10
12
  const textEncoder = createTextEncoder();
11
13
  const listeners = new Set();
12
14
  if (config.id.includes(LOCAL_STORAGE_KEY_WILDCARD)) {
@@ -25,7 +27,8 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
25
27
  return {
26
28
  id: config.id,
27
29
  get length() {
28
- return getLocalStorage().length;
30
+ const keys = this.getAllKeys();
31
+ return keys.length;
29
32
  },
30
33
  get size() {
31
34
  return this.byteSize;
@@ -58,7 +61,12 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
58
61
  const storage = getLocalStorage();
59
62
  if (key === '')
60
63
  throw new Error('Cannot set a value for an empty key!');
61
- storage.setItem(prefixedKey(key), value.toString());
64
+ if (value instanceof ArrayBuffer) {
65
+ storage.setItem(prefixedKey(key), textDecoder.decode(value));
66
+ }
67
+ else {
68
+ storage.setItem(prefixedKey(key), String(value));
69
+ }
62
70
  callListeners(key);
63
71
  },
64
72
  getString: (key) => {
@@ -109,6 +117,9 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
109
117
  trim: () => {
110
118
  // no-op
111
119
  },
120
+ checkContentChanged: () => {
121
+ // no-op
122
+ },
112
123
  dispose: () => { },
113
124
  equals: () => false,
114
125
  name: 'MMKV',
@@ -74,6 +74,9 @@ export function createMockMMKV(config = { id: 'mmkv.default' }) {
74
74
  trim: () => {
75
75
  // no-op
76
76
  },
77
+ checkContentChanged: () => {
78
+ // no-op
79
+ },
77
80
  name: 'MMKV',
78
81
  dispose: () => { },
79
82
  equals: () => {
@@ -5,9 +5,13 @@ function isConfigurationEqual(left, right) {
5
5
  if (left == null || right == null)
6
6
  return left == null && right == null;
7
7
  return (left.encryptionKey === right.encryptionKey &&
8
+ left.encryptionType === right.encryptionType &&
8
9
  left.id === right.id &&
9
10
  left.path === right.path &&
10
- left.mode === right.mode);
11
+ left.mode === right.mode &&
12
+ left.readOnly === right.readOnly &&
13
+ left.compareBeforeSet === right.compareBeforeSet &&
14
+ left.recoveryStrategy === right.recoveryStrategy);
11
15
  }
12
16
  export function useMMKV(configuration) {
13
17
  const instance = useRef(undefined);
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { MMKV } from './specs/MMKV.nitro';
2
- export type { Configuration, Mode } from './specs/MMKVFactory.nitro';
2
+ export type { Configuration, Mode, RecoveryStrategy, } from './specs/MMKVFactory.nitro';
3
3
  export { createMMKV } from './createMMKV/createMMKV';
4
4
  export { existsMMKV } from './existsMMKV/existsMMKV';
5
5
  export { deleteMMKV } from './deleteMMKV/deleteMMKV';
@@ -134,6 +134,11 @@ export interface MMKV extends HybridObject<{
134
134
  * In most applications, this is not needed at all.
135
135
  */
136
136
  trim(): void;
137
+ /**
138
+ * Checks whether the underlying storage file changed outside this process,
139
+ * and reloads this instance's data if needed.
140
+ */
141
+ checkContentChanged(): void;
137
142
  /**
138
143
  * Adds a value changed listener. The Listener will be called whenever any value
139
144
  * in this storage instance changes (set or delete).
@@ -12,6 +12,12 @@ export type Mode = 'single-process' | 'multi-process';
12
12
  * - `AES-256`: Uses AES-256 encryption for enhanced security.
13
13
  */
14
14
  export type EncryptionType = 'AES-128' | 'AES-256';
15
+ /**
16
+ * Configures how MMKV should handle recoverable storage errors.
17
+ * - `discard-on-error`: Discards data when MMKV detects a CRC or file-length error.
18
+ * - `recover-on-error`: Attempts to recover data when MMKV detects a CRC or file-length error.
19
+ */
20
+ export type RecoveryStrategy = 'discard-on-error' | 'recover-on-error';
15
21
  /**
16
22
  * Used for configuration of a single MMKV instance.
17
23
  */
@@ -91,6 +97,12 @@ export interface Configuration {
91
97
  * @default false
92
98
  */
93
99
  compareBeforeSet?: boolean;
100
+ /**
101
+ * Configure how MMKV should handle recoverable storage errors.
102
+ *
103
+ * @default undefined
104
+ */
105
+ recoveryStrategy?: RecoveryStrategy;
94
106
  }
95
107
  export interface MMKVFactory extends HybridObject<{
96
108
  ios: 'c++';
@@ -0,0 +1 @@
1
+ export declare function createTextDecoder(): TextDecoder;
@@ -0,0 +1,16 @@
1
+ export function createTextDecoder() {
2
+ const g = global ?? globalThis ?? window;
3
+ if (g.TextDecoder != null) {
4
+ return new g.TextDecoder();
5
+ }
6
+ else {
7
+ return {
8
+ decode: () => {
9
+ throw new Error('TextDecoder is not supported in this environment!');
10
+ },
11
+ encoding: 'utf-8',
12
+ fatal: false,
13
+ ignoreBOM: false,
14
+ };
15
+ }
16
+ }
package/nitro.json CHANGED
@@ -14,11 +14,20 @@
14
14
  },
15
15
  "autolinking": {
16
16
  "MMKVFactory": {
17
- "cpp": "HybridMMKVFactory"
17
+ "all": {
18
+ "language": "c++",
19
+ "implementationClassName": "HybridMMKVFactory"
20
+ }
18
21
  },
19
22
  "MMKVPlatformContext": {
20
- "swift": "HybridMMKVPlatformContext",
21
- "kotlin": "HybridMMKVPlatformContext"
23
+ "ios": {
24
+ "language": "swift",
25
+ "implementationClassName": "HybridMMKVPlatformContext"
26
+ },
27
+ "android": {
28
+ "language": "kotlin",
29
+ "implementationClassName": "HybridMMKVPlatformContext"
30
+ }
22
31
  }
23
32
  },
24
33
  "ignorePaths": [
@@ -28,9 +28,9 @@ int initialize(JavaVM* vm) {
28
28
  }
29
29
 
30
30
  struct JHybridMMKVPlatformContextSpecImpl: public jni::JavaClass<JHybridMMKVPlatformContextSpecImpl, JHybridMMKVPlatformContextSpec::JavaPart> {
31
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/mmkv/HybridMMKVPlatformContext;";
31
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/mmkv/HybridMMKVPlatformContext;";
32
32
  static std::shared_ptr<JHybridMMKVPlatformContextSpec> create() {
33
- static auto constructorFn = javaClassStatic()->getConstructor<JHybridMMKVPlatformContextSpecImpl::javaobject()>();
33
+ static const auto constructorFn = javaClassStatic()->getConstructor<JHybridMMKVPlatformContextSpecImpl::javaobject()>();
34
34
  jni::local_ref<JHybridMMKVPlatformContextSpec::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
35
35
  return javaPart->getJHybridMMKVPlatformContextSpec();
36
36
  }
@@ -21,11 +21,11 @@ namespace margelo::nitro::mmkv {
21
21
  class JHybridMMKVPlatformContextSpec: public virtual HybridMMKVPlatformContextSpec, public virtual JHybridObject {
22
22
  public:
23
23
  struct JavaPart: public jni::JavaClass<JavaPart, JHybridObject::JavaPart> {
24
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec;";
24
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec;";
25
25
  std::shared_ptr<JHybridMMKVPlatformContextSpec> getJHybridMMKVPlatformContextSpec();
26
26
  };
27
27
  struct CxxPart: public jni::HybridClass<CxxPart, JHybridObject::CxxPart> {
28
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec$CxxPart;";
28
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec$CxxPart;";
29
29
  static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
30
30
  static void registerNatives();
31
31
  using HybridBase::HybridBase;
@@ -56,5 +56,7 @@ def add_nitrogen_files(spec)
56
56
  "SWIFT_OBJC_INTEROP_MODE" => "objcxx",
57
57
  # Enables stricter modular headers
58
58
  "DEFINES_MODULE" => "YES",
59
+ # Disable auto-generated ObjC header for Swift (Static linkage on Xcode 26.4 breaks here)
60
+ "SWIFT_INSTALL_OBJC_HEADER" => "NO",
59
61
  })
60
62
  end
@@ -32,11 +32,14 @@
32
32
  namespace margelo::nitro::mmkv { enum class EncryptionType; }
33
33
  // Forward declaration of `Mode` to properly resolve imports.
34
34
  namespace margelo::nitro::mmkv { enum class Mode; }
35
+ // Forward declaration of `RecoveryStrategy` to properly resolve imports.
36
+ namespace margelo::nitro::mmkv { enum class RecoveryStrategy; }
35
37
 
36
38
  #include <string>
37
39
  #include <optional>
38
40
  #include "EncryptionType.hpp"
39
41
  #include "Mode.hpp"
42
+ #include "RecoveryStrategy.hpp"
40
43
 
41
44
  namespace margelo::nitro::mmkv {
42
45
 
@@ -52,10 +55,11 @@ namespace margelo::nitro::mmkv {
52
55
  std::optional<Mode> mode SWIFT_PRIVATE;
53
56
  std::optional<bool> readOnly SWIFT_PRIVATE;
54
57
  std::optional<bool> compareBeforeSet SWIFT_PRIVATE;
58
+ std::optional<RecoveryStrategy> recoveryStrategy SWIFT_PRIVATE;
55
59
 
56
60
  public:
57
61
  Configuration() = default;
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) {}
62
+ 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, std::optional<RecoveryStrategy> recoveryStrategy): id(id), path(path), encryptionKey(encryptionKey), encryptionType(encryptionType), mode(mode), readOnly(readOnly), compareBeforeSet(compareBeforeSet), recoveryStrategy(recoveryStrategy) {}
59
63
 
60
64
  public:
61
65
  friend bool operator==(const Configuration& lhs, const Configuration& rhs) = default;
@@ -77,7 +81,8 @@ namespace margelo::nitro {
77
81
  JSIConverter<std::optional<margelo::nitro::mmkv::EncryptionType>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "encryptionType"))),
78
82
  JSIConverter<std::optional<margelo::nitro::mmkv::Mode>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "mode"))),
79
83
  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")))
84
+ JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compareBeforeSet"))),
85
+ JSIConverter<std::optional<margelo::nitro::mmkv::RecoveryStrategy>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "recoveryStrategy")))
81
86
  );
82
87
  }
83
88
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::mmkv::Configuration& arg) {
@@ -89,6 +94,7 @@ namespace margelo::nitro {
89
94
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "mode"), JSIConverter<std::optional<margelo::nitro::mmkv::Mode>>::toJSI(runtime, arg.mode));
90
95
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "readOnly"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.readOnly));
91
96
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "compareBeforeSet"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.compareBeforeSet));
97
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "recoveryStrategy"), JSIConverter<std::optional<margelo::nitro::mmkv::RecoveryStrategy>>::toJSI(runtime, arg.recoveryStrategy));
92
98
  return obj;
93
99
  }
94
100
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -106,6 +112,7 @@ namespace margelo::nitro {
106
112
  if (!JSIConverter<std::optional<margelo::nitro::mmkv::Mode>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "mode")))) return false;
107
113
  if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "readOnly")))) return false;
108
114
  if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compareBeforeSet")))) return false;
115
+ if (!JSIConverter<std::optional<margelo::nitro::mmkv::RecoveryStrategy>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "recoveryStrategy")))) return false;
109
116
  return true;
110
117
  }
111
118
  };
@@ -33,6 +33,7 @@ namespace margelo::nitro::mmkv {
33
33
  prototype.registerHybridMethod("encrypt", &HybridMMKVSpec::encrypt);
34
34
  prototype.registerHybridMethod("decrypt", &HybridMMKVSpec::decrypt);
35
35
  prototype.registerHybridMethod("trim", &HybridMMKVSpec::trim);
36
+ prototype.registerHybridMethod("checkContentChanged", &HybridMMKVSpec::checkContentChanged);
36
37
  prototype.registerHybridMethod("addOnValueChangedListener", &HybridMMKVSpec::addOnValueChangedListener);
37
38
  prototype.registerHybridMethod("importAllFrom", &HybridMMKVSpec::importAllFrom);
38
39
  });
@@ -80,6 +80,7 @@ namespace margelo::nitro::mmkv {
80
80
  virtual void encrypt(const std::string& key, std::optional<EncryptionType> encryptionType) = 0;
81
81
  virtual void decrypt() = 0;
82
82
  virtual void trim() = 0;
83
+ virtual void checkContentChanged() = 0;
83
84
  virtual Listener addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) = 0;
84
85
  virtual double importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) = 0;
85
86
 
@@ -0,0 +1,76 @@
1
+ ///
2
+ /// RecoveryStrategy.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/NitroHash.hpp>)
11
+ #include <NitroModules/NitroHash.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+ #if __has_include(<NitroModules/JSIConverter.hpp>)
16
+ #include <NitroModules/JSIConverter.hpp>
17
+ #else
18
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19
+ #endif
20
+ #if __has_include(<NitroModules/NitroDefines.hpp>)
21
+ #include <NitroModules/NitroDefines.hpp>
22
+ #else
23
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
+ #endif
25
+
26
+ namespace margelo::nitro::mmkv {
27
+
28
+ /**
29
+ * An enum which can be represented as a JavaScript union (RecoveryStrategy).
30
+ */
31
+ enum class RecoveryStrategy {
32
+ DISCARD_ON_ERROR SWIFT_NAME(discardOnError) = 0,
33
+ RECOVER_ON_ERROR SWIFT_NAME(recoverOnError) = 1,
34
+ } CLOSED_ENUM;
35
+
36
+ } // namespace margelo::nitro::mmkv
37
+
38
+ namespace margelo::nitro {
39
+
40
+ // C++ RecoveryStrategy <> JS RecoveryStrategy (union)
41
+ template <>
42
+ struct JSIConverter<margelo::nitro::mmkv::RecoveryStrategy> final {
43
+ static inline margelo::nitro::mmkv::RecoveryStrategy fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
44
+ std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
45
+ switch (hashString(unionValue.c_str(), unionValue.size())) {
46
+ case hashString("discard-on-error"): return margelo::nitro::mmkv::RecoveryStrategy::DISCARD_ON_ERROR;
47
+ case hashString("recover-on-error"): return margelo::nitro::mmkv::RecoveryStrategy::RECOVER_ON_ERROR;
48
+ default: [[unlikely]]
49
+ throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum RecoveryStrategy - invalid value!");
50
+ }
51
+ }
52
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::mmkv::RecoveryStrategy arg) {
53
+ switch (arg) {
54
+ case margelo::nitro::mmkv::RecoveryStrategy::DISCARD_ON_ERROR: return JSIConverter<std::string>::toJSI(runtime, "discard-on-error");
55
+ case margelo::nitro::mmkv::RecoveryStrategy::RECOVER_ON_ERROR: return JSIConverter<std::string>::toJSI(runtime, "recover-on-error");
56
+ default: [[unlikely]]
57
+ throw std::invalid_argument("Cannot convert RecoveryStrategy to JS - invalid value: "
58
+ + std::to_string(static_cast<int>(arg)) + "!");
59
+ }
60
+ }
61
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
62
+ if (!value.isString()) {
63
+ return false;
64
+ }
65
+ std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
66
+ switch (hashString(unionValue.c_str(), unionValue.size())) {
67
+ case hashString("discard-on-error"):
68
+ case hashString("recover-on-error"):
69
+ return true;
70
+ default:
71
+ return false;
72
+ }
73
+ }
74
+ };
75
+
76
+ } // namespace margelo::nitro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mmkv",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
4
4
  "description": "⚡️ The fastest key/value storage for React Native.",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -33,8 +33,8 @@
33
33
  "scripts": {
34
34
  "typecheck": "tsc --noEmit",
35
35
  "clean": "rm -rf android/build node_modules/**/android/build lib",
36
- "lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
37
- "lint-ci": "eslint \"**/*.{js,ts,tsx}\" -f @jamesacarr/github-actions",
36
+ "lint": "eslint \"**/*.{js,ts,tsx}\" --fix && prettier --write \"**/*.{js,ts,tsx}\"",
37
+ "lint-ci": "eslint \"**/*.{js,ts,tsx}\" --max-warnings=0 -f @jamesacarr/github-actions && prettier --check \"**/*.{js,ts,tsx}\"",
38
38
  "typescript": "tsc",
39
39
  "specs": "tsc && nitrogen --logLevel=\"debug\"",
40
40
  "build": "tsc --noEmit false",
@@ -59,20 +59,19 @@
59
59
  "registry": "https://registry.npmjs.org/"
60
60
  },
61
61
  "devDependencies": {
62
- "@expo/config-plugins": "^10.1.2",
63
- "@react-native/eslint-config": "0.82.0",
62
+ "@react-native/eslint-config": "0.85.3",
63
+ "@react-native/jest-preset": "0.85.3",
64
64
  "@testing-library/react-native": "^13.3.1",
65
- "@types/jest": "^29.5.12",
66
- "@types/react": "^19.0.6",
67
- "eslint": "^8.57.0",
68
- "eslint-config-prettier": "^9.1.0",
69
- "eslint-plugin-prettier": "^5.2.1",
70
- "nitrogen": "0.35.0",
71
- "prettier": "^3.3.3",
72
- "react": "19.1.1",
73
- "react-native": "0.82.0",
74
- "react-native-nitro-modules": "0.35.0",
75
- "typescript": "^5.8.3"
65
+ "@types/jest": "^29.5.14",
66
+ "@types/react": "^19.2.15",
67
+ "eslint": "^8.57.1",
68
+ "eslint-config-prettier": "^10.1.8",
69
+ "nitrogen": "0.35.9",
70
+ "prettier": "^3.8.3",
71
+ "react": "19.2.6",
72
+ "react-native": "0.85.3",
73
+ "react-native-nitro-modules": "0.35.9",
74
+ "typescript": "^6.0.3"
76
75
  },
77
76
  "peerDependencies": {
78
77
  "react": "*",
@@ -84,22 +83,7 @@
84
83
  "extends": [
85
84
  "@react-native",
86
85
  "prettier"
87
- ],
88
- "plugins": [
89
- "prettier"
90
- ],
91
- "rules": {
92
- "prettier/prettier": [
93
- "warn",
94
- {
95
- "quoteProps": "consistent",
96
- "singleQuote": true,
97
- "tabWidth": 2,
98
- "trailingComma": "es5",
99
- "useTabs": false
100
- }
101
- ]
102
- }
86
+ ]
103
87
  },
104
88
  "eslintIgnore": [
105
89
  "node_modules/",
@@ -0,0 +1,61 @@
1
+ import { AppState } from 'react-native'
2
+ import type { AppStateStatus, NativeEventSubscription } from 'react-native'
3
+ import { createMockMMKV } from '../createMMKV/createMockMMKV'
4
+ import { addContentChangedListener } from '../addContentChangedListener/addContentChangedListener'
5
+
6
+ function mockAppStateChangeListener(): {
7
+ emit: (state: AppStateStatus) => void
8
+ } {
9
+ const listeners = new Set<(state: AppStateStatus) => void>()
10
+
11
+ jest
12
+ .spyOn(AppState, 'addEventListener')
13
+ .mockImplementation((type, listener): NativeEventSubscription => {
14
+ if (type === 'change') {
15
+ listeners.add(listener as (state: AppStateStatus) => void)
16
+ }
17
+
18
+ return {
19
+ remove: () => {
20
+ listeners.delete(listener as (state: AppStateStatus) => void)
21
+ },
22
+ } as NativeEventSubscription
23
+ })
24
+
25
+ return {
26
+ emit: (state) => {
27
+ listeners.forEach((listener) => listener(state))
28
+ },
29
+ }
30
+ }
31
+
32
+ afterEach(() => {
33
+ jest.clearAllMocks()
34
+ jest.restoreAllMocks()
35
+ })
36
+
37
+ test('content changed listener checks content when app becomes active', () => {
38
+ const appState = mockAppStateChangeListener()
39
+ const storage = createMockMMKV({ id: 'content-changed-listener-test' })
40
+ const checkContentChanged = jest.spyOn(storage, 'checkContentChanged')
41
+
42
+ addContentChangedListener(storage)
43
+
44
+ appState.emit('background')
45
+ expect(checkContentChanged).not.toHaveBeenCalled()
46
+
47
+ appState.emit('active')
48
+ expect(checkContentChanged).toHaveBeenCalledTimes(1)
49
+ })
50
+
51
+ test('content changed listener registers once per instance', () => {
52
+ mockAppStateChangeListener()
53
+ const storage = createMockMMKV({
54
+ id: 'content-changed-listener-once-test',
55
+ })
56
+
57
+ addContentChangedListener(storage)
58
+ addContentChangedListener(storage)
59
+
60
+ expect(AppState.addEventListener).toHaveBeenCalledTimes(1)
61
+ })
@@ -0,0 +1,5 @@
1
+ import type { MMKV } from '../specs/MMKV.nitro'
2
+
3
+ export function addContentChangedListener(_mmkv: MMKV): void {
4
+ // no-op in mocked environments
5
+ }
@@ -0,0 +1,36 @@
1
+ import { AppState } from 'react-native'
2
+ import type { AppStateStatus, NativeEventSubscription } from 'react-native'
3
+ import type { MMKV } from '../specs/MMKV.nitro'
4
+
5
+ const registeredInstances = new WeakSet<MMKV>()
6
+
7
+ export function addContentChangedListener(mmkv: MMKV): void {
8
+ if (registeredInstances.has(mmkv)) {
9
+ return
10
+ }
11
+ registeredInstances.add(mmkv)
12
+
13
+ if (global.WeakRef != null && global.FinalizationRegistry != null) {
14
+ const weakMmkv = new WeakRef(mmkv)
15
+ const listener = AppState.addEventListener(
16
+ 'change',
17
+ (state: AppStateStatus) => {
18
+ if (state === 'active') {
19
+ weakMmkv.deref()?.checkContentChanged()
20
+ }
21
+ }
22
+ )
23
+ const finalization = new FinalizationRegistry(
24
+ (l: NativeEventSubscription) => {
25
+ l.remove()
26
+ }
27
+ )
28
+ finalization.register(mmkv, listener)
29
+ } else {
30
+ AppState.addEventListener('change', (state: AppStateStatus) => {
31
+ if (state === 'active') {
32
+ mmkv.checkContentChanged()
33
+ }
34
+ })
35
+ }
36
+ }
@@ -0,0 +1,5 @@
1
+ import type { MMKV } from '../specs/MMKV.nitro'
2
+
3
+ export function addContentChangedListener(_mmkv: MMKV): void {
4
+ // no-op on Web
5
+ }
@@ -2,6 +2,7 @@ import type { MMKV } from '../specs/MMKV.nitro'
2
2
  import type { Configuration } from '../specs/MMKVFactory.nitro'
3
3
  import { Platform } from 'react-native'
4
4
  import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener'
5
+ import { addContentChangedListener } from '../addContentChangedListener/addContentChangedListener'
5
6
  import { isTest } from '../isTest'
6
7
  import { createMockMMKV } from './createMockMMKV'
7
8
  import { getMMKVFactory, getPlatformContext } from '../getMMKVFactory'
@@ -33,5 +34,7 @@ export function createMMKV(configuration?: Configuration): MMKV {
33
34
  const mmkv = factory.createMMKV(config)
34
35
  // Add a hook that trims the storage when we get a memory warning
35
36
  addMemoryWarningListener(mmkv)
37
+ // Check for updates from app extensions/App Clips/background services when the app becomes active
38
+ addContentChangedListener(mmkv)
36
39
  return mmkv
37
40
  }
@@ -1,5 +1,6 @@
1
1
  import type { MMKV } from '../specs/MMKV.nitro'
2
2
  import type { Configuration } from '../specs/MMKVFactory.nitro'
3
+ import { createTextDecoder } from '../web/createTextDecoder'
3
4
  import { createTextEncoder } from '../web/createTextEncoder'
4
5
  import {
5
6
  getLocalStorage,
@@ -16,6 +17,7 @@ export function createMMKV(
16
17
  throw new Error("MMKV: 'path' is not supported on Web!")
17
18
  }
18
19
 
20
+ const textDecoder = createTextDecoder()
19
21
  const textEncoder = createTextEncoder()
20
22
  const listeners = new Set<(key: string) => void>()
21
23
 
@@ -40,7 +42,8 @@ export function createMMKV(
40
42
  return {
41
43
  id: config.id,
42
44
  get length(): number {
43
- return getLocalStorage().length
45
+ const keys = this.getAllKeys()
46
+ return keys.length
44
47
  },
45
48
  get size(): number {
46
49
  return this.byteSize
@@ -71,7 +74,11 @@ export function createMMKV(
71
74
  set: (key, value) => {
72
75
  const storage = getLocalStorage()
73
76
  if (key === '') throw new Error('Cannot set a value for an empty key!')
74
- storage.setItem(prefixedKey(key), value.toString())
77
+ if (value instanceof ArrayBuffer) {
78
+ storage.setItem(prefixedKey(key), textDecoder.decode(value))
79
+ } else {
80
+ storage.setItem(prefixedKey(key), String(value))
81
+ }
75
82
  callListeners(key)
76
83
  },
77
84
  getString: (key) => {
@@ -119,6 +126,9 @@ export function createMMKV(
119
126
  trim: () => {
120
127
  // no-op
121
128
  },
129
+ checkContentChanged: () => {
130
+ // no-op
131
+ },
122
132
  dispose: () => {},
123
133
  equals: () => false,
124
134
  name: 'MMKV',
@@ -80,6 +80,9 @@ export function createMockMMKV(
80
80
  trim: () => {
81
81
  // no-op
82
82
  },
83
+ checkContentChanged: () => {
84
+ // no-op
85
+ },
83
86
  name: 'MMKV',
84
87
  dispose: () => {},
85
88
  equals: () => {
@@ -12,9 +12,13 @@ function isConfigurationEqual(
12
12
 
13
13
  return (
14
14
  left.encryptionKey === right.encryptionKey &&
15
+ left.encryptionType === right.encryptionType &&
15
16
  left.id === right.id &&
16
17
  left.path === right.path &&
17
- left.mode === right.mode
18
+ left.mode === right.mode &&
19
+ left.readOnly === right.readOnly &&
20
+ left.compareBeforeSet === right.compareBeforeSet &&
21
+ left.recoveryStrategy === right.recoveryStrategy
18
22
  )
19
23
  }
20
24
 
package/src/index.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  // All types
2
2
  export type { MMKV } from './specs/MMKV.nitro'
3
- export type { Configuration, Mode } from './specs/MMKVFactory.nitro'
3
+ export type {
4
+ Configuration,
5
+ Mode,
6
+ RecoveryStrategy,
7
+ } from './specs/MMKVFactory.nitro'
4
8
 
5
9
  // The create function
6
10
  export { createMMKV } from './createMMKV/createMMKV'
@@ -133,6 +133,11 @@ export interface MMKV extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
133
133
  * In most applications, this is not needed at all.
134
134
  */
135
135
  trim(): void
136
+ /**
137
+ * Checks whether the underlying storage file changed outside this process,
138
+ * and reloads this instance's data if needed.
139
+ */
140
+ checkContentChanged(): void
136
141
  /**
137
142
  * Adds a value changed listener. The Listener will be called whenever any value
138
143
  * in this storage instance changes (set or delete).
@@ -15,6 +15,13 @@ export type Mode = 'single-process' | 'multi-process'
15
15
  */
16
16
  export type EncryptionType = 'AES-128' | 'AES-256'
17
17
 
18
+ /**
19
+ * Configures how MMKV should handle recoverable storage errors.
20
+ * - `discard-on-error`: Discards data when MMKV detects a CRC or file-length error.
21
+ * - `recover-on-error`: Attempts to recover data when MMKV detects a CRC or file-length error.
22
+ */
23
+ export type RecoveryStrategy = 'discard-on-error' | 'recover-on-error'
24
+
18
25
  /**
19
26
  * Used for configuration of a single MMKV instance.
20
27
  */
@@ -94,10 +101,18 @@ export interface Configuration {
94
101
  * @default false
95
102
  */
96
103
  compareBeforeSet?: boolean
104
+ /**
105
+ * Configure how MMKV should handle recoverable storage errors.
106
+ *
107
+ * @default undefined
108
+ */
109
+ recoveryStrategy?: RecoveryStrategy
97
110
  }
98
111
 
99
- export interface MMKVFactory
100
- extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
112
+ export interface MMKVFactory extends HybridObject<{
113
+ ios: 'c++'
114
+ android: 'c++'
115
+ }> {
101
116
  /**
102
117
  * Initialize the MMKV library with the given root path.
103
118
  * This has to be called once, before using {@linkcode createMMKV}.
@@ -1,7 +1,9 @@
1
1
  import type { HybridObject } from 'react-native-nitro-modules'
2
2
 
3
- export interface MMKVPlatformContext
4
- extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
3
+ export interface MMKVPlatformContext extends HybridObject<{
4
+ ios: 'swift'
5
+ android: 'kotlin'
6
+ }> {
5
7
  /**
6
8
  * Get the MMKV base directory
7
9
  */
@@ -0,0 +1,15 @@
1
+ export function createTextDecoder(): TextDecoder {
2
+ const g = global ?? globalThis ?? window
3
+ if (g.TextDecoder != null) {
4
+ return new g.TextDecoder()
5
+ } else {
6
+ return {
7
+ decode: () => {
8
+ throw new Error('TextDecoder is not supported in this environment!')
9
+ },
10
+ encoding: 'utf-8',
11
+ fatal: false,
12
+ ignoreBOM: false,
13
+ }
14
+ }
15
+ }
@@ -1,4 +1,4 @@
1
- export function createTextEncoder() {
1
+ export function createTextEncoder(): TextEncoder {
2
2
  const g = global ?? globalThis ?? window
3
3
  if (g.TextEncoder != null) {
4
4
  return new g.TextEncoder()