strata-storage 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,9 @@ import androidx.security.crypto.MasterKey;
8
8
  import java.util.Set;
9
9
  import java.util.HashSet;
10
10
  import java.util.Map;
11
+ import java.util.List;
12
+ import java.util.ArrayList;
13
+ import org.json.JSONObject;
11
14
 
12
15
  public class EncryptedStorage {
13
16
  private SharedPreferences encryptedPrefs;
@@ -42,9 +45,23 @@ public class EncryptedStorage {
42
45
  }
43
46
  }
44
47
 
45
- public boolean set(String key, String value) {
46
- editor.putString(key, value);
47
- return editor.commit();
48
+ public boolean set(String key, Object value) {
49
+ try {
50
+ String stringValue;
51
+ if (value instanceof String) {
52
+ stringValue = (String) value;
53
+ } else {
54
+ // Convert complex objects to JSON
55
+ stringValue = value instanceof JSONObject ?
56
+ ((JSONObject) value).toString() :
57
+ new JSONObject(value).toString();
58
+ }
59
+ editor.putString(key, stringValue);
60
+ return editor.commit();
61
+ } catch (Exception e) {
62
+ e.printStackTrace();
63
+ return false;
64
+ }
48
65
  }
49
66
 
50
67
  public String get(String key) {
@@ -79,19 +96,19 @@ public class EncryptedStorage {
79
96
  return editor.commit();
80
97
  }
81
98
 
82
- public Set<String> keys() {
99
+ public List<String> keys() {
83
100
  return keys(null);
84
101
  }
85
102
 
86
- public Set<String> keys(String pattern) {
103
+ public List<String> keys(String pattern) {
87
104
  Set<String> allKeys = encryptedPrefs.getAll().keySet();
88
105
 
89
106
  if (pattern == null) {
90
- return allKeys;
107
+ return new ArrayList<>(allKeys);
91
108
  }
92
109
 
93
110
  // Filter keys by pattern
94
- Set<String> filteredKeys = new HashSet<>();
111
+ List<String> filteredKeys = new ArrayList<>();
95
112
  for (String key : allKeys) {
96
113
  if (key.startsWith(pattern) || key.contains(pattern)) {
97
114
  filteredKeys.add(key);
@@ -103,4 +120,36 @@ public class EncryptedStorage {
103
120
  public boolean has(String key) {
104
121
  return encryptedPrefs.contains(key);
105
122
  }
123
+
124
+ public SizeInfo size() {
125
+ Map<String, ?> all = encryptedPrefs.getAll();
126
+ long totalSize = 0;
127
+ int count = all.size();
128
+
129
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
130
+ String key = entry.getKey();
131
+ Object value = entry.getValue();
132
+
133
+ // Estimate size (key + value in bytes)
134
+ totalSize += key.getBytes().length;
135
+ if (value != null) {
136
+ totalSize += value.toString().getBytes().length;
137
+ }
138
+ }
139
+
140
+ return new SizeInfo(totalSize, count);
141
+ }
142
+
143
+ /**
144
+ * Size information class
145
+ */
146
+ public static class SizeInfo {
147
+ public final long total;
148
+ public final int count;
149
+
150
+ public SizeInfo(long total, int count) {
151
+ this.total = total;
152
+ this.count = count;
153
+ }
154
+ }
106
155
  }
@@ -9,6 +9,8 @@ import java.util.ArrayList;
9
9
  import java.util.List;
10
10
  import java.util.HashMap;
11
11
  import java.util.Map;
12
+ import org.json.JSONObject;
13
+ import java.nio.charset.StandardCharsets;
12
14
 
13
15
  public class SQLiteStorage extends SQLiteOpenHelper {
14
16
  private static final int DATABASE_VERSION = 1;
@@ -50,13 +52,31 @@ public class SQLiteStorage extends SQLiteOpenHelper {
50
52
  onCreate(db);
51
53
  }
52
54
 
53
- public boolean set(String key, byte[] value, Long expires, String tags, String metadata) {
55
+ public boolean set(String key, Object value, Long expires, String tags, String metadata) {
56
+ byte[] valueBytes;
57
+
58
+ try {
59
+ if (value instanceof byte[]) {
60
+ valueBytes = (byte[]) value;
61
+ } else if (value instanceof String) {
62
+ valueBytes = ((String) value).getBytes(StandardCharsets.UTF_8);
63
+ } else {
64
+ // Convert complex objects to JSON then to bytes
65
+ String json = value instanceof JSONObject ?
66
+ ((JSONObject) value).toString() :
67
+ new JSONObject(value).toString();
68
+ valueBytes = json.getBytes(StandardCharsets.UTF_8);
69
+ }
70
+ } catch (Exception e) {
71
+ e.printStackTrace();
72
+ return false;
73
+ }
54
74
  SQLiteDatabase db = this.getWritableDatabase();
55
75
  ContentValues values = new ContentValues();
56
76
 
57
77
  long now = System.currentTimeMillis();
58
78
  values.put(KEY_ID, key);
59
- values.put(KEY_VALUE, value);
79
+ values.put(KEY_VALUE, valueBytes);
60
80
  values.put(KEY_CREATED, now);
61
81
  values.put(KEY_UPDATED, now);
62
82
 
@@ -75,6 +95,11 @@ public class SQLiteStorage extends SQLiteOpenHelper {
75
95
  return result != -1;
76
96
  }
77
97
 
98
+ // Convenience method for simple Object values
99
+ public boolean set(String key, Object value) {
100
+ return set(key, value, null, null, null);
101
+ }
102
+
78
103
  public Map<String, Object> get(String key) {
79
104
  SQLiteDatabase db = this.getReadableDatabase();
80
105
  Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
@@ -174,4 +199,34 @@ public class SQLiteStorage extends SQLiteOpenHelper {
174
199
  db.close();
175
200
  return exists;
176
201
  }
202
+
203
+ public SizeInfo size() {
204
+ SQLiteDatabase db = this.getReadableDatabase();
205
+ Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
206
+
207
+ long totalSize = 0;
208
+ int count = 0;
209
+
210
+ if (cursor != null && cursor.moveToFirst()) {
211
+ count = cursor.getInt(0);
212
+ totalSize = cursor.getLong(1);
213
+ cursor.close();
214
+ }
215
+
216
+ db.close();
217
+ return new SizeInfo(totalSize, count);
218
+ }
219
+
220
+ /**
221
+ * Size information class
222
+ */
223
+ public static class SizeInfo {
224
+ public final long total;
225
+ public final int count;
226
+
227
+ public SizeInfo(long total, int count) {
228
+ this.total = total;
229
+ this.count = count;
230
+ }
231
+ }
177
232
  }
@@ -5,6 +5,8 @@ import android.content.SharedPreferences;
5
5
  import java.util.Map;
6
6
  import java.util.Set;
7
7
  import java.util.HashSet;
8
+ import java.util.List;
9
+ import java.util.ArrayList;
8
10
  import org.json.JSONObject;
9
11
  import org.json.JSONArray;
10
12
 
@@ -83,19 +85,19 @@ public class SharedPreferencesStorage {
83
85
  return editor.commit();
84
86
  }
85
87
 
86
- public Set<String> keys() {
88
+ public List<String> keys() {
87
89
  return keys(null);
88
90
  }
89
91
 
90
- public Set<String> keys(String pattern) {
92
+ public List<String> keys(String pattern) {
91
93
  Set<String> allKeys = prefs.getAll().keySet();
92
94
 
93
95
  if (pattern == null) {
94
- return allKeys;
96
+ return new ArrayList<>(allKeys);
95
97
  }
96
98
 
97
99
  // Filter keys by pattern
98
- Set<String> filteredKeys = new HashSet<>();
100
+ List<String> filteredKeys = new ArrayList<>();
99
101
  for (String key : allKeys) {
100
102
  if (key.startsWith(pattern) || key.contains(pattern)) {
101
103
  filteredKeys.add(key);
@@ -111,4 +113,36 @@ public class SharedPreferencesStorage {
111
113
  public Map<String, ?> getAll() {
112
114
  return prefs.getAll();
113
115
  }
116
+
117
+ public SizeInfo size() {
118
+ Map<String, ?> all = prefs.getAll();
119
+ long totalSize = 0;
120
+ int count = all.size();
121
+
122
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
123
+ String key = entry.getKey();
124
+ Object value = entry.getValue();
125
+
126
+ // Estimate size (key + value in bytes)
127
+ totalSize += key.getBytes().length;
128
+ if (value != null) {
129
+ totalSize += value.toString().getBytes().length;
130
+ }
131
+ }
132
+
133
+ return new SizeInfo(totalSize, count);
134
+ }
135
+
136
+ /**
137
+ * Size information class
138
+ */
139
+ public static class SizeInfo {
140
+ public final long total;
141
+ public final int count;
142
+
143
+ public SizeInfo(long total, int count) {
144
+ this.total = total;
145
+ this.count = count;
146
+ }
147
+ }
114
148
  }
@@ -11,7 +11,6 @@ import com.strata.storage.SQLiteStorage;
11
11
  import org.json.JSONArray;
12
12
  import org.json.JSONException;
13
13
  import java.util.List;
14
- import java.util.Set;
15
14
 
16
15
  /**
17
16
  * Main Capacitor plugin for Strata Storage
@@ -226,40 +225,31 @@ public class StrataStoragePlugin extends Plugin {
226
225
  String storage = call.getString("storage", "preferences");
227
226
 
228
227
  try {
229
- SizeInfo sizeInfo = null;
228
+ JSObject result = new JSObject();
230
229
 
231
230
  switch (storage) {
232
231
  case "secure":
233
- sizeInfo = encryptedStorage.size();
232
+ EncryptedStorage.SizeInfo encryptedSizeInfo = encryptedStorage.size();
233
+ result.put("total", encryptedSizeInfo.total);
234
+ result.put("count", encryptedSizeInfo.count);
234
235
  break;
235
236
  case "sqlite":
236
- sizeInfo = sqliteStorage.size();
237
+ SQLiteStorage.SizeInfo sqliteSizeInfo = sqliteStorage.size();
238
+ result.put("total", sqliteSizeInfo.total);
239
+ result.put("count", sqliteSizeInfo.count);
237
240
  break;
238
241
  case "preferences":
239
242
  default:
240
- sizeInfo = sharedPrefsStorage.size();
243
+ SharedPreferencesStorage.SizeInfo prefsSizeInfo = sharedPrefsStorage.size();
244
+ result.put("total", prefsSizeInfo.total);
245
+ result.put("count", prefsSizeInfo.count);
241
246
  break;
242
247
  }
243
248
 
244
- JSObject result = new JSObject();
245
- result.put("total", sizeInfo.total);
246
- result.put("count", sizeInfo.count);
247
249
  call.resolve(result);
248
250
  } catch (Exception e) {
249
251
  call.reject("Failed to get size", e);
250
252
  }
251
253
  }
252
254
 
253
- /**
254
- * Size information class
255
- */
256
- static class SizeInfo {
257
- public final long total;
258
- public final int count;
259
-
260
- public SizeInfo(long total, int count) {
261
- this.total = total;
262
- this.count = count;
263
- }
264
- }
265
255
  }
@@ -8,6 +8,9 @@ import androidx.security.crypto.MasterKey;
8
8
  import java.util.Set;
9
9
  import java.util.HashSet;
10
10
  import java.util.Map;
11
+ import java.util.List;
12
+ import java.util.ArrayList;
13
+ import org.json.JSONObject;
11
14
 
12
15
  public class EncryptedStorage {
13
16
  private SharedPreferences encryptedPrefs;
@@ -42,9 +45,23 @@ public class EncryptedStorage {
42
45
  }
43
46
  }
44
47
 
45
- public boolean set(String key, String value) {
46
- editor.putString(key, value);
47
- return editor.commit();
48
+ public boolean set(String key, Object value) {
49
+ try {
50
+ String stringValue;
51
+ if (value instanceof String) {
52
+ stringValue = (String) value;
53
+ } else {
54
+ // Convert complex objects to JSON
55
+ stringValue = value instanceof JSONObject ?
56
+ ((JSONObject) value).toString() :
57
+ new JSONObject(value).toString();
58
+ }
59
+ editor.putString(key, stringValue);
60
+ return editor.commit();
61
+ } catch (Exception e) {
62
+ e.printStackTrace();
63
+ return false;
64
+ }
48
65
  }
49
66
 
50
67
  public String get(String key) {
@@ -79,19 +96,19 @@ public class EncryptedStorage {
79
96
  return editor.commit();
80
97
  }
81
98
 
82
- public Set<String> keys() {
99
+ public List<String> keys() {
83
100
  return keys(null);
84
101
  }
85
102
 
86
- public Set<String> keys(String pattern) {
103
+ public List<String> keys(String pattern) {
87
104
  Set<String> allKeys = encryptedPrefs.getAll().keySet();
88
105
 
89
106
  if (pattern == null) {
90
- return allKeys;
107
+ return new ArrayList<>(allKeys);
91
108
  }
92
109
 
93
110
  // Filter keys by pattern
94
- Set<String> filteredKeys = new HashSet<>();
111
+ List<String> filteredKeys = new ArrayList<>();
95
112
  for (String key : allKeys) {
96
113
  if (key.startsWith(pattern) || key.contains(pattern)) {
97
114
  filteredKeys.add(key);
@@ -103,4 +120,36 @@ public class EncryptedStorage {
103
120
  public boolean has(String key) {
104
121
  return encryptedPrefs.contains(key);
105
122
  }
123
+
124
+ public SizeInfo size() {
125
+ Map<String, ?> all = encryptedPrefs.getAll();
126
+ long totalSize = 0;
127
+ int count = all.size();
128
+
129
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
130
+ String key = entry.getKey();
131
+ Object value = entry.getValue();
132
+
133
+ // Estimate size (key + value in bytes)
134
+ totalSize += key.getBytes().length;
135
+ if (value != null) {
136
+ totalSize += value.toString().getBytes().length;
137
+ }
138
+ }
139
+
140
+ return new SizeInfo(totalSize, count);
141
+ }
142
+
143
+ /**
144
+ * Size information class
145
+ */
146
+ public static class SizeInfo {
147
+ public final long total;
148
+ public final int count;
149
+
150
+ public SizeInfo(long total, int count) {
151
+ this.total = total;
152
+ this.count = count;
153
+ }
154
+ }
106
155
  }
@@ -9,6 +9,8 @@ import java.util.ArrayList;
9
9
  import java.util.List;
10
10
  import java.util.HashMap;
11
11
  import java.util.Map;
12
+ import org.json.JSONObject;
13
+ import java.nio.charset.StandardCharsets;
12
14
 
13
15
  public class SQLiteStorage extends SQLiteOpenHelper {
14
16
  private static final int DATABASE_VERSION = 1;
@@ -50,13 +52,31 @@ public class SQLiteStorage extends SQLiteOpenHelper {
50
52
  onCreate(db);
51
53
  }
52
54
 
53
- public boolean set(String key, byte[] value, Long expires, String tags, String metadata) {
55
+ public boolean set(String key, Object value, Long expires, String tags, String metadata) {
56
+ byte[] valueBytes;
57
+
58
+ try {
59
+ if (value instanceof byte[]) {
60
+ valueBytes = (byte[]) value;
61
+ } else if (value instanceof String) {
62
+ valueBytes = ((String) value).getBytes(StandardCharsets.UTF_8);
63
+ } else {
64
+ // Convert complex objects to JSON then to bytes
65
+ String json = value instanceof JSONObject ?
66
+ ((JSONObject) value).toString() :
67
+ new JSONObject(value).toString();
68
+ valueBytes = json.getBytes(StandardCharsets.UTF_8);
69
+ }
70
+ } catch (Exception e) {
71
+ e.printStackTrace();
72
+ return false;
73
+ }
54
74
  SQLiteDatabase db = this.getWritableDatabase();
55
75
  ContentValues values = new ContentValues();
56
76
 
57
77
  long now = System.currentTimeMillis();
58
78
  values.put(KEY_ID, key);
59
- values.put(KEY_VALUE, value);
79
+ values.put(KEY_VALUE, valueBytes);
60
80
  values.put(KEY_CREATED, now);
61
81
  values.put(KEY_UPDATED, now);
62
82
 
@@ -75,6 +95,11 @@ public class SQLiteStorage extends SQLiteOpenHelper {
75
95
  return result != -1;
76
96
  }
77
97
 
98
+ // Convenience method for simple Object values
99
+ public boolean set(String key, Object value) {
100
+ return set(key, value, null, null, null);
101
+ }
102
+
78
103
  public Map<String, Object> get(String key) {
79
104
  SQLiteDatabase db = this.getReadableDatabase();
80
105
  Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
@@ -174,4 +199,34 @@ public class SQLiteStorage extends SQLiteOpenHelper {
174
199
  db.close();
175
200
  return exists;
176
201
  }
202
+
203
+ public SizeInfo size() {
204
+ SQLiteDatabase db = this.getReadableDatabase();
205
+ Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
206
+
207
+ long totalSize = 0;
208
+ int count = 0;
209
+
210
+ if (cursor != null && cursor.moveToFirst()) {
211
+ count = cursor.getInt(0);
212
+ totalSize = cursor.getLong(1);
213
+ cursor.close();
214
+ }
215
+
216
+ db.close();
217
+ return new SizeInfo(totalSize, count);
218
+ }
219
+
220
+ /**
221
+ * Size information class
222
+ */
223
+ public static class SizeInfo {
224
+ public final long total;
225
+ public final int count;
226
+
227
+ public SizeInfo(long total, int count) {
228
+ this.total = total;
229
+ this.count = count;
230
+ }
231
+ }
177
232
  }
@@ -5,6 +5,8 @@ import android.content.SharedPreferences;
5
5
  import java.util.Map;
6
6
  import java.util.Set;
7
7
  import java.util.HashSet;
8
+ import java.util.List;
9
+ import java.util.ArrayList;
8
10
  import org.json.JSONObject;
9
11
  import org.json.JSONArray;
10
12
 
@@ -83,19 +85,19 @@ public class SharedPreferencesStorage {
83
85
  return editor.commit();
84
86
  }
85
87
 
86
- public Set<String> keys() {
88
+ public List<String> keys() {
87
89
  return keys(null);
88
90
  }
89
91
 
90
- public Set<String> keys(String pattern) {
92
+ public List<String> keys(String pattern) {
91
93
  Set<String> allKeys = prefs.getAll().keySet();
92
94
 
93
95
  if (pattern == null) {
94
- return allKeys;
96
+ return new ArrayList<>(allKeys);
95
97
  }
96
98
 
97
99
  // Filter keys by pattern
98
- Set<String> filteredKeys = new HashSet<>();
100
+ List<String> filteredKeys = new ArrayList<>();
99
101
  for (String key : allKeys) {
100
102
  if (key.startsWith(pattern) || key.contains(pattern)) {
101
103
  filteredKeys.add(key);
@@ -111,4 +113,36 @@ public class SharedPreferencesStorage {
111
113
  public Map<String, ?> getAll() {
112
114
  return prefs.getAll();
113
115
  }
116
+
117
+ public SizeInfo size() {
118
+ Map<String, ?> all = prefs.getAll();
119
+ long totalSize = 0;
120
+ int count = all.size();
121
+
122
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
123
+ String key = entry.getKey();
124
+ Object value = entry.getValue();
125
+
126
+ // Estimate size (key + value in bytes)
127
+ totalSize += key.getBytes().length;
128
+ if (value != null) {
129
+ totalSize += value.toString().getBytes().length;
130
+ }
131
+ }
132
+
133
+ return new SizeInfo(totalSize, count);
134
+ }
135
+
136
+ /**
137
+ * Size information class
138
+ */
139
+ public static class SizeInfo {
140
+ public final long total;
141
+ public final int count;
142
+
143
+ public SizeInfo(long total, int count) {
144
+ this.total = total;
145
+ this.count = count;
146
+ }
147
+ }
114
148
  }
@@ -11,7 +11,6 @@ import com.strata.storage.SQLiteStorage;
11
11
  import org.json.JSONArray;
12
12
  import org.json.JSONException;
13
13
  import java.util.List;
14
- import java.util.Set;
15
14
 
16
15
  /**
17
16
  * Main Capacitor plugin for Strata Storage
@@ -226,40 +225,31 @@ public class StrataStoragePlugin extends Plugin {
226
225
  String storage = call.getString("storage", "preferences");
227
226
 
228
227
  try {
229
- SizeInfo sizeInfo = null;
228
+ JSObject result = new JSObject();
230
229
 
231
230
  switch (storage) {
232
231
  case "secure":
233
- sizeInfo = encryptedStorage.size();
232
+ EncryptedStorage.SizeInfo encryptedSizeInfo = encryptedStorage.size();
233
+ result.put("total", encryptedSizeInfo.total);
234
+ result.put("count", encryptedSizeInfo.count);
234
235
  break;
235
236
  case "sqlite":
236
- sizeInfo = sqliteStorage.size();
237
+ SQLiteStorage.SizeInfo sqliteSizeInfo = sqliteStorage.size();
238
+ result.put("total", sqliteSizeInfo.total);
239
+ result.put("count", sqliteSizeInfo.count);
237
240
  break;
238
241
  case "preferences":
239
242
  default:
240
- sizeInfo = sharedPrefsStorage.size();
243
+ SharedPreferencesStorage.SizeInfo prefsSizeInfo = sharedPrefsStorage.size();
244
+ result.put("total", prefsSizeInfo.total);
245
+ result.put("count", prefsSizeInfo.count);
241
246
  break;
242
247
  }
243
248
 
244
- JSObject result = new JSObject();
245
- result.put("total", sizeInfo.total);
246
- result.put("count", sizeInfo.count);
247
249
  call.resolve(result);
248
250
  } catch (Exception e) {
249
251
  call.reject("Failed to get size", e);
250
252
  }
251
253
  }
252
254
 
253
- /**
254
- * Size information class
255
- */
256
- static class SizeInfo {
257
- public final long total;
258
- public final int count;
259
-
260
- public SizeInfo(long total, int count) {
261
- this.total = total;
262
- this.count = count;
263
- }
264
- }
265
255
  }
@@ -11,18 +11,30 @@ import Security
11
11
  super.init()
12
12
  }
13
13
 
14
- @objc public func set(key: String, value: Data) -> Bool {
14
+ @objc public func set(key: String, value: Any) throws -> Bool {
15
+ let data: Data
16
+
17
+ if let dataValue = value as? Data {
18
+ data = dataValue
19
+ } else if let stringValue = value as? String {
20
+ data = stringValue.data(using: .utf8) ?? Data()
21
+ } else {
22
+ // Convert to JSON for complex objects
23
+ let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
24
+ data = jsonData
25
+ }
26
+
15
27
  let query = createQuery(key: key)
16
28
  SecItemDelete(query as CFDictionary)
17
29
 
18
30
  var newItem = query
19
- newItem[kSecValueData as String] = value
31
+ newItem[kSecValueData as String] = data
20
32
 
21
33
  let status = SecItemAdd(newItem as CFDictionary, nil)
22
34
  return status == errSecSuccess
23
35
  }
24
36
 
25
- @objc public func get(key: String) -> Data? {
37
+ @objc public func get(key: String) throws -> Any? {
26
38
  var query = createQuery(key: key)
27
39
  query[kSecReturnData as String] = true
28
40
  query[kSecMatchLimit as String] = kSecMatchLimitOne
@@ -31,22 +43,29 @@ import Security
31
43
  let status = SecItemCopyMatching(query as CFDictionary, &result)
32
44
 
33
45
  guard status == errSecSuccess else { return nil }
34
- return result as? Data
46
+ guard let data = result as? Data else { return nil }
47
+
48
+ // Try to parse as JSON first, fallback to string
49
+ if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) {
50
+ return jsonObject
51
+ } else {
52
+ return String(data: data, encoding: .utf8)
53
+ }
35
54
  }
36
55
 
37
- @objc public func remove(key: String) -> Bool {
56
+ @objc public func remove(key: String) throws -> Bool {
38
57
  let query = createQuery(key: key)
39
58
  let status = SecItemDelete(query as CFDictionary)
40
59
  return status == errSecSuccess
41
60
  }
42
61
 
43
- @objc public func clear(prefix: String? = nil) -> Bool {
62
+ @objc public func clear(prefix: String? = nil) throws -> Bool {
44
63
  if let prefix = prefix {
45
64
  // Clear only keys with the given prefix
46
- let keysToRemove = keys(pattern: prefix)
65
+ let keysToRemove = try keys(pattern: prefix)
47
66
  var allSuccess = true
48
67
  for key in keysToRemove {
49
- if !remove(key: key) {
68
+ if !(try remove(key: key)) {
50
69
  allSuccess = false
51
70
  }
52
71
  }
@@ -62,7 +81,7 @@ import Security
62
81
  }
63
82
  }
64
83
 
65
- @objc public func keys(pattern: String? = nil) -> [String] {
84
+ @objc public func keys(pattern: String? = nil) throws -> [String] {
66
85
  var query: [String: Any] = [
67
86
  kSecClass as String: kSecClassGenericPassword,
68
87
  kSecAttrService as String: service,
@@ -106,4 +125,20 @@ import Security
106
125
 
107
126
  return query
108
127
  }
128
+
129
+ @objc public func size() throws -> (total: Int, count: Int) {
130
+ let allKeys = try keys()
131
+ var totalSize = 0
132
+
133
+ for key in allKeys {
134
+ if let data = try get(key: key) as? Data {
135
+ totalSize += data.count
136
+ } else if let string = try get(key: key) as? String {
137
+ totalSize += string.data(using: .utf8)?.count ?? 0
138
+ }
139
+ totalSize += key.data(using: .utf8)?.count ?? 0
140
+ }
141
+
142
+ return (total: totalSize, count: allKeys.count)
143
+ }
109
144
  }
@@ -53,7 +53,18 @@ import SQLite3
53
53
  sqlite3_finalize(createTableStatement)
54
54
  }
55
55
 
56
- @objc public func set(key: String, value: Data, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) -> Bool {
56
+ @objc public func set(key: String, value: Any, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) throws -> Bool {
57
+ let data: Data
58
+
59
+ if let dataValue = value as? Data {
60
+ data = dataValue
61
+ } else if let stringValue = value as? String {
62
+ data = stringValue.data(using: .utf8) ?? Data()
63
+ } else {
64
+ // Convert to JSON for complex objects
65
+ data = try JSONSerialization.data(withJSONObject: value, options: [])
66
+ }
67
+
57
68
  let now = Int64(Date().timeIntervalSince1970 * 1000)
58
69
  let tagsJson = tags != nil ? try? JSONSerialization.data(withJSONObject: tags!, options: []) : nil
59
70
  let metadataJson = metadata != nil ? try? JSONSerialization.data(withJSONObject: metadata!, options: []) : nil
@@ -67,7 +78,7 @@ import SQLite3
67
78
  var statement: OpaquePointer?
68
79
  let result = sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) == SQLITE_OK &&
69
80
  sqlite3_bind_text(statement, 1, key, -1, nil) == SQLITE_OK &&
70
- sqlite3_bind_blob(statement, 2, (value as NSData).bytes, Int32(value.count), nil) == SQLITE_OK &&
81
+ sqlite3_bind_blob(statement, 2, (data as NSData).bytes, Int32(data.count), nil) == SQLITE_OK &&
71
82
  sqlite3_bind_int64(statement, 3, now) == SQLITE_OK &&
72
83
  sqlite3_bind_int64(statement, 4, now) == SQLITE_OK &&
73
84
  (expires != nil ? sqlite3_bind_int64(statement, 5, expires!) : sqlite3_bind_null(statement, 5)) == SQLITE_OK &&
@@ -79,6 +90,11 @@ import SQLite3
79
90
  return result
80
91
  }
81
92
 
93
+ // Convenience method for simple values
94
+ @objc public func set(key: String, value: Any) throws -> Bool {
95
+ return try set(key: key, value: value, expires: nil, tags: nil, metadata: nil)
96
+ }
97
+
82
98
  @objc public func get(key: String) -> [String: Any]? {
83
99
  let querySQL = "SELECT * FROM \(tableName) WHERE key = ? LIMIT 1"
84
100
  var statement: OpaquePointer?
@@ -187,4 +203,22 @@ import SQLite3
187
203
  sqlite3_finalize(statement)
188
204
  return keys
189
205
  }
206
+
207
+ @objc public func size() throws -> (total: Int, count: Int) {
208
+ let querySQL = "SELECT COUNT(*), SUM(LENGTH(value)) FROM \(tableName)"
209
+ var statement: OpaquePointer?
210
+
211
+ var totalSize = 0
212
+ var count = 0
213
+
214
+ if sqlite3_prepare_v2(db, querySQL, -1, &statement, nil) == SQLITE_OK {
215
+ if sqlite3_step(statement) == SQLITE_ROW {
216
+ count = Int(sqlite3_column_int(statement, 0))
217
+ totalSize = Int(sqlite3_column_int64(statement, 1))
218
+ }
219
+ }
220
+
221
+ sqlite3_finalize(statement)
222
+ return (total: totalSize, count: count)
223
+ }
190
224
  }
@@ -59,4 +59,18 @@ import Foundation
59
59
  @objc public func has(key: String) -> Bool {
60
60
  return userDefaults.object(forKey: key) != nil
61
61
  }
62
+
63
+ @objc public func size() -> (total: Int, count: Int) {
64
+ let all = userDefaults.dictionaryRepresentation()
65
+ var totalSize = 0
66
+ let count = all.count
67
+
68
+ for (key, value) in all {
69
+ // Estimate size (key + value in bytes)
70
+ totalSize += key.data(using: .utf8)?.count ?? 0
71
+ totalSize += "\(value)".data(using: .utf8)?.count ?? 0
72
+ }
73
+
74
+ return (total: totalSize, count: count)
75
+ }
62
76
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strata-storage",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -11,18 +11,30 @@ import Security
11
11
  super.init()
12
12
  }
13
13
 
14
- @objc public func set(key: String, value: Data) -> Bool {
14
+ @objc public func set(key: String, value: Any) throws -> Bool {
15
+ let data: Data
16
+
17
+ if let dataValue = value as? Data {
18
+ data = dataValue
19
+ } else if let stringValue = value as? String {
20
+ data = stringValue.data(using: .utf8) ?? Data()
21
+ } else {
22
+ // Convert to JSON for complex objects
23
+ let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
24
+ data = jsonData
25
+ }
26
+
15
27
  let query = createQuery(key: key)
16
28
  SecItemDelete(query as CFDictionary)
17
29
 
18
30
  var newItem = query
19
- newItem[kSecValueData as String] = value
31
+ newItem[kSecValueData as String] = data
20
32
 
21
33
  let status = SecItemAdd(newItem as CFDictionary, nil)
22
34
  return status == errSecSuccess
23
35
  }
24
36
 
25
- @objc public func get(key: String) -> Data? {
37
+ @objc public func get(key: String) throws -> Any? {
26
38
  var query = createQuery(key: key)
27
39
  query[kSecReturnData as String] = true
28
40
  query[kSecMatchLimit as String] = kSecMatchLimitOne
@@ -31,22 +43,29 @@ import Security
31
43
  let status = SecItemCopyMatching(query as CFDictionary, &result)
32
44
 
33
45
  guard status == errSecSuccess else { return nil }
34
- return result as? Data
46
+ guard let data = result as? Data else { return nil }
47
+
48
+ // Try to parse as JSON first, fallback to string
49
+ if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) {
50
+ return jsonObject
51
+ } else {
52
+ return String(data: data, encoding: .utf8)
53
+ }
35
54
  }
36
55
 
37
- @objc public func remove(key: String) -> Bool {
56
+ @objc public func remove(key: String) throws -> Bool {
38
57
  let query = createQuery(key: key)
39
58
  let status = SecItemDelete(query as CFDictionary)
40
59
  return status == errSecSuccess
41
60
  }
42
61
 
43
- @objc public func clear(prefix: String? = nil) -> Bool {
62
+ @objc public func clear(prefix: String? = nil) throws -> Bool {
44
63
  if let prefix = prefix {
45
64
  // Clear only keys with the given prefix
46
- let keysToRemove = keys(pattern: prefix)
65
+ let keysToRemove = try keys(pattern: prefix)
47
66
  var allSuccess = true
48
67
  for key in keysToRemove {
49
- if !remove(key: key) {
68
+ if !(try remove(key: key)) {
50
69
  allSuccess = false
51
70
  }
52
71
  }
@@ -62,7 +81,7 @@ import Security
62
81
  }
63
82
  }
64
83
 
65
- @objc public func keys(pattern: String? = nil) -> [String] {
84
+ @objc public func keys(pattern: String? = nil) throws -> [String] {
66
85
  var query: [String: Any] = [
67
86
  kSecClass as String: kSecClassGenericPassword,
68
87
  kSecAttrService as String: service,
@@ -106,4 +125,20 @@ import Security
106
125
 
107
126
  return query
108
127
  }
128
+
129
+ @objc public func size() throws -> (total: Int, count: Int) {
130
+ let allKeys = try keys()
131
+ var totalSize = 0
132
+
133
+ for key in allKeys {
134
+ if let data = try get(key: key) as? Data {
135
+ totalSize += data.count
136
+ } else if let string = try get(key: key) as? String {
137
+ totalSize += string.data(using: .utf8)?.count ?? 0
138
+ }
139
+ totalSize += key.data(using: .utf8)?.count ?? 0
140
+ }
141
+
142
+ return (total: totalSize, count: allKeys.count)
143
+ }
109
144
  }
@@ -53,7 +53,18 @@ import SQLite3
53
53
  sqlite3_finalize(createTableStatement)
54
54
  }
55
55
 
56
- @objc public func set(key: String, value: Data, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) -> Bool {
56
+ @objc public func set(key: String, value: Any, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) throws -> Bool {
57
+ let data: Data
58
+
59
+ if let dataValue = value as? Data {
60
+ data = dataValue
61
+ } else if let stringValue = value as? String {
62
+ data = stringValue.data(using: .utf8) ?? Data()
63
+ } else {
64
+ // Convert to JSON for complex objects
65
+ data = try JSONSerialization.data(withJSONObject: value, options: [])
66
+ }
67
+
57
68
  let now = Int64(Date().timeIntervalSince1970 * 1000)
58
69
  let tagsJson = tags != nil ? try? JSONSerialization.data(withJSONObject: tags!, options: []) : nil
59
70
  let metadataJson = metadata != nil ? try? JSONSerialization.data(withJSONObject: metadata!, options: []) : nil
@@ -67,7 +78,7 @@ import SQLite3
67
78
  var statement: OpaquePointer?
68
79
  let result = sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) == SQLITE_OK &&
69
80
  sqlite3_bind_text(statement, 1, key, -1, nil) == SQLITE_OK &&
70
- sqlite3_bind_blob(statement, 2, (value as NSData).bytes, Int32(value.count), nil) == SQLITE_OK &&
81
+ sqlite3_bind_blob(statement, 2, (data as NSData).bytes, Int32(data.count), nil) == SQLITE_OK &&
71
82
  sqlite3_bind_int64(statement, 3, now) == SQLITE_OK &&
72
83
  sqlite3_bind_int64(statement, 4, now) == SQLITE_OK &&
73
84
  (expires != nil ? sqlite3_bind_int64(statement, 5, expires!) : sqlite3_bind_null(statement, 5)) == SQLITE_OK &&
@@ -79,6 +90,11 @@ import SQLite3
79
90
  return result
80
91
  }
81
92
 
93
+ // Convenience method for simple values
94
+ @objc public func set(key: String, value: Any) throws -> Bool {
95
+ return try set(key: key, value: value, expires: nil, tags: nil, metadata: nil)
96
+ }
97
+
82
98
  @objc public func get(key: String) -> [String: Any]? {
83
99
  let querySQL = "SELECT * FROM \(tableName) WHERE key = ? LIMIT 1"
84
100
  var statement: OpaquePointer?
@@ -187,4 +203,22 @@ import SQLite3
187
203
  sqlite3_finalize(statement)
188
204
  return keys
189
205
  }
206
+
207
+ @objc public func size() throws -> (total: Int, count: Int) {
208
+ let querySQL = "SELECT COUNT(*), SUM(LENGTH(value)) FROM \(tableName)"
209
+ var statement: OpaquePointer?
210
+
211
+ var totalSize = 0
212
+ var count = 0
213
+
214
+ if sqlite3_prepare_v2(db, querySQL, -1, &statement, nil) == SQLITE_OK {
215
+ if sqlite3_step(statement) == SQLITE_ROW {
216
+ count = Int(sqlite3_column_int(statement, 0))
217
+ totalSize = Int(sqlite3_column_int64(statement, 1))
218
+ }
219
+ }
220
+
221
+ sqlite3_finalize(statement)
222
+ return (total: totalSize, count: count)
223
+ }
190
224
  }
@@ -59,4 +59,18 @@ import Foundation
59
59
  @objc public func has(key: String) -> Bool {
60
60
  return userDefaults.object(forKey: key) != nil
61
61
  }
62
+
63
+ @objc public func size() -> (total: Int, count: Int) {
64
+ let all = userDefaults.dictionaryRepresentation()
65
+ var totalSize = 0
66
+ let count = all.count
67
+
68
+ for (key, value) in all {
69
+ // Estimate size (key + value in bytes)
70
+ totalSize += key.data(using: .utf8)?.count ?? 0
71
+ totalSize += "\(value)".data(using: .utf8)?.count ?? 0
72
+ }
73
+
74
+ return (total: totalSize, count: count)
75
+ }
62
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strata-storage",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -90,18 +90,18 @@
90
90
  },
91
91
  "devDependencies": {
92
92
  "@eslint/js": "^10.0.0",
93
- "@types/node": "^24.6.0",
94
- "@types/react": "^19.1.16",
95
- "@typescript-eslint/eslint-plugin": "^8.45.0",
96
- "@typescript-eslint/parser": "^8.45.0",
93
+ "@types/node": "^24.7.0",
94
+ "@types/react": "^19.2.2",
95
+ "@typescript-eslint/eslint-plugin": "^8.46.0",
96
+ "@typescript-eslint/parser": "^8.46.0",
97
97
  "@vitest/coverage-v8": "^3.2.4",
98
- "eslint": "^9.36.0",
98
+ "eslint": "^9.37.0",
99
99
  "eslint-config-prettier": "^10.1.8",
100
100
  "eslint-plugin-prettier": "^5.5.4",
101
101
  "jsdom": "^27.0.0",
102
102
  "prettier": "^3.6.2",
103
- "typescript": "^5.9.2",
104
- "typescript-eslint": "^8.45.0",
103
+ "typescript": "^5.9.3",
104
+ "typescript-eslint": "^8.46.0",
105
105
  "vitest": "^3.2.4"
106
106
  },
107
107
  "engines": {