react-native-mmkv 4.0.1 → 4.1.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.
Files changed (42) hide show
  1. package/NitroMmkv.podspec +0 -1
  2. package/android/build.gradle +1 -1
  3. package/cpp/HybridMMKV.cpp +10 -0
  4. package/cpp/HybridMMKV.hpp +1 -0
  5. package/cpp/HybridMMKVFactory.cpp +12 -4
  6. package/cpp/HybridMMKVFactory.hpp +4 -1
  7. package/lib/createMMKV/createMMKV.js +3 -13
  8. package/lib/createMMKV/createMMKV.web.js +40 -50
  9. package/lib/createMMKV/createMockMMKV.js +12 -0
  10. package/lib/deleteMMKV/deleteMMKV.d.ts +1 -0
  11. package/lib/deleteMMKV/deleteMMKV.js +9 -0
  12. package/lib/deleteMMKV/deleteMMKV.web.d.ts +1 -0
  13. package/lib/deleteMMKV/deleteMMKV.web.js +14 -0
  14. package/lib/existsMMKV/existsMMKV.d.ts +1 -0
  15. package/lib/existsMMKV/existsMMKV.js +9 -0
  16. package/lib/existsMMKV/existsMMKV.web.d.ts +1 -0
  17. package/lib/existsMMKV/existsMMKV.web.js +7 -0
  18. package/lib/getMMKVFactory.d.ts +4 -0
  19. package/lib/getMMKVFactory.js +20 -0
  20. package/lib/index.d.ts +2 -0
  21. package/lib/index.js +3 -0
  22. package/lib/specs/MMKV.nitro.d.ts +6 -0
  23. package/lib/specs/MMKVFactory.nitro.d.ts +13 -3
  24. package/lib/web/getLocalStorage.d.ts +2 -0
  25. package/lib/web/getLocalStorage.js +33 -0
  26. package/nitrogen/generated/shared/c++/HybridMMKVFactorySpec.cpp +3 -1
  27. package/nitrogen/generated/shared/c++/HybridMMKVFactorySpec.hpp +3 -1
  28. package/nitrogen/generated/shared/c++/HybridMMKVSpec.cpp +1 -0
  29. package/nitrogen/generated/shared/c++/HybridMMKVSpec.hpp +5 -0
  30. package/package.json +1 -1
  31. package/src/createMMKV/createMMKV.ts +4 -18
  32. package/src/createMMKV/createMMKV.web.ts +43 -64
  33. package/src/createMMKV/createMockMMKV.ts +12 -0
  34. package/src/deleteMMKV/deleteMMKV.ts +11 -0
  35. package/src/deleteMMKV/deleteMMKV.web.ts +20 -0
  36. package/src/existsMMKV/existsMMKV.ts +11 -0
  37. package/src/existsMMKV/existsMMKV.web.ts +11 -0
  38. package/src/getMMKVFactory.ts +28 -0
  39. package/src/index.ts +4 -0
  40. package/src/specs/MMKV.nitro.ts +7 -0
  41. package/src/specs/MMKVFactory.nitro.ts +15 -3
  42. package/src/web/getLocalStorage.ts +42 -0
package/NitroMmkv.podspec CHANGED
@@ -24,7 +24,6 @@ Pod::Spec.new do |s|
24
24
 
25
25
  # Add MMKV Core dependency
26
26
  s.compiler_flags = '-x objective-c++'
27
- s.libraries = 'z', 'c++'
28
27
  s.dependency 'MMKVCore', '2.2.4'
29
28
 
30
29
  # TODO: Remove when no one uses RN 0.79 anymore
@@ -5,7 +5,7 @@ buildscript {
5
5
  }
6
6
 
7
7
  dependencies {
8
- classpath "com.android.tools.build:gradle:8.13.0"
8
+ classpath "com.android.tools.build:gradle:8.13.1"
9
9
  }
10
10
  }
11
11
 
@@ -212,4 +212,14 @@ MMKVMode HybridMMKV::getMMKVMode(const Configuration& config) {
212
212
  }
213
213
  }
214
214
 
215
+ double HybridMMKV::importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) {
216
+ auto hybridMMKV = std::dynamic_pointer_cast<HybridMMKV>(other);
217
+ if (hybridMMKV == nullptr) {
218
+ throw std::runtime_error("The given `MMKV` instance is not of type `HybridMMKV`!");
219
+ }
220
+
221
+ size_t importedCount = instance->importFrom(hybridMMKV->instance);
222
+ return static_cast<double>(importedCount);
223
+ }
224
+
215
225
  } // namespace margelo::nitro::mmkv
@@ -37,6 +37,7 @@ public:
37
37
  void recrypt(const std::optional<std::string>& key) override;
38
38
  void trim() override;
39
39
  Listener addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) override;
40
+ double importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) override;
40
41
 
41
42
  private:
42
43
  static MMKVMode getMMKVMode(const Configuration& config);
@@ -15,10 +15,6 @@ std::string HybridMMKVFactory::getDefaultMMKVInstanceId() {
15
15
  return DEFAULT_MMAP_ID;
16
16
  }
17
17
 
18
- std::shared_ptr<HybridMMKVSpec> HybridMMKVFactory::createMMKV(const Configuration& configuration) {
19
- return std::make_shared<HybridMMKV>(configuration);
20
- }
21
-
22
18
  void HybridMMKVFactory::initializeMMKV(const std::string& rootPath) {
23
19
  Logger::log(LogLevel::Info, TAG, "Initializing MMKV with rootPath=%s", rootPath.c_str());
24
20
 
@@ -30,4 +26,16 @@ void HybridMMKVFactory::initializeMMKV(const std::string& rootPath) {
30
26
  MMKV::initializeMMKV(rootPath, logLevel);
31
27
  }
32
28
 
29
+ std::shared_ptr<HybridMMKVSpec> HybridMMKVFactory::createMMKV(const Configuration& configuration) {
30
+ return std::make_shared<HybridMMKV>(configuration);
31
+ }
32
+
33
+ bool HybridMMKVFactory::deleteMMKV(const std::string& id) {
34
+ return MMKV::removeStorage(id);
35
+ }
36
+
37
+ bool HybridMMKVFactory::existsMMKV(const std::string& id) {
38
+ return MMKV::checkExist(id);
39
+ }
40
+
33
41
  } // namespace margelo::nitro::mmkv
@@ -17,8 +17,11 @@ public:
17
17
 
18
18
  public:
19
19
  std::string getDefaultMMKVInstanceId() override;
20
- std::shared_ptr<HybridMMKVSpec> createMMKV(const Configuration& configuration) override;
21
20
  void initializeMMKV(const std::string& rootPath) override;
21
+
22
+ 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;
22
25
  };
23
26
 
24
27
  } // namespace margelo::nitro::mmkv
@@ -1,31 +1,21 @@
1
- import { NitroModules } from 'react-native-nitro-modules';
2
1
  import { Platform } from 'react-native';
3
2
  import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener';
4
3
  import { isTest } from '../isTest';
5
4
  import { createMockMMKV } from './createMockMMKV';
6
- let factory;
7
- let platformContext;
5
+ import { getMMKVFactory, getPlatformContext } from '../getMMKVFactory';
8
6
  export function createMMKV(configuration) {
9
7
  if (isTest()) {
10
8
  // In a test environment, we mock the MMKV instance.
11
9
  return createMockMMKV(configuration);
12
10
  }
13
- if (platformContext == null) {
14
- // Lazy-init the platform-context HybridObject
15
- platformContext = NitroModules.createHybridObject('MMKVPlatformContext');
16
- }
17
- if (factory == null) {
18
- // Lazy-init the factory HybridObject
19
- factory = NitroModules.createHybridObject('MMKVFactory');
20
- const baseDirectory = platformContext.getBaseDirectory();
21
- factory.initializeMMKV(baseDirectory);
22
- }
11
+ const factory = getMMKVFactory();
23
12
  // Pre-parse the config
24
13
  let config = configuration ?? { id: factory.defaultMMKVInstanceId };
25
14
  if (Platform.OS === 'ios') {
26
15
  if (config.path == null) {
27
16
  // If the user set an App Group directory in Info.plist, let's use
28
17
  // the App Group as a MMKV path:
18
+ const platformContext = getPlatformContext();
29
19
  const appGroupDirectory = platformContext.getAppGroupDirectory();
30
20
  if (appGroupDirectory != null) {
31
21
  config.path = appGroupDirectory;
@@ -1,17 +1,5 @@
1
1
  import { createTextEncoder } from '../web/createTextEncoder';
2
- const canUseDOM = typeof window !== 'undefined' && window.document?.createElement != null;
3
- const hasAccessToLocalStorage = () => {
4
- try {
5
- // throws ACCESS_DENIED error
6
- window.localStorage;
7
- return true;
8
- }
9
- catch {
10
- return false;
11
- }
12
- };
13
- const KEY_WILDCARD = '\\';
14
- const inMemoryStorage = new Map();
2
+ import { getLocalStorage, LOCAL_STORAGE_KEY_WILDCARD, } from '../web/getLocalStorage';
15
3
  export function createMMKV(config = { id: 'mmkv.default' }) {
16
4
  if (config.encryptionKey != null) {
17
5
  throw new Error("MMKV: 'encryptionKey' is not supported on Web!");
@@ -19,36 +7,12 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
19
7
  if (config.path != null) {
20
8
  throw new Error("MMKV: 'path' is not supported on Web!");
21
9
  }
22
- // canUseDOM check prevents spam in Node server environments, such as Next.js server side props.
23
- if (!hasAccessToLocalStorage() && canUseDOM) {
24
- console.warn('MMKV: LocalStorage has been disabled. Your experience will be limited to in-memory storage!');
25
- }
26
- const storage = () => {
27
- if (!canUseDOM) {
28
- throw new Error('Tried to access storage on the server. Did you forget to call this in useEffect?');
29
- }
30
- if (!hasAccessToLocalStorage()) {
31
- return {
32
- getItem: (key) => inMemoryStorage.get(key) ?? null,
33
- setItem: (key, value) => inMemoryStorage.set(key, value),
34
- removeItem: (key) => inMemoryStorage.delete(key),
35
- clear: () => inMemoryStorage.clear(),
36
- length: inMemoryStorage.size,
37
- key: (index) => Object.keys(inMemoryStorage).at(index) ?? null,
38
- };
39
- }
40
- const domStorage = global?.localStorage ?? window?.localStorage ?? localStorage;
41
- if (domStorage == null) {
42
- throw new Error(`Could not find 'localStorage' instance!`);
43
- }
44
- return domStorage;
45
- };
46
10
  const textEncoder = createTextEncoder();
47
11
  const listeners = new Set();
48
- if (config.id.includes(KEY_WILDCARD)) {
12
+ if (config.id.includes(LOCAL_STORAGE_KEY_WILDCARD)) {
49
13
  throw new Error('MMKV: `id` cannot contain the backslash character (`\\`)!');
50
14
  }
51
- const keyPrefix = `${config.id}${KEY_WILDCARD}`; // mmkv.default\\
15
+ const keyPrefix = `${config.id}${LOCAL_STORAGE_KEY_WILDCARD}`; // mmkv.default\\
52
16
  const prefixedKey = (key) => {
53
17
  if (key.includes('\\')) {
54
18
  throw new Error('MMKV: `key` cannot contain the backslash character (`\\`)!');
@@ -63,53 +27,66 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
63
27
  size: 0,
64
28
  isReadOnly: false,
65
29
  clearAll: () => {
66
- const keys = Object.keys(storage());
30
+ const storage = getLocalStorage();
31
+ const keys = Object.keys(storage);
67
32
  for (const key of keys) {
68
33
  if (key.startsWith(keyPrefix)) {
69
- storage().removeItem(key);
34
+ storage.removeItem(key);
70
35
  callListeners(key);
71
36
  }
72
37
  }
73
38
  },
74
39
  remove: (key) => {
75
- storage().removeItem(prefixedKey(key));
76
- const wasRemoved = storage().getItem(prefixedKey(key)) === null;
40
+ const storage = getLocalStorage();
41
+ storage.removeItem(prefixedKey(key));
42
+ const wasRemoved = storage.getItem(prefixedKey(key)) === null;
77
43
  if (wasRemoved)
78
44
  callListeners(key);
79
45
  return wasRemoved;
80
46
  },
81
47
  set: (key, value) => {
48
+ const storage = getLocalStorage();
82
49
  if (key === '')
83
50
  throw new Error('Cannot set a value for an empty key!');
84
- storage().setItem(prefixedKey(key), value.toString());
51
+ storage.setItem(prefixedKey(key), value.toString());
85
52
  callListeners(key);
86
53
  },
87
- getString: (key) => storage().getItem(prefixedKey(key)) ?? undefined,
54
+ getString: (key) => {
55
+ const storage = getLocalStorage();
56
+ return storage.getItem(prefixedKey(key)) ?? undefined;
57
+ },
88
58
  getNumber: (key) => {
89
- const value = storage().getItem(prefixedKey(key));
59
+ const storage = getLocalStorage();
60
+ const value = storage.getItem(prefixedKey(key));
90
61
  if (value == null)
91
62
  return undefined;
92
63
  return Number(value);
93
64
  },
94
65
  getBoolean: (key) => {
95
- const value = storage().getItem(prefixedKey(key));
66
+ const storage = getLocalStorage();
67
+ const value = storage.getItem(prefixedKey(key));
96
68
  if (value == null)
97
69
  return undefined;
98
70
  return value === 'true';
99
71
  },
100
72
  getBuffer: (key) => {
101
- const value = storage().getItem(prefixedKey(key));
73
+ const storage = getLocalStorage();
74
+ const value = storage.getItem(prefixedKey(key));
102
75
  if (value == null)
103
76
  return undefined;
104
77
  return textEncoder.encode(value).buffer;
105
78
  },
106
79
  getAllKeys: () => {
107
- const keys = Object.keys(storage());
80
+ const storage = getLocalStorage();
81
+ const keys = Object.keys(storage);
108
82
  return keys
109
83
  .filter((key) => key.startsWith(keyPrefix))
110
84
  .map((key) => key.slice(keyPrefix.length));
111
85
  },
112
- contains: (key) => storage().getItem(prefixedKey(key)) != null,
86
+ contains: (key) => {
87
+ const storage = getLocalStorage();
88
+ return storage.getItem(prefixedKey(key)) != null;
89
+ },
113
90
  recrypt: () => {
114
91
  throw new Error('`recrypt(..)` is not supported on Web!');
115
92
  },
@@ -127,5 +104,18 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
127
104
  },
128
105
  };
129
106
  },
107
+ importAllFrom: (other) => {
108
+ const storage = getLocalStorage();
109
+ const keys = other.getAllKeys();
110
+ let imported = 0;
111
+ for (const key of keys) {
112
+ const string = other.getString(key);
113
+ if (string != null) {
114
+ storage.set(key, string);
115
+ imported++;
116
+ }
117
+ }
118
+ return imported;
119
+ },
130
120
  };
131
121
  }
@@ -73,5 +73,17 @@ export function createMockMMKV(config = { id: 'mmkv.default' }) {
73
73
  },
74
74
  };
75
75
  },
76
+ importAllFrom: (other) => {
77
+ const keys = other.getAllKeys();
78
+ let imported = 0;
79
+ for (const key of keys) {
80
+ const data = other.getBuffer(key);
81
+ if (data != null) {
82
+ storage.set(key, data);
83
+ imported++;
84
+ }
85
+ }
86
+ return imported;
87
+ },
76
88
  };
77
89
  }
@@ -0,0 +1 @@
1
+ export declare function deleteMMKV(id: string): boolean;
@@ -0,0 +1,9 @@
1
+ import { getMMKVFactory } from '../getMMKVFactory';
2
+ import { isTest } from '../isTest';
3
+ export function deleteMMKV(id) {
4
+ if (isTest()) {
5
+ return true;
6
+ }
7
+ const factory = getMMKVFactory();
8
+ return factory.deleteMMKV(id);
9
+ }
@@ -0,0 +1 @@
1
+ export declare function deleteMMKV(id: string): boolean;
@@ -0,0 +1,14 @@
1
+ import { getLocalStorage, LOCAL_STORAGE_KEY_WILDCARD, } from '../web/getLocalStorage';
2
+ export function deleteMMKV(id) {
3
+ const storage = getLocalStorage();
4
+ const prefix = id + LOCAL_STORAGE_KEY_WILDCARD;
5
+ let wasRemoved = false;
6
+ const keys = Object.keys(storage);
7
+ for (const key of keys) {
8
+ if (key.startsWith(prefix)) {
9
+ storage.removeItem(key);
10
+ wasRemoved = true;
11
+ }
12
+ }
13
+ return wasRemoved;
14
+ }
@@ -0,0 +1 @@
1
+ export declare function existsMMKV(id: string): boolean;
@@ -0,0 +1,9 @@
1
+ import { getMMKVFactory } from '../getMMKVFactory';
2
+ import { isTest } from '../isTest';
3
+ export function existsMMKV(id) {
4
+ if (isTest()) {
5
+ return true;
6
+ }
7
+ const factory = getMMKVFactory();
8
+ return factory.existsMMKV(id);
9
+ }
@@ -0,0 +1 @@
1
+ export declare function existsMMKV(id: string): boolean;
@@ -0,0 +1,7 @@
1
+ import { getLocalStorage, LOCAL_STORAGE_KEY_WILDCARD, } from '../web/getLocalStorage';
2
+ export function existsMMKV(id) {
3
+ const storage = getLocalStorage();
4
+ const prefix = id + LOCAL_STORAGE_KEY_WILDCARD;
5
+ const keys = Object.keys(storage);
6
+ return keys.some((k) => k.startsWith(prefix));
7
+ }
@@ -0,0 +1,4 @@
1
+ import type { MMKVFactory } from './specs/MMKVFactory.nitro';
2
+ import type { MMKVPlatformContext } from './specs/MMKVPlatformContext.nitro';
3
+ export declare function getPlatformContext(): MMKVPlatformContext;
4
+ export declare function getMMKVFactory(): MMKVFactory;
@@ -0,0 +1,20 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ let factory;
3
+ let platformContext;
4
+ export function getPlatformContext() {
5
+ if (platformContext == null) {
6
+ // Lazy-init the platform-context HybridObject
7
+ platformContext = NitroModules.createHybridObject('MMKVPlatformContext');
8
+ }
9
+ return platformContext;
10
+ }
11
+ export function getMMKVFactory() {
12
+ if (factory == null) {
13
+ // Lazy-init the factory HybridObject
14
+ factory = NitroModules.createHybridObject('MMKVFactory');
15
+ const context = getPlatformContext();
16
+ const baseDirectory = context.getBaseDirectory();
17
+ factory.initializeMMKV(baseDirectory);
18
+ }
19
+ return factory;
20
+ }
package/lib/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export type { MMKV } from './specs/MMKV.nitro';
2
2
  export type { Configuration, Mode } from './specs/MMKVFactory.nitro';
3
3
  export { createMMKV } from './createMMKV/createMMKV';
4
+ export { existsMMKV } from './existsMMKV/existsMMKV';
5
+ export { deleteMMKV } from './deleteMMKV/deleteMMKV';
4
6
  export { useMMKV } from './hooks/useMMKV';
5
7
  export { useMMKVBoolean } from './hooks/useMMKVBoolean';
6
8
  export { useMMKVBuffer } from './hooks/useMMKVBuffer';
package/lib/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  // The create function
2
2
  export { createMMKV } from './createMMKV/createMMKV';
3
+ // Exists + Delete
4
+ export { existsMMKV } from './existsMMKV/existsMMKV';
5
+ export { deleteMMKV } from './deleteMMKV/deleteMMKV';
3
6
  // All the hooks
4
7
  export { useMMKV } from './hooks/useMMKV';
5
8
  export { useMMKVBoolean } from './hooks/useMMKVBoolean';
@@ -96,4 +96,10 @@ export interface MMKV extends HybridObject<{
96
96
  * To unsubscribe from value changes, call `remove()` on the Listener.
97
97
  */
98
98
  addOnValueChangedListener(onValueChanged: (key: string) => void): Listener;
99
+ /**
100
+ * Imports all keys and values from the
101
+ * given other {@linkcode MMKV} instance.
102
+ * @returns the number of imported keys/values.
103
+ */
104
+ importAllFrom(other: MMKV): number;
99
105
  }
@@ -67,15 +67,25 @@ export interface MMKVFactory extends HybridObject<{
67
67
  ios: 'c++';
68
68
  android: 'c++';
69
69
  }> {
70
+ /**
71
+ * Initialize the MMKV library with the given root path.
72
+ * This has to be called once, before using {@linkcode createMMKV}.
73
+ */
74
+ initializeMMKV(rootPath: string): void;
70
75
  /**
71
76
  * Create a new {@linkcode MMKV} instance with the given {@linkcode Configuration}
72
77
  */
73
78
  createMMKV(configuration: Configuration): MMKV;
74
79
  /**
75
- * Initialize the MMKV library with the given root path.
76
- * This has to be called once, before using {@linkcode createMMKV}.
80
+ * Deletes the MMKV instance with the
81
+ * given {@linkcode id}.
77
82
  */
78
- initializeMMKV(rootPath: string): void;
83
+ deleteMMKV(id: string): boolean;
84
+ /**
85
+ * Returns `true` if an MMKV instance with the
86
+ * given {@linkcode id} exists, `false` otherwise.
87
+ */
88
+ existsMMKV(id: string): boolean;
79
89
  /**
80
90
  * Get the default MMKV instance's ID.
81
91
  * @default 'mmkv.default'
@@ -0,0 +1,2 @@
1
+ export declare const LOCAL_STORAGE_KEY_WILDCARD = "\\";
2
+ export declare function getLocalStorage(): Storage;
@@ -0,0 +1,33 @@
1
+ export const LOCAL_STORAGE_KEY_WILDCARD = '\\';
2
+ const canUseDOM = typeof window !== 'undefined' && window.document?.createElement != null;
3
+ const hasAccessToLocalStorage = () => {
4
+ try {
5
+ // throws ACCESS_DENIED error
6
+ window.localStorage;
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ };
13
+ const inMemoryStorage = new Map();
14
+ export function getLocalStorage() {
15
+ if (!canUseDOM) {
16
+ throw new Error('Tried to access storage on the server. Did you forget to call this in useEffect?');
17
+ }
18
+ if (!hasAccessToLocalStorage()) {
19
+ return {
20
+ getItem: (key) => inMemoryStorage.get(key) ?? null,
21
+ setItem: (key, value) => inMemoryStorage.set(key, value),
22
+ removeItem: (key) => inMemoryStorage.delete(key),
23
+ clear: () => inMemoryStorage.clear(),
24
+ length: inMemoryStorage.size,
25
+ key: (index) => Object.keys(inMemoryStorage).at(index) ?? null,
26
+ };
27
+ }
28
+ const domStorage = global?.localStorage ?? window?.localStorage ?? localStorage;
29
+ if (domStorage == null) {
30
+ throw new Error(`Could not find 'localStorage' instance!`);
31
+ }
32
+ return domStorage;
33
+ }
@@ -15,8 +15,10 @@ namespace margelo::nitro::mmkv {
15
15
  // load custom methods/properties
16
16
  registerHybrids(this, [](Prototype& prototype) {
17
17
  prototype.registerHybridGetter("defaultMMKVInstanceId", &HybridMMKVFactorySpec::getDefaultMMKVInstanceId);
18
- prototype.registerHybridMethod("createMMKV", &HybridMMKVFactorySpec::createMMKV);
19
18
  prototype.registerHybridMethod("initializeMMKV", &HybridMMKVFactorySpec::initializeMMKV);
19
+ prototype.registerHybridMethod("createMMKV", &HybridMMKVFactorySpec::createMMKV);
20
+ prototype.registerHybridMethod("deleteMMKV", &HybridMMKVFactorySpec::deleteMMKV);
21
+ prototype.registerHybridMethod("existsMMKV", &HybridMMKVFactorySpec::existsMMKV);
20
22
  });
21
23
  }
22
24
 
@@ -54,8 +54,10 @@ namespace margelo::nitro::mmkv {
54
54
 
55
55
  public:
56
56
  // Methods
57
- virtual std::shared_ptr<HybridMMKVSpec> createMMKV(const Configuration& configuration) = 0;
58
57
  virtual void initializeMMKV(const std::string& rootPath) = 0;
58
+ 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;
59
61
 
60
62
  protected:
61
63
  // Hybrid Setup
@@ -29,6 +29,7 @@ namespace margelo::nitro::mmkv {
29
29
  prototype.registerHybridMethod("recrypt", &HybridMMKVSpec::recrypt);
30
30
  prototype.registerHybridMethod("trim", &HybridMMKVSpec::trim);
31
31
  prototype.registerHybridMethod("addOnValueChangedListener", &HybridMMKVSpec::addOnValueChangedListener);
32
+ prototype.registerHybridMethod("importAllFrom", &HybridMMKVSpec::importAllFrom);
32
33
  });
33
34
  }
34
35
 
@@ -15,6 +15,8 @@
15
15
 
16
16
  // Forward declaration of `Listener` to properly resolve imports.
17
17
  namespace margelo::nitro::mmkv { struct Listener; }
18
+ // Forward declaration of `HybridMMKVSpec` to properly resolve imports.
19
+ namespace margelo::nitro::mmkv { class HybridMMKVSpec; }
18
20
 
19
21
  #include <string>
20
22
  #include <NitroModules/ArrayBuffer.hpp>
@@ -23,6 +25,8 @@ namespace margelo::nitro::mmkv { struct Listener; }
23
25
  #include <vector>
24
26
  #include "Listener.hpp"
25
27
  #include <functional>
28
+ #include <memory>
29
+ #include "HybridMMKVSpec.hpp"
26
30
 
27
31
  namespace margelo::nitro::mmkv {
28
32
 
@@ -69,6 +73,7 @@ namespace margelo::nitro::mmkv {
69
73
  virtual void recrypt(const std::optional<std::string>& key) = 0;
70
74
  virtual void trim() = 0;
71
75
  virtual Listener addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) = 0;
76
+ virtual double importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) = 0;
72
77
 
73
78
  protected:
74
79
  // Hybrid Setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mmkv",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "⚡️ The fastest key/value storage for React Native.",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -1,14 +1,10 @@
1
- import { NitroModules } from 'react-native-nitro-modules'
2
1
  import type { MMKV } from '../specs/MMKV.nitro'
3
- import type { Configuration, MMKVFactory } from '../specs/MMKVFactory.nitro'
4
- import type { MMKVPlatformContext } from '../specs/MMKVPlatformContext.nitro'
2
+ import type { Configuration } from '../specs/MMKVFactory.nitro'
5
3
  import { Platform } from 'react-native'
6
4
  import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener'
7
5
  import { isTest } from '../isTest'
8
6
  import { createMockMMKV } from './createMockMMKV'
9
-
10
- let factory: MMKVFactory | undefined
11
- let platformContext: MMKVPlatformContext | undefined
7
+ import { getMMKVFactory, getPlatformContext } from '../getMMKVFactory'
12
8
 
13
9
  export function createMMKV(configuration?: Configuration): MMKV {
14
10
  if (isTest()) {
@@ -16,18 +12,7 @@ export function createMMKV(configuration?: Configuration): MMKV {
16
12
  return createMockMMKV(configuration)
17
13
  }
18
14
 
19
- if (platformContext == null) {
20
- // Lazy-init the platform-context HybridObject
21
- platformContext = NitroModules.createHybridObject<MMKVPlatformContext>(
22
- 'MMKVPlatformContext'
23
- )
24
- }
25
- if (factory == null) {
26
- // Lazy-init the factory HybridObject
27
- factory = NitroModules.createHybridObject<MMKVFactory>('MMKVFactory')
28
- const baseDirectory = platformContext.getBaseDirectory()
29
- factory.initializeMMKV(baseDirectory)
30
- }
15
+ const factory = getMMKVFactory()
31
16
 
32
17
  // Pre-parse the config
33
18
  let config = configuration ?? { id: factory.defaultMMKVInstanceId }
@@ -36,6 +21,7 @@ export function createMMKV(configuration?: Configuration): MMKV {
36
21
  if (config.path == null) {
37
22
  // If the user set an App Group directory in Info.plist, let's use
38
23
  // the App Group as a MMKV path:
24
+ const platformContext = getPlatformContext()
39
25
  const appGroupDirectory = platformContext.getAppGroupDirectory()
40
26
  if (appGroupDirectory != null) {
41
27
  config.path = appGroupDirectory
@@ -1,23 +1,10 @@
1
1
  import type { MMKV } from '../specs/MMKV.nitro'
2
2
  import type { Configuration } from '../specs/MMKVFactory.nitro'
3
3
  import { createTextEncoder } from '../web/createTextEncoder'
4
-
5
- const canUseDOM =
6
- typeof window !== 'undefined' && window.document?.createElement != null
7
-
8
- const hasAccessToLocalStorage = () => {
9
- try {
10
- // throws ACCESS_DENIED error
11
- window.localStorage
12
-
13
- return true
14
- } catch {
15
- return false
16
- }
17
- }
18
-
19
- const KEY_WILDCARD = '\\'
20
- const inMemoryStorage = new Map<string, string>()
4
+ import {
5
+ getLocalStorage,
6
+ LOCAL_STORAGE_KEY_WILDCARD,
7
+ } from '../web/getLocalStorage'
21
8
 
22
9
  export function createMMKV(
23
10
  config: Configuration = { id: 'mmkv.default' }
@@ -29,48 +16,14 @@ export function createMMKV(
29
16
  throw new Error("MMKV: 'path' is not supported on Web!")
30
17
  }
31
18
 
32
- // canUseDOM check prevents spam in Node server environments, such as Next.js server side props.
33
- if (!hasAccessToLocalStorage() && canUseDOM) {
34
- console.warn(
35
- 'MMKV: LocalStorage has been disabled. Your experience will be limited to in-memory storage!'
36
- )
37
- }
38
-
39
- const storage = () => {
40
- if (!canUseDOM) {
41
- throw new Error(
42
- 'Tried to access storage on the server. Did you forget to call this in useEffect?'
43
- )
44
- }
45
-
46
- if (!hasAccessToLocalStorage()) {
47
- return {
48
- getItem: (key: string) => inMemoryStorage.get(key) ?? null,
49
- setItem: (key: string, value: string) =>
50
- inMemoryStorage.set(key, value),
51
- removeItem: (key: string) => inMemoryStorage.delete(key),
52
- clear: () => inMemoryStorage.clear(),
53
- length: inMemoryStorage.size,
54
- key: (index: number) => Object.keys(inMemoryStorage).at(index) ?? null,
55
- } as Storage
56
- }
57
-
58
- const domStorage =
59
- global?.localStorage ?? window?.localStorage ?? localStorage
60
- if (domStorage == null) {
61
- throw new Error(`Could not find 'localStorage' instance!`)
62
- }
63
- return domStorage
64
- }
65
-
66
19
  const textEncoder = createTextEncoder()
67
20
  const listeners = new Set<(key: string) => void>()
68
21
 
69
- if (config.id.includes(KEY_WILDCARD)) {
22
+ if (config.id.includes(LOCAL_STORAGE_KEY_WILDCARD)) {
70
23
  throw new Error('MMKV: `id` cannot contain the backslash character (`\\`)!')
71
24
  }
72
25
 
73
- const keyPrefix = `${config.id}${KEY_WILDCARD}` // mmkv.default\\
26
+ const keyPrefix = `${config.id}${LOCAL_STORAGE_KEY_WILDCARD}` // mmkv.default\\
74
27
  const prefixedKey = (key: string) => {
75
28
  if (key.includes('\\')) {
76
29
  throw new Error(
@@ -89,48 +42,61 @@ export function createMMKV(
89
42
  size: 0,
90
43
  isReadOnly: false,
91
44
  clearAll: () => {
92
- const keys = Object.keys(storage())
45
+ const storage = getLocalStorage()
46
+ const keys = Object.keys(storage)
93
47
  for (const key of keys) {
94
48
  if (key.startsWith(keyPrefix)) {
95
- storage().removeItem(key)
49
+ storage.removeItem(key)
96
50
  callListeners(key)
97
51
  }
98
52
  }
99
53
  },
100
54
  remove: (key) => {
101
- storage().removeItem(prefixedKey(key))
102
- const wasRemoved = storage().getItem(prefixedKey(key)) === null
55
+ const storage = getLocalStorage()
56
+ storage.removeItem(prefixedKey(key))
57
+ const wasRemoved = storage.getItem(prefixedKey(key)) === null
103
58
  if (wasRemoved) callListeners(key)
104
59
  return wasRemoved
105
60
  },
106
61
  set: (key, value) => {
62
+ const storage = getLocalStorage()
107
63
  if (key === '') throw new Error('Cannot set a value for an empty key!')
108
- storage().setItem(prefixedKey(key), value.toString())
64
+ storage.setItem(prefixedKey(key), value.toString())
109
65
  callListeners(key)
110
66
  },
111
- getString: (key) => storage().getItem(prefixedKey(key)) ?? undefined,
67
+ getString: (key) => {
68
+ const storage = getLocalStorage()
69
+ return storage.getItem(prefixedKey(key)) ?? undefined
70
+ },
112
71
  getNumber: (key) => {
113
- const value = storage().getItem(prefixedKey(key))
72
+ const storage = getLocalStorage()
73
+ const value = storage.getItem(prefixedKey(key))
114
74
  if (value == null) return undefined
115
75
  return Number(value)
116
76
  },
117
77
  getBoolean: (key) => {
118
- const value = storage().getItem(prefixedKey(key))
78
+ const storage = getLocalStorage()
79
+ const value = storage.getItem(prefixedKey(key))
119
80
  if (value == null) return undefined
120
81
  return value === 'true'
121
82
  },
122
83
  getBuffer: (key) => {
123
- const value = storage().getItem(prefixedKey(key))
84
+ const storage = getLocalStorage()
85
+ const value = storage.getItem(prefixedKey(key))
124
86
  if (value == null) return undefined
125
87
  return textEncoder.encode(value).buffer
126
88
  },
127
89
  getAllKeys: () => {
128
- const keys = Object.keys(storage())
90
+ const storage = getLocalStorage()
91
+ const keys = Object.keys(storage)
129
92
  return keys
130
93
  .filter((key) => key.startsWith(keyPrefix))
131
94
  .map((key) => key.slice(keyPrefix.length))
132
95
  },
133
- contains: (key) => storage().getItem(prefixedKey(key)) != null,
96
+ contains: (key) => {
97
+ const storage = getLocalStorage()
98
+ return storage.getItem(prefixedKey(key)) != null
99
+ },
134
100
  recrypt: () => {
135
101
  throw new Error('`recrypt(..)` is not supported on Web!')
136
102
  },
@@ -148,5 +114,18 @@ export function createMMKV(
148
114
  },
149
115
  }
150
116
  },
117
+ importAllFrom: (other) => {
118
+ const storage = getLocalStorage()
119
+ const keys = other.getAllKeys()
120
+ let imported = 0
121
+ for (const key of keys) {
122
+ const string = other.getString(key)
123
+ if (string != null) {
124
+ storage.set(key, string)
125
+ imported++
126
+ }
127
+ }
128
+ return imported
129
+ },
151
130
  }
152
131
  }
@@ -79,5 +79,17 @@ export function createMockMMKV(
79
79
  },
80
80
  }
81
81
  },
82
+ importAllFrom: (other) => {
83
+ const keys = other.getAllKeys()
84
+ let imported = 0
85
+ for (const key of keys) {
86
+ const data = other.getBuffer(key)
87
+ if (data != null) {
88
+ storage.set(key, data)
89
+ imported++
90
+ }
91
+ }
92
+ return imported
93
+ },
82
94
  }
83
95
  }
@@ -0,0 +1,11 @@
1
+ import { getMMKVFactory } from '../getMMKVFactory'
2
+ import { isTest } from '../isTest'
3
+
4
+ export function deleteMMKV(id: string): boolean {
5
+ if (isTest()) {
6
+ return true
7
+ }
8
+
9
+ const factory = getMMKVFactory()
10
+ return factory.deleteMMKV(id)
11
+ }
@@ -0,0 +1,20 @@
1
+ import {
2
+ getLocalStorage,
3
+ LOCAL_STORAGE_KEY_WILDCARD,
4
+ } from '../web/getLocalStorage'
5
+
6
+ export function deleteMMKV(id: string): boolean {
7
+ const storage = getLocalStorage()
8
+ const prefix = id + LOCAL_STORAGE_KEY_WILDCARD
9
+ let wasRemoved = false
10
+
11
+ const keys = Object.keys(storage)
12
+ for (const key of keys) {
13
+ if (key.startsWith(prefix)) {
14
+ storage.removeItem(key)
15
+ wasRemoved = true
16
+ }
17
+ }
18
+
19
+ return wasRemoved
20
+ }
@@ -0,0 +1,11 @@
1
+ import { getMMKVFactory } from '../getMMKVFactory'
2
+ import { isTest } from '../isTest'
3
+
4
+ export function existsMMKV(id: string): boolean {
5
+ if (isTest()) {
6
+ return true
7
+ }
8
+
9
+ const factory = getMMKVFactory()
10
+ return factory.existsMMKV(id)
11
+ }
@@ -0,0 +1,11 @@
1
+ import {
2
+ getLocalStorage,
3
+ LOCAL_STORAGE_KEY_WILDCARD,
4
+ } from '../web/getLocalStorage'
5
+
6
+ export function existsMMKV(id: string): boolean {
7
+ const storage = getLocalStorage()
8
+ const prefix = id + LOCAL_STORAGE_KEY_WILDCARD
9
+ const keys = Object.keys(storage)
10
+ return keys.some((k) => k.startsWith(prefix))
11
+ }
@@ -0,0 +1,28 @@
1
+ import { NitroModules } from 'react-native-nitro-modules'
2
+ import type { MMKVFactory } from './specs/MMKVFactory.nitro'
3
+ import type { MMKVPlatformContext } from './specs/MMKVPlatformContext.nitro'
4
+
5
+ let factory: MMKVFactory | undefined
6
+ let platformContext: MMKVPlatformContext | undefined
7
+
8
+ export function getPlatformContext(): MMKVPlatformContext {
9
+ if (platformContext == null) {
10
+ // Lazy-init the platform-context HybridObject
11
+ platformContext = NitroModules.createHybridObject<MMKVPlatformContext>(
12
+ 'MMKVPlatformContext'
13
+ )
14
+ }
15
+ return platformContext
16
+ }
17
+
18
+ export function getMMKVFactory(): MMKVFactory {
19
+ if (factory == null) {
20
+ // Lazy-init the factory HybridObject
21
+ factory = NitroModules.createHybridObject<MMKVFactory>('MMKVFactory')
22
+ const context = getPlatformContext()
23
+ const baseDirectory = context.getBaseDirectory()
24
+ factory.initializeMMKV(baseDirectory)
25
+ }
26
+
27
+ return factory
28
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,10 @@ export type { Configuration, Mode } from './specs/MMKVFactory.nitro'
5
5
  // The create function
6
6
  export { createMMKV } from './createMMKV/createMMKV'
7
7
 
8
+ // Exists + Delete
9
+ export { existsMMKV } from './existsMMKV/existsMMKV'
10
+ export { deleteMMKV } from './deleteMMKV/deleteMMKV'
11
+
8
12
  // All the hooks
9
13
  export { useMMKV } from './hooks/useMMKV'
10
14
  export { useMMKVBoolean } from './hooks/useMMKVBoolean'
@@ -95,4 +95,11 @@ export interface MMKV extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
95
95
  * To unsubscribe from value changes, call `remove()` on the Listener.
96
96
  */
97
97
  addOnValueChangedListener(onValueChanged: (key: string) => void): Listener
98
+
99
+ /**
100
+ * Imports all keys and values from the
101
+ * given other {@linkcode MMKV} instance.
102
+ * @returns the number of imported keys/values.
103
+ */
104
+ importAllFrom(other: MMKV): number
98
105
  }
@@ -68,16 +68,28 @@ export interface Configuration {
68
68
 
69
69
  export interface MMKVFactory
70
70
  extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
71
+ /**
72
+ * Initialize the MMKV library with the given root path.
73
+ * This has to be called once, before using {@linkcode createMMKV}.
74
+ */
75
+ initializeMMKV(rootPath: string): void
76
+
71
77
  /**
72
78
  * Create a new {@linkcode MMKV} instance with the given {@linkcode Configuration}
73
79
  */
74
80
  createMMKV(configuration: Configuration): MMKV
75
81
 
76
82
  /**
77
- * Initialize the MMKV library with the given root path.
78
- * This has to be called once, before using {@linkcode createMMKV}.
83
+ * Deletes the MMKV instance with the
84
+ * given {@linkcode id}.
79
85
  */
80
- initializeMMKV(rootPath: string): void
86
+ deleteMMKV(id: string): boolean
87
+
88
+ /**
89
+ * Returns `true` if an MMKV instance with the
90
+ * given {@linkcode id} exists, `false` otherwise.
91
+ */
92
+ existsMMKV(id: string): boolean
81
93
 
82
94
  /**
83
95
  * Get the default MMKV instance's ID.
@@ -0,0 +1,42 @@
1
+ export const LOCAL_STORAGE_KEY_WILDCARD = '\\'
2
+
3
+ const canUseDOM =
4
+ typeof window !== 'undefined' && window.document?.createElement != null
5
+
6
+ const hasAccessToLocalStorage = () => {
7
+ try {
8
+ // throws ACCESS_DENIED error
9
+ window.localStorage
10
+
11
+ return true
12
+ } catch {
13
+ return false
14
+ }
15
+ }
16
+ const inMemoryStorage = new Map<string, string>()
17
+
18
+ export function getLocalStorage(): Storage {
19
+ if (!canUseDOM) {
20
+ throw new Error(
21
+ 'Tried to access storage on the server. Did you forget to call this in useEffect?'
22
+ )
23
+ }
24
+
25
+ if (!hasAccessToLocalStorage()) {
26
+ return {
27
+ getItem: (key: string) => inMemoryStorage.get(key) ?? null,
28
+ setItem: (key: string, value: string) => inMemoryStorage.set(key, value),
29
+ removeItem: (key: string) => inMemoryStorage.delete(key),
30
+ clear: () => inMemoryStorage.clear(),
31
+ length: inMemoryStorage.size,
32
+ key: (index: number) => Object.keys(inMemoryStorage).at(index) ?? null,
33
+ } as Storage
34
+ }
35
+
36
+ const domStorage =
37
+ global?.localStorage ?? window?.localStorage ?? localStorage
38
+ if (domStorage == null) {
39
+ throw new Error(`Could not find 'localStorage' instance!`)
40
+ }
41
+ return domStorage
42
+ }