react-native-mmkv 4.0.0-beta.9 → 4.0.1
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 +9 -1
- package/android/build.gradle +3 -3
- package/cpp/HybridMMKV.cpp +40 -9
- package/cpp/HybridMMKV.hpp +2 -1
- package/lib/createMMKV/createMMKV.js +2 -2
- package/lib/createMMKV/createMMKV.web.d.ts +1 -1
- package/lib/createMMKV/createMMKV.web.js +18 -4
- package/lib/createMMKV/createMockMMKV.d.ts +6 -0
- package/lib/createMMKV/{createMMKV.mock.js → createMockMMKV.js} +8 -5
- package/lib/specs/MMKV.nitro.d.ts +16 -11
- package/nitrogen/generated/android/NitroMmkv+autolinking.cmake +7 -4
- package/nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.cpp +6 -0
- package/nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.hpp +1 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec.kt +7 -2
- package/nitrogen/generated/ios/NitroMmkv-Swift-Cxx-Bridge.cpp +3 -2
- package/nitrogen/generated/ios/NitroMmkv-Swift-Cxx-Bridge.hpp +2 -2
- package/nitrogen/generated/ios/c++/HybridMMKVPlatformContextSpecSwift.hpp +3 -0
- package/nitrogen/generated/ios/swift/HybridMMKVPlatformContextSpec.swift +7 -0
- package/nitrogen/generated/ios/swift/HybridMMKVPlatformContextSpec_cxx.swift +9 -1
- package/nitrogen/generated/shared/c++/Configuration.hpp +8 -0
- package/nitrogen/generated/shared/c++/HybridMMKVSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridMMKVSpec.hpp +2 -3
- package/nitrogen/generated/shared/c++/Listener.hpp +8 -0
- package/package.json +7 -8
- package/src/createMMKV/createMMKV.ts +2 -2
- package/src/createMMKV/createMMKV.web.ts +19 -4
- package/src/createMMKV/{createMMKV.mock.ts → createMockMMKV.ts} +10 -5
- package/src/specs/MMKV.nitro.ts +16 -11
- package/app.plugin.js +0 -1
- package/lib/createMMKV/createMMKV.mock.d.ts +0 -5
- package/lib/expo-plugin/withMMKV.cjs +0 -26
- package/lib/expo-plugin/withMMKV.d.cts +0 -3
- package/lib/expo-plugin/withMMKV.d.ts +0 -3
- package/lib/expo-plugin/withMMKV.js +0 -17
- package/src/expo-plugin/withMMKV.cts +0 -31
package/NitroMmkv.podspec
CHANGED
|
@@ -25,7 +25,15 @@ Pod::Spec.new do |s|
|
|
|
25
25
|
# Add MMKV Core dependency
|
|
26
26
|
s.compiler_flags = '-x objective-c++'
|
|
27
27
|
s.libraries = 'z', 'c++'
|
|
28
|
-
s.dependency 'MMKVCore', '
|
|
28
|
+
s.dependency 'MMKVCore', '2.2.4'
|
|
29
|
+
|
|
30
|
+
# TODO: Remove when no one uses RN 0.79 anymore
|
|
31
|
+
# Add support for React Native 0.79 or below
|
|
32
|
+
s.pod_target_xcconfig = {
|
|
33
|
+
"HEADER_SEARCH_PATHS" => ["${PODS_ROOT}/RCT-Folly"],
|
|
34
|
+
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES",
|
|
35
|
+
"OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1"
|
|
36
|
+
}
|
|
29
37
|
|
|
30
38
|
load 'nitrogen/generated/ios/NitroMmkv+autolinking.rb'
|
|
31
39
|
add_nitrogen_files(s)
|
package/android/build.gradle
CHANGED
|
@@ -5,13 +5,13 @@ buildscript {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
dependencies {
|
|
8
|
-
classpath "com.android.tools.build:gradle:8.
|
|
8
|
+
classpath "com.android.tools.build:gradle:8.13.0"
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
def reactNativeArchitectures() {
|
|
13
13
|
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
14
|
-
return value ? value.split(",") : ["x86_64", "arm64-v8a"]
|
|
14
|
+
return value ? value.split(",") : ["x86", "x86_64", "armeabi-v7a", "arm64-v8a"]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
def isNewArchitectureEnabled() {
|
|
@@ -139,6 +139,6 @@ dependencies {
|
|
|
139
139
|
implementation project(":react-native-nitro-modules")
|
|
140
140
|
|
|
141
141
|
// Add a dependency on mmkv core (this ships a C++ prefab)
|
|
142
|
-
implementation "
|
|
142
|
+
implementation "io.github.zhongwuzw:mmkv:2.2.4"
|
|
143
143
|
}
|
|
144
144
|
|
package/cpp/HybridMMKV.cpp
CHANGED
|
@@ -55,6 +55,9 @@ HybridMMKV::HybridMMKV(const Configuration& config) : HybridObject(TAG) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
std::string HybridMMKV::getId() {
|
|
59
|
+
return instance->mmapID();
|
|
60
|
+
}
|
|
58
61
|
double HybridMMKV::getSize() {
|
|
59
62
|
return instance->actualSize();
|
|
60
63
|
}
|
|
@@ -70,15 +73,33 @@ struct overloaded : Ts... {
|
|
|
70
73
|
template <class... Ts>
|
|
71
74
|
overloaded(Ts...) -> overloaded<Ts...>;
|
|
72
75
|
|
|
73
|
-
void HybridMMKV::set(const std::string& key, const std::variant<
|
|
76
|
+
void HybridMMKV::set(const std::string& key, const std::variant<bool, std::shared_ptr<ArrayBuffer>, std::string, double>& value) {
|
|
77
|
+
if (key.empty()) [[unlikely]] {
|
|
78
|
+
throw std::runtime_error("Cannot set a value for an empty key!");
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
// Pattern-match each potential value in std::variant
|
|
75
|
-
std::visit(overloaded{[&](
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
bool didSet = std::visit(overloaded{[&](bool b) {
|
|
83
|
+
// boolean
|
|
84
|
+
return instance->set(b, key);
|
|
85
|
+
},
|
|
86
|
+
[&](const std::shared_ptr<ArrayBuffer>& buf) {
|
|
87
|
+
// ArrayBuffer
|
|
88
|
+
MMBuffer buffer(buf->data(), buf->size(), MMBufferCopyFlag::MMBufferNoCopy);
|
|
89
|
+
return instance->set(std::move(buffer), key);
|
|
90
|
+
},
|
|
91
|
+
[&](const std::string& string) {
|
|
92
|
+
// string
|
|
93
|
+
return instance->set(string, key);
|
|
94
|
+
},
|
|
95
|
+
[&](double number) {
|
|
96
|
+
// number
|
|
97
|
+
return instance->set(number, key);
|
|
98
|
+
}},
|
|
99
|
+
value);
|
|
100
|
+
if (!didSet) {
|
|
101
|
+
throw std::runtime_error("Failed to set value for key \"" + key + "\"!");
|
|
102
|
+
}
|
|
82
103
|
|
|
83
104
|
// Notify on changed
|
|
84
105
|
MMKVValueChangedListenerRegistry::notifyOnValueChanged(instance->mmapID(), key);
|
|
@@ -130,13 +151,23 @@ bool HybridMMKV::contains(const std::string& key) {
|
|
|
130
151
|
return instance->containsKey(key);
|
|
131
152
|
}
|
|
132
153
|
bool HybridMMKV::remove(const std::string& key) {
|
|
133
|
-
|
|
154
|
+
bool wasRemoved = instance->removeValueForKey(key);
|
|
155
|
+
if (wasRemoved) {
|
|
156
|
+
// Notify on changed
|
|
157
|
+
MMKVValueChangedListenerRegistry::notifyOnValueChanged(instance->mmapID(), key);
|
|
158
|
+
}
|
|
159
|
+
return wasRemoved;
|
|
134
160
|
}
|
|
135
161
|
std::vector<std::string> HybridMMKV::getAllKeys() {
|
|
136
162
|
return instance->allKeys();
|
|
137
163
|
}
|
|
138
164
|
void HybridMMKV::clearAll() {
|
|
165
|
+
auto keysBefore = getAllKeys();
|
|
139
166
|
instance->clearAll();
|
|
167
|
+
for (const auto& key : keysBefore) {
|
|
168
|
+
// Notify on changed
|
|
169
|
+
MMKVValueChangedListenerRegistry::notifyOnValueChanged(instance->mmapID(), key);
|
|
170
|
+
}
|
|
140
171
|
}
|
|
141
172
|
void HybridMMKV::recrypt(const std::optional<std::string>& key) {
|
|
142
173
|
bool successful = false;
|
package/cpp/HybridMMKV.hpp
CHANGED
|
@@ -19,12 +19,13 @@ public:
|
|
|
19
19
|
|
|
20
20
|
public:
|
|
21
21
|
// Properties
|
|
22
|
+
std::string getId() override;
|
|
22
23
|
double getSize() override;
|
|
23
24
|
bool getIsReadOnly() override;
|
|
24
25
|
|
|
25
26
|
public:
|
|
26
27
|
// Methods
|
|
27
|
-
void set(const std::string& key, const std::variant<
|
|
28
|
+
void set(const std::string& key, const std::variant<bool, std::shared_ptr<ArrayBuffer>, std::string, double>& value) override;
|
|
28
29
|
std::optional<bool> getBoolean(const std::string& key) override;
|
|
29
30
|
std::optional<std::string> getString(const std::string& key) override;
|
|
30
31
|
std::optional<double> getNumber(const std::string& key) override;
|
|
@@ -2,13 +2,13 @@ import { NitroModules } from 'react-native-nitro-modules';
|
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
3
|
import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener';
|
|
4
4
|
import { isTest } from '../isTest';
|
|
5
|
-
import { createMockMMKV } from './
|
|
5
|
+
import { createMockMMKV } from './createMockMMKV';
|
|
6
6
|
let factory;
|
|
7
7
|
let platformContext;
|
|
8
8
|
export function createMMKV(configuration) {
|
|
9
9
|
if (isTest()) {
|
|
10
10
|
// In a test environment, we mock the MMKV instance.
|
|
11
|
-
return createMockMMKV();
|
|
11
|
+
return createMockMMKV(configuration);
|
|
12
12
|
}
|
|
13
13
|
if (platformContext == null) {
|
|
14
14
|
// Lazy-init the platform-context HybridObject
|
|
@@ -12,7 +12,7 @@ const hasAccessToLocalStorage = () => {
|
|
|
12
12
|
};
|
|
13
13
|
const KEY_WILDCARD = '\\';
|
|
14
14
|
const inMemoryStorage = new Map();
|
|
15
|
-
export function createMMKV(config) {
|
|
15
|
+
export function createMMKV(config = { id: 'mmkv.default' }) {
|
|
16
16
|
if (config.encryptionKey != null) {
|
|
17
17
|
throw new Error("MMKV: 'encryptionKey' is not supported on Web!");
|
|
18
18
|
}
|
|
@@ -55,18 +55,34 @@ export function createMMKV(config) {
|
|
|
55
55
|
}
|
|
56
56
|
return `${keyPrefix}${key}`;
|
|
57
57
|
};
|
|
58
|
+
const callListeners = (key) => {
|
|
59
|
+
listeners.forEach((l) => l(key));
|
|
60
|
+
};
|
|
58
61
|
return {
|
|
62
|
+
id: config.id,
|
|
63
|
+
size: 0,
|
|
64
|
+
isReadOnly: false,
|
|
59
65
|
clearAll: () => {
|
|
60
66
|
const keys = Object.keys(storage());
|
|
61
67
|
for (const key of keys) {
|
|
62
68
|
if (key.startsWith(keyPrefix)) {
|
|
63
69
|
storage().removeItem(key);
|
|
70
|
+
callListeners(key);
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
},
|
|
67
|
-
remove: (key) =>
|
|
74
|
+
remove: (key) => {
|
|
75
|
+
storage().removeItem(prefixedKey(key));
|
|
76
|
+
const wasRemoved = storage().getItem(prefixedKey(key)) === null;
|
|
77
|
+
if (wasRemoved)
|
|
78
|
+
callListeners(key);
|
|
79
|
+
return wasRemoved;
|
|
80
|
+
},
|
|
68
81
|
set: (key, value) => {
|
|
82
|
+
if (key === '')
|
|
83
|
+
throw new Error('Cannot set a value for an empty key!');
|
|
69
84
|
storage().setItem(prefixedKey(key), value.toString());
|
|
85
|
+
callListeners(key);
|
|
70
86
|
},
|
|
71
87
|
getString: (key) => storage().getItem(prefixedKey(key)) ?? undefined,
|
|
72
88
|
getNumber: (key) => {
|
|
@@ -97,8 +113,6 @@ export function createMMKV(config) {
|
|
|
97
113
|
recrypt: () => {
|
|
98
114
|
throw new Error('`recrypt(..)` is not supported on Web!');
|
|
99
115
|
},
|
|
100
|
-
size: 0,
|
|
101
|
-
isReadOnly: false,
|
|
102
116
|
trim: () => {
|
|
103
117
|
// no-op
|
|
104
118
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mock MMKV instance when used in a Jest/Test environment.
|
|
3
3
|
*/
|
|
4
|
-
export function createMockMMKV() {
|
|
4
|
+
export function createMockMMKV(config = { id: 'mmkv.default' }) {
|
|
5
5
|
const storage = new Map();
|
|
6
6
|
const listeners = new Set();
|
|
7
7
|
const notifyListeners = (key) => {
|
|
@@ -10,6 +10,11 @@ export function createMockMMKV() {
|
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
12
|
return {
|
|
13
|
+
id: config.id,
|
|
14
|
+
get size() {
|
|
15
|
+
return storage.size;
|
|
16
|
+
},
|
|
17
|
+
isReadOnly: false,
|
|
13
18
|
clearAll: () => {
|
|
14
19
|
const keysBefore = storage.keys();
|
|
15
20
|
storage.clear();
|
|
@@ -26,6 +31,8 @@ export function createMockMMKV() {
|
|
|
26
31
|
return deleted;
|
|
27
32
|
},
|
|
28
33
|
set: (key, value) => {
|
|
34
|
+
if (key === '')
|
|
35
|
+
throw new Error('Cannot set a value for an empty key!');
|
|
29
36
|
storage.set(key, value);
|
|
30
37
|
notifyListeners(key);
|
|
31
38
|
},
|
|
@@ -50,10 +57,6 @@ export function createMockMMKV() {
|
|
|
50
57
|
recrypt: () => {
|
|
51
58
|
console.warn('Encryption is not supported in mocked MMKV instances!');
|
|
52
59
|
},
|
|
53
|
-
get size() {
|
|
54
|
-
return storage.size;
|
|
55
|
-
},
|
|
56
|
-
isReadOnly: false,
|
|
57
60
|
trim: () => {
|
|
58
61
|
// no-op
|
|
59
62
|
},
|
|
@@ -7,9 +7,23 @@ export interface MMKV extends HybridObject<{
|
|
|
7
7
|
android: 'c++';
|
|
8
8
|
}> {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Get the ID of this {@linkcode MMKV} instance.
|
|
11
|
+
*/
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/**
|
|
14
|
+
* Get the current total size of the storage, in bytes.
|
|
15
|
+
*/
|
|
16
|
+
readonly size: number;
|
|
17
|
+
/**
|
|
18
|
+
* Returns whether this instance is in read-only mode or not.
|
|
19
|
+
* If this is `true`, you can only use "get"-functions.
|
|
20
|
+
*/
|
|
21
|
+
readonly isReadOnly: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Set a {@linkcode value} for the given {@linkcode key}.
|
|
11
24
|
*
|
|
12
|
-
* @throws an Error if the
|
|
25
|
+
* @throws an Error if the {@linkcode key} is empty.
|
|
26
|
+
* @throws an Error if the {@linkcode value} cannot be set.
|
|
13
27
|
*/
|
|
14
28
|
set(key: string, value: boolean | string | number | ArrayBuffer): void;
|
|
15
29
|
/**
|
|
@@ -75,15 +89,6 @@ export interface MMKV extends HybridObject<{
|
|
|
75
89
|
* In most applications, this is not needed at all.
|
|
76
90
|
*/
|
|
77
91
|
trim(): void;
|
|
78
|
-
/**
|
|
79
|
-
* Get the current total size of the storage, in bytes.
|
|
80
|
-
*/
|
|
81
|
-
readonly size: number;
|
|
82
|
-
/**
|
|
83
|
-
* Returns whether this instance is in read-only mode or not.
|
|
84
|
-
* If this is `true`, you can only use "get"-functions.
|
|
85
|
-
*/
|
|
86
|
-
readonly isReadOnly: boolean;
|
|
87
92
|
/**
|
|
88
93
|
* Adds a value changed listener. The Listener will be called whenever any value
|
|
89
94
|
* in this storage instance changes (set or delete).
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroMmkv+autolinking.cmake)
|
|
14
14
|
# ```
|
|
15
15
|
|
|
16
|
+
# Define a flag to check if we are building properly
|
|
17
|
+
add_definitions(-DBUILDING_NITROMMKV_WITH_GENERATED_CMAKE_PROJECT)
|
|
18
|
+
|
|
19
|
+
# Enable Raw Props parsing in react-native (for Nitro Views)
|
|
20
|
+
add_definitions(-DRN_SERIALIZABLE_STATE)
|
|
21
|
+
|
|
16
22
|
# Add all headers that were generated by Nitrogen
|
|
17
23
|
include_directories(
|
|
18
24
|
"../nitrogen/generated/shared/c++"
|
|
@@ -34,12 +40,9 @@ target_sources(
|
|
|
34
40
|
../nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.cpp
|
|
35
41
|
)
|
|
36
42
|
|
|
37
|
-
# Define a flag to check if we are building properly
|
|
38
|
-
add_definitions(-DBUILDING_NITROMMKV_WITH_GENERATED_CMAKE_PROJECT)
|
|
39
|
-
|
|
40
43
|
# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
|
|
41
44
|
# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
|
|
42
|
-
|
|
45
|
+
target_compile_definitions(
|
|
43
46
|
NitroMmkv PRIVATE
|
|
44
47
|
-DFOLLY_NO_CONFIG=1
|
|
45
48
|
-DFOLLY_HAVE_CLOCK_GETTIME=1
|
|
@@ -34,6 +34,12 @@ namespace margelo::nitro::mmkv {
|
|
|
34
34
|
method(_javaPart);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
std::string JHybridMMKVPlatformContextSpec::toString() {
|
|
38
|
+
static const auto method = javaClassStatic()->getMethod<jni::JString()>("toString");
|
|
39
|
+
auto javaString = method(_javaPart);
|
|
40
|
+
return javaString->toStdString();
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
// Properties
|
|
38
44
|
|
|
39
45
|
|
|
@@ -41,6 +41,7 @@ namespace margelo::nitro::mmkv {
|
|
|
41
41
|
public:
|
|
42
42
|
size_t getExternalMemorySize() noexcept override;
|
|
43
43
|
void dispose() noexcept override;
|
|
44
|
+
std::string toString() override;
|
|
44
45
|
|
|
45
46
|
public:
|
|
46
47
|
inline const jni::global_ref<JHybridMMKVPlatformContextSpec::javaobject>& getJavaPart() const noexcept {
|
package/nitrogen/generated/android/kotlin/com/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec.kt
CHANGED
|
@@ -10,7 +10,7 @@ package com.margelo.nitro.mmkv
|
|
|
10
10
|
import androidx.annotation.Keep
|
|
11
11
|
import com.facebook.jni.HybridData
|
|
12
12
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
13
|
-
import com.margelo.nitro.core
|
|
13
|
+
import com.margelo.nitro.core.HybridObject
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* A Kotlin class representing the MMKVPlatformContext HybridObject.
|
|
@@ -36,6 +36,11 @@ abstract class HybridMMKVPlatformContextSpec: HybridObject() {
|
|
|
36
36
|
super.updateNative(hybridData)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Default implementation of `HybridObject.toString()`
|
|
40
|
+
override fun toString(): String {
|
|
41
|
+
return "[HybridObject MMKVPlatformContext]"
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
// Properties
|
|
40
45
|
|
|
41
46
|
|
|
@@ -51,6 +56,6 @@ abstract class HybridMMKVPlatformContextSpec: HybridObject() {
|
|
|
51
56
|
private external fun initHybrid(): HybridData
|
|
52
57
|
|
|
53
58
|
companion object {
|
|
54
|
-
|
|
59
|
+
protected const val TAG = "HybridMMKVPlatformContextSpec"
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
// Include C++ implementation defined types
|
|
11
11
|
#include "HybridMMKVPlatformContextSpecSwift.hpp"
|
|
12
12
|
#include "NitroMmkv-Swift-Cxx-Umbrella.hpp"
|
|
13
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
13
14
|
|
|
14
15
|
namespace margelo::nitro::mmkv::bridge::swift {
|
|
15
16
|
|
|
16
17
|
// pragma MARK: std::shared_ptr<HybridMMKVPlatformContextSpec>
|
|
17
|
-
std::shared_ptr<HybridMMKVPlatformContextSpec> create_std__shared_ptr_HybridMMKVPlatformContextSpec_(void*
|
|
18
|
+
std::shared_ptr<HybridMMKVPlatformContextSpec> create_std__shared_ptr_HybridMMKVPlatformContextSpec_(void* NON_NULL swiftUnsafePointer) noexcept {
|
|
18
19
|
NitroMmkv::HybridMMKVPlatformContextSpec_cxx swiftPart = NitroMmkv::HybridMMKVPlatformContextSpec_cxx::fromUnsafe(swiftUnsafePointer);
|
|
19
20
|
return std::make_shared<margelo::nitro::mmkv::HybridMMKVPlatformContextSpecSwift>(swiftPart);
|
|
20
21
|
}
|
|
21
|
-
void*
|
|
22
|
+
void* NON_NULL get_std__shared_ptr_HybridMMKVPlatformContextSpec_(std__shared_ptr_HybridMMKVPlatformContextSpec_ cppType) {
|
|
22
23
|
std::shared_ptr<margelo::nitro::mmkv::HybridMMKVPlatformContextSpecSwift> swiftWrapper = std::dynamic_pointer_cast<margelo::nitro::mmkv::HybridMMKVPlatformContextSpecSwift>(cppType);
|
|
23
24
|
#ifdef NITRO_DEBUG
|
|
24
25
|
if (swiftWrapper == nullptr) [[unlikely]] {
|
|
@@ -49,8 +49,8 @@ namespace margelo::nitro::mmkv::bridge::swift {
|
|
|
49
49
|
* Specialized version of `std::shared_ptr<HybridMMKVPlatformContextSpec>`.
|
|
50
50
|
*/
|
|
51
51
|
using std__shared_ptr_HybridMMKVPlatformContextSpec_ = std::shared_ptr<HybridMMKVPlatformContextSpec>;
|
|
52
|
-
std::shared_ptr<HybridMMKVPlatformContextSpec> create_std__shared_ptr_HybridMMKVPlatformContextSpec_(void*
|
|
53
|
-
void*
|
|
52
|
+
std::shared_ptr<HybridMMKVPlatformContextSpec> create_std__shared_ptr_HybridMMKVPlatformContextSpec_(void* NON_NULL swiftUnsafePointer) noexcept;
|
|
53
|
+
void* NON_NULL get_std__shared_ptr_HybridMMKVPlatformContextSpec_(std__shared_ptr_HybridMMKVPlatformContextSpec_ cppType);
|
|
54
54
|
|
|
55
55
|
// pragma MARK: std::weak_ptr<HybridMMKVPlatformContextSpec>
|
|
56
56
|
using std__weak_ptr_HybridMMKVPlatformContextSpec_ = std::weak_ptr<HybridMMKVPlatformContextSpec>;
|
|
@@ -18,6 +18,13 @@ public protocol HybridMMKVPlatformContextSpec_protocol: HybridObject {
|
|
|
18
18
|
func getAppGroupDirectory() throws -> String?
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
public extension HybridMMKVPlatformContextSpec_protocol {
|
|
22
|
+
/// Default implementation of ``HybridObject.toString``
|
|
23
|
+
func toString() -> String {
|
|
24
|
+
return "[HybridObject MMKVPlatformContext]"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
/// See ``HybridMMKVPlatformContextSpec``
|
|
22
29
|
open class HybridMMKVPlatformContextSpec_base {
|
|
23
30
|
private weak var cxxWrapper: HybridMMKVPlatformContextSpec_cxx? = nil
|
|
@@ -76,7 +76,7 @@ open class HybridMMKVPlatformContextSpec_cxx {
|
|
|
76
76
|
*/
|
|
77
77
|
public func getCxxPart() -> bridge.std__shared_ptr_HybridMMKVPlatformContextSpec_ {
|
|
78
78
|
let cachedCxxPart = self.__cxxPart.lock()
|
|
79
|
-
if cachedCxxPart
|
|
79
|
+
if Bool(fromCxx: cachedCxxPart) {
|
|
80
80
|
return cachedCxxPart
|
|
81
81
|
} else {
|
|
82
82
|
let newCxxPart = bridge.create_std__shared_ptr_HybridMMKVPlatformContextSpec_(self.toUnsafe())
|
|
@@ -105,6 +105,14 @@ open class HybridMMKVPlatformContextSpec_cxx {
|
|
|
105
105
|
self.__implementation.dispose()
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Call toString() on the Swift class.
|
|
110
|
+
*/
|
|
111
|
+
@inline(__always)
|
|
112
|
+
public func toString() -> String {
|
|
113
|
+
return self.__implementation.toString()
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
// Properties
|
|
109
117
|
|
|
110
118
|
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
#else
|
|
18
18
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
19
|
#endif
|
|
20
|
+
#if __has_include(<NitroModules/JSIHelpers.hpp>)
|
|
21
|
+
#include <NitroModules/JSIHelpers.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
20
25
|
|
|
21
26
|
// Forward declaration of `Mode` to properly resolve imports.
|
|
22
27
|
namespace margelo::nitro::mmkv { enum class Mode; }
|
|
@@ -74,6 +79,9 @@ namespace margelo::nitro {
|
|
|
74
79
|
return false;
|
|
75
80
|
}
|
|
76
81
|
jsi::Object obj = value.getObject(runtime);
|
|
82
|
+
if (!nitro::isPlainObject(runtime, obj)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
77
85
|
if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "id"))) return false;
|
|
78
86
|
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "path"))) return false;
|
|
79
87
|
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "encryptionKey"))) return false;
|
|
@@ -14,6 +14,7 @@ namespace margelo::nitro::mmkv {
|
|
|
14
14
|
HybridObject::loadHybridMethods();
|
|
15
15
|
// load custom methods/properties
|
|
16
16
|
registerHybrids(this, [](Prototype& prototype) {
|
|
17
|
+
prototype.registerHybridGetter("id", &HybridMMKVSpec::getId);
|
|
17
18
|
prototype.registerHybridGetter("size", &HybridMMKVSpec::getSize);
|
|
18
19
|
prototype.registerHybridGetter("isReadOnly", &HybridMMKVSpec::getIsReadOnly);
|
|
19
20
|
prototype.registerHybridMethod("set", &HybridMMKVSpec::set);
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
14
|
#endif
|
|
15
15
|
|
|
16
|
-
// Forward declaration of `ArrayBuffer` to properly resolve imports.
|
|
17
|
-
namespace NitroModules { class ArrayBuffer; }
|
|
18
16
|
// Forward declaration of `Listener` to properly resolve imports.
|
|
19
17
|
namespace margelo::nitro::mmkv { struct Listener; }
|
|
20
18
|
|
|
@@ -53,12 +51,13 @@ namespace margelo::nitro::mmkv {
|
|
|
53
51
|
|
|
54
52
|
public:
|
|
55
53
|
// Properties
|
|
54
|
+
virtual std::string getId() = 0;
|
|
56
55
|
virtual double getSize() = 0;
|
|
57
56
|
virtual bool getIsReadOnly() = 0;
|
|
58
57
|
|
|
59
58
|
public:
|
|
60
59
|
// Methods
|
|
61
|
-
virtual void set(const std::string& key, const std::variant<
|
|
60
|
+
virtual void set(const std::string& key, const std::variant<bool, std::shared_ptr<ArrayBuffer>, std::string, double>& value) = 0;
|
|
62
61
|
virtual std::optional<bool> getBoolean(const std::string& key) = 0;
|
|
63
62
|
virtual std::optional<std::string> getString(const std::string& key) = 0;
|
|
64
63
|
virtual std::optional<double> getNumber(const std::string& key) = 0;
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
#else
|
|
18
18
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
19
|
#endif
|
|
20
|
+
#if __has_include(<NitroModules/JSIHelpers.hpp>)
|
|
21
|
+
#include <NitroModules/JSIHelpers.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
|
|
@@ -59,6 +64,9 @@ namespace margelo::nitro {
|
|
|
59
64
|
return false;
|
|
60
65
|
}
|
|
61
66
|
jsi::Object obj = value.getObject(runtime);
|
|
67
|
+
if (!nitro::isPlainObject(runtime, obj)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
62
70
|
if (!JSIConverter<std::function<void()>>::canConvert(runtime, obj.getProperty(runtime, "remove"))) return false;
|
|
63
71
|
return true;
|
|
64
72
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-mmkv",
|
|
3
|
-
"version": "4.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
|
+
"description": "⚡️ The fastest key/value storage for React Native.",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"module": "lib/index",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
|
-
"postinstall": "tsc || exit 0;",
|
|
35
34
|
"typecheck": "tsc --noEmit",
|
|
36
35
|
"clean": "rm -rf android/build node_modules/**/android/build lib",
|
|
37
36
|
"lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
|
|
@@ -61,18 +60,18 @@
|
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
62
|
"@expo/config-plugins": "^10.1.2",
|
|
64
|
-
"@react-native/eslint-config": "0.
|
|
63
|
+
"@react-native/eslint-config": "0.82.0",
|
|
65
64
|
"@testing-library/react-native": "^13.3.1",
|
|
66
65
|
"@types/jest": "^29.5.12",
|
|
67
66
|
"@types/react": "^19.0.6",
|
|
68
67
|
"eslint": "^8.57.0",
|
|
69
68
|
"eslint-config-prettier": "^9.1.0",
|
|
70
69
|
"eslint-plugin-prettier": "^5.2.1",
|
|
71
|
-
"nitrogen": "
|
|
70
|
+
"nitrogen": "0.31.7",
|
|
72
71
|
"prettier": "^3.3.3",
|
|
73
|
-
"react": "19.1.
|
|
74
|
-
"react-native": "0.
|
|
75
|
-
"react-native-nitro-modules": "
|
|
72
|
+
"react": "19.1.1",
|
|
73
|
+
"react-native": "0.82.0",
|
|
74
|
+
"react-native-nitro-modules": "0.31.7",
|
|
76
75
|
"typescript": "^5.8.3"
|
|
77
76
|
},
|
|
78
77
|
"peerDependencies": {
|
|
@@ -5,7 +5,7 @@ import type { MMKVPlatformContext } from '../specs/MMKVPlatformContext.nitro'
|
|
|
5
5
|
import { Platform } from 'react-native'
|
|
6
6
|
import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener'
|
|
7
7
|
import { isTest } from '../isTest'
|
|
8
|
-
import { createMockMMKV } from './
|
|
8
|
+
import { createMockMMKV } from './createMockMMKV'
|
|
9
9
|
|
|
10
10
|
let factory: MMKVFactory | undefined
|
|
11
11
|
let platformContext: MMKVPlatformContext | undefined
|
|
@@ -13,7 +13,7 @@ let platformContext: MMKVPlatformContext | undefined
|
|
|
13
13
|
export function createMMKV(configuration?: Configuration): MMKV {
|
|
14
14
|
if (isTest()) {
|
|
15
15
|
// In a test environment, we mock the MMKV instance.
|
|
16
|
-
return createMockMMKV()
|
|
16
|
+
return createMockMMKV(configuration)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
if (platformContext == null) {
|
|
@@ -19,7 +19,9 @@ const hasAccessToLocalStorage = () => {
|
|
|
19
19
|
const KEY_WILDCARD = '\\'
|
|
20
20
|
const inMemoryStorage = new Map<string, string>()
|
|
21
21
|
|
|
22
|
-
export function createMMKV(
|
|
22
|
+
export function createMMKV(
|
|
23
|
+
config: Configuration = { id: 'mmkv.default' }
|
|
24
|
+
): MMKV {
|
|
23
25
|
if (config.encryptionKey != null) {
|
|
24
26
|
throw new Error("MMKV: 'encryptionKey' is not supported on Web!")
|
|
25
27
|
}
|
|
@@ -78,18 +80,33 @@ export function createMMKV(config: Configuration): MMKV {
|
|
|
78
80
|
return `${keyPrefix}${key}`
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
const callListeners = (key: string) => {
|
|
84
|
+
listeners.forEach((l) => l(key))
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
return {
|
|
88
|
+
id: config.id,
|
|
89
|
+
size: 0,
|
|
90
|
+
isReadOnly: false,
|
|
82
91
|
clearAll: () => {
|
|
83
92
|
const keys = Object.keys(storage())
|
|
84
93
|
for (const key of keys) {
|
|
85
94
|
if (key.startsWith(keyPrefix)) {
|
|
86
95
|
storage().removeItem(key)
|
|
96
|
+
callListeners(key)
|
|
87
97
|
}
|
|
88
98
|
}
|
|
89
99
|
},
|
|
90
|
-
remove: (key) =>
|
|
100
|
+
remove: (key) => {
|
|
101
|
+
storage().removeItem(prefixedKey(key))
|
|
102
|
+
const wasRemoved = storage().getItem(prefixedKey(key)) === null
|
|
103
|
+
if (wasRemoved) callListeners(key)
|
|
104
|
+
return wasRemoved
|
|
105
|
+
},
|
|
91
106
|
set: (key, value) => {
|
|
107
|
+
if (key === '') throw new Error('Cannot set a value for an empty key!')
|
|
92
108
|
storage().setItem(prefixedKey(key), value.toString())
|
|
109
|
+
callListeners(key)
|
|
93
110
|
},
|
|
94
111
|
getString: (key) => storage().getItem(prefixedKey(key)) ?? undefined,
|
|
95
112
|
getNumber: (key) => {
|
|
@@ -117,8 +134,6 @@ export function createMMKV(config: Configuration): MMKV {
|
|
|
117
134
|
recrypt: () => {
|
|
118
135
|
throw new Error('`recrypt(..)` is not supported on Web!')
|
|
119
136
|
},
|
|
120
|
-
size: 0,
|
|
121
|
-
isReadOnly: false,
|
|
122
137
|
trim: () => {
|
|
123
138
|
// no-op
|
|
124
139
|
},
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { MMKV } from '../specs/MMKV.nitro'
|
|
2
|
+
import type { Configuration } from '../specs/MMKVFactory.nitro'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Mock MMKV instance when used in a Jest/Test environment.
|
|
5
6
|
*/
|
|
6
|
-
export function createMockMMKV(
|
|
7
|
+
export function createMockMMKV(
|
|
8
|
+
config: Configuration = { id: 'mmkv.default' }
|
|
9
|
+
): MMKV {
|
|
7
10
|
const storage = new Map<string, string | boolean | number | ArrayBuffer>()
|
|
8
11
|
const listeners = new Set<(key: string) => void>()
|
|
9
12
|
|
|
@@ -14,6 +17,11 @@ export function createMockMMKV(): MMKV {
|
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
return {
|
|
20
|
+
id: config.id,
|
|
21
|
+
get size(): number {
|
|
22
|
+
return storage.size
|
|
23
|
+
},
|
|
24
|
+
isReadOnly: false,
|
|
17
25
|
clearAll: () => {
|
|
18
26
|
const keysBefore = storage.keys()
|
|
19
27
|
storage.clear()
|
|
@@ -30,6 +38,7 @@ export function createMockMMKV(): MMKV {
|
|
|
30
38
|
return deleted
|
|
31
39
|
},
|
|
32
40
|
set: (key, value) => {
|
|
41
|
+
if (key === '') throw new Error('Cannot set a value for an empty key!')
|
|
33
42
|
storage.set(key, value)
|
|
34
43
|
notifyListeners(key)
|
|
35
44
|
},
|
|
@@ -54,10 +63,6 @@ export function createMockMMKV(): MMKV {
|
|
|
54
63
|
recrypt: () => {
|
|
55
64
|
console.warn('Encryption is not supported in mocked MMKV instances!')
|
|
56
65
|
},
|
|
57
|
-
get size(): number {
|
|
58
|
-
return storage.size
|
|
59
|
-
},
|
|
60
|
-
isReadOnly: false,
|
|
61
66
|
trim: () => {
|
|
62
67
|
// no-op
|
|
63
68
|
},
|
package/src/specs/MMKV.nitro.ts
CHANGED
|
@@ -6,9 +6,23 @@ export interface Listener {
|
|
|
6
6
|
|
|
7
7
|
export interface MMKV extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Get the ID of this {@linkcode MMKV} instance.
|
|
10
|
+
*/
|
|
11
|
+
readonly id: string
|
|
12
|
+
/**
|
|
13
|
+
* Get the current total size of the storage, in bytes.
|
|
14
|
+
*/
|
|
15
|
+
readonly size: number
|
|
16
|
+
/**
|
|
17
|
+
* Returns whether this instance is in read-only mode or not.
|
|
18
|
+
* If this is `true`, you can only use "get"-functions.
|
|
19
|
+
*/
|
|
20
|
+
readonly isReadOnly: boolean
|
|
21
|
+
/**
|
|
22
|
+
* Set a {@linkcode value} for the given {@linkcode key}.
|
|
10
23
|
*
|
|
11
|
-
* @throws an Error if the
|
|
24
|
+
* @throws an Error if the {@linkcode key} is empty.
|
|
25
|
+
* @throws an Error if the {@linkcode value} cannot be set.
|
|
12
26
|
*/
|
|
13
27
|
set(key: string, value: boolean | string | number | ArrayBuffer): void
|
|
14
28
|
/**
|
|
@@ -74,15 +88,6 @@ export interface MMKV extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
|
|
|
74
88
|
* In most applications, this is not needed at all.
|
|
75
89
|
*/
|
|
76
90
|
trim(): void
|
|
77
|
-
/**
|
|
78
|
-
* Get the current total size of the storage, in bytes.
|
|
79
|
-
*/
|
|
80
|
-
readonly size: number
|
|
81
|
-
/**
|
|
82
|
-
* Returns whether this instance is in read-only mode or not.
|
|
83
|
-
* If this is `true`, you can only use "get"-functions.
|
|
84
|
-
*/
|
|
85
|
-
readonly isReadOnly: boolean
|
|
86
91
|
/**
|
|
87
92
|
* Adds a value changed listener. The Listener will be called whenever any value
|
|
88
93
|
* in this storage instance changes (set or delete).
|
package/app.plugin.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('./lib/expo-plugin/withMMKV.cjs')
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const { createRunOncePlugin, withGradleProperties } = require('@expo/config-plugins');
|
|
3
|
-
const pkg = require('../../package.json');
|
|
4
|
-
const withMMKV = (config) => {
|
|
5
|
-
// remove 32-bit architectures from gradle.properties
|
|
6
|
-
return withGradleProperties(config, (cfg) => {
|
|
7
|
-
// Define the wanted property
|
|
8
|
-
const property = {
|
|
9
|
-
type: 'property',
|
|
10
|
-
key: 'reactNativeArchitectures',
|
|
11
|
-
value: 'arm64-v8a,x86_64',
|
|
12
|
-
};
|
|
13
|
-
// If it exists, update its value
|
|
14
|
-
const index = cfg.modResults.findIndex((p) => p.type === 'property' && p.key === property.key);
|
|
15
|
-
if (index !== -1) {
|
|
16
|
-
// Overwrite it
|
|
17
|
-
cfg.modResults[index] = property;
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
// Append it
|
|
21
|
-
cfg.modResults.push(property);
|
|
22
|
-
}
|
|
23
|
-
return cfg;
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
module.exports = createRunOncePlugin(withMMKV, pkg.name, pkg.version);
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { createRunOncePlugin, withGradleProperties } from '@expo/config-plugins';
|
|
2
|
-
const pkg = require('../../package.json');
|
|
3
|
-
const withMMKV = (config) => {
|
|
4
|
-
// remove 32-bit architectures from gradle.properties
|
|
5
|
-
return withGradleProperties(config, (cfg) => {
|
|
6
|
-
// Drop any existing entry…
|
|
7
|
-
cfg.modResults = cfg.modResults.filter((p) => !(p.type === 'property' && p.key === 'reactNativeArchitectures'));
|
|
8
|
-
// …and force 64-bit only.
|
|
9
|
-
cfg.modResults.push({
|
|
10
|
-
type: 'property',
|
|
11
|
-
key: 'reactNativeArchitectures',
|
|
12
|
-
value: 'arm64-v8a,x86_64',
|
|
13
|
-
});
|
|
14
|
-
return cfg;
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
|
-
export default createRunOncePlugin(withMMKV, pkg.name, pkg.version);
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ConfigPlugin, ExportedConfigWithProps } from '@expo/config-plugins'
|
|
2
|
-
const { createRunOncePlugin, withGradleProperties } = require('@expo/config-plugins')
|
|
3
|
-
import type { Properties } from '@expo/config-plugins/build/android'
|
|
4
|
-
|
|
5
|
-
const pkg = require('../../package.json')
|
|
6
|
-
|
|
7
|
-
const withMMKV: ConfigPlugin<{}> = (config) => {
|
|
8
|
-
// remove 32-bit architectures from gradle.properties
|
|
9
|
-
return withGradleProperties(config, (cfg: ExportedConfigWithProps<Properties.PropertiesItem[]>) => {
|
|
10
|
-
// Define the wanted property
|
|
11
|
-
const property = {
|
|
12
|
-
type: 'property',
|
|
13
|
-
key: 'reactNativeArchitectures',
|
|
14
|
-
value: 'arm64-v8a,x86_64',
|
|
15
|
-
} as const
|
|
16
|
-
// If it exists, update its value
|
|
17
|
-
const index = cfg.modResults.findIndex(
|
|
18
|
-
(p) => p.type === 'property' && p.key === property.key
|
|
19
|
-
)
|
|
20
|
-
if (index !== -1) {
|
|
21
|
-
// Overwrite it
|
|
22
|
-
cfg.modResults[index] = property
|
|
23
|
-
} else {
|
|
24
|
-
// Append it
|
|
25
|
-
cfg.modResults.push(property)
|
|
26
|
-
}
|
|
27
|
-
return cfg
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export = createRunOncePlugin(withMMKV, pkg.name, pkg.version) as ConfigPlugin<{}>
|