strata-storage 2.2.0 → 2.4.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.
@@ -9,6 +9,9 @@ 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 org.json.JSONArray;
14
+ import java.nio.charset.StandardCharsets;
12
15
 
13
16
  public class SQLiteStorage extends SQLiteOpenHelper {
14
17
  private static final int DATABASE_VERSION = 1;
@@ -50,13 +53,40 @@ public class SQLiteStorage extends SQLiteOpenHelper {
50
53
  onCreate(db);
51
54
  }
52
55
 
53
- public boolean set(String key, byte[] value, Long expires, String tags, String metadata) {
56
+ public boolean set(String key, Object value, Long expires, String tags, String metadata) {
57
+ byte[] valueBytes;
58
+
59
+ try {
60
+ if (value instanceof byte[]) {
61
+ valueBytes = (byte[]) value;
62
+ } else if (value instanceof String) {
63
+ valueBytes = ((String) value).getBytes(StandardCharsets.UTF_8);
64
+ } else {
65
+ // Convert complex objects to JSON then to bytes
66
+ String json;
67
+ if (value instanceof JSONObject) {
68
+ json = ((JSONObject) value).toString();
69
+ } else {
70
+ // Use helper method to convert object to JSON string
71
+ try {
72
+ json = objectToJsonString(value);
73
+ } catch (Exception jsonEx) {
74
+ // Fallback to string representation
75
+ json = value.toString();
76
+ }
77
+ }
78
+ valueBytes = json.getBytes(StandardCharsets.UTF_8);
79
+ }
80
+ } catch (Exception e) {
81
+ e.printStackTrace();
82
+ return false;
83
+ }
54
84
  SQLiteDatabase db = this.getWritableDatabase();
55
85
  ContentValues values = new ContentValues();
56
86
 
57
87
  long now = System.currentTimeMillis();
58
88
  values.put(KEY_ID, key);
59
- values.put(KEY_VALUE, value);
89
+ values.put(KEY_VALUE, valueBytes);
60
90
  values.put(KEY_CREATED, now);
61
91
  values.put(KEY_UPDATED, now);
62
92
 
@@ -75,6 +105,11 @@ public class SQLiteStorage extends SQLiteOpenHelper {
75
105
  return result != -1;
76
106
  }
77
107
 
108
+ // Convenience method for simple Object values
109
+ public boolean set(String key, Object value) {
110
+ return set(key, value, null, null, null);
111
+ }
112
+
78
113
  public Map<String, Object> get(String key) {
79
114
  SQLiteDatabase db = this.getReadableDatabase();
80
115
  Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
@@ -174,4 +209,79 @@ public class SQLiteStorage extends SQLiteOpenHelper {
174
209
  db.close();
175
210
  return exists;
176
211
  }
212
+
213
+ public SizeInfo size() {
214
+ SQLiteDatabase db = this.getReadableDatabase();
215
+ Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
216
+
217
+ long totalSize = 0;
218
+ int count = 0;
219
+
220
+ if (cursor != null && cursor.moveToFirst()) {
221
+ count = cursor.getInt(0);
222
+ totalSize = cursor.getLong(1);
223
+ cursor.close();
224
+ }
225
+
226
+ db.close();
227
+ return new SizeInfo(totalSize, count);
228
+ }
229
+
230
+ /**
231
+ * Convert an object to JSON string using reflection
232
+ */
233
+ private String objectToJsonString(Object obj) throws Exception {
234
+ if (obj == null) {
235
+ return "null";
236
+ }
237
+
238
+ if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
239
+ return obj.toString();
240
+ }
241
+
242
+ if (obj instanceof Map) {
243
+ JSONObject jsonObj = new JSONObject();
244
+ Map<?, ?> map = (Map<?, ?>) obj;
245
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
246
+ String key = entry.getKey().toString();
247
+ jsonObj.put(key, entry.getValue());
248
+ }
249
+ return jsonObj.toString();
250
+ }
251
+
252
+ if (obj instanceof List || obj.getClass().isArray()) {
253
+ JSONArray jsonArray = new JSONArray();
254
+ if (obj instanceof List) {
255
+ List<?> list = (List<?>) obj;
256
+ for (Object item : list) {
257
+ jsonArray.put(item);
258
+ }
259
+ } else {
260
+ Object[] array = (Object[]) obj;
261
+ for (Object item : array) {
262
+ jsonArray.put(item);
263
+ }
264
+ }
265
+ return jsonArray.toString();
266
+ }
267
+
268
+ // For other objects, create a simple JSON object with their string representation
269
+ JSONObject jsonObj = new JSONObject();
270
+ jsonObj.put("value", obj.toString());
271
+ jsonObj.put("type", obj.getClass().getSimpleName());
272
+ return jsonObj.toString();
273
+ }
274
+
275
+ /**
276
+ * Size information class
277
+ */
278
+ public static class SizeInfo {
279
+ public final long total;
280
+ public final int count;
281
+
282
+ public SizeInfo(long total, int count) {
283
+ this.total = total;
284
+ this.count = count;
285
+ }
286
+ }
177
287
  }
@@ -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
 
@@ -38,9 +40,18 @@ public class SharedPreferencesStorage {
38
40
  editor.putStringSet(key, (Set<String>) value);
39
41
  } else {
40
42
  // Convert complex objects to JSON
41
- String json = value instanceof JSONObject ?
42
- ((JSONObject) value).toString() :
43
- new JSONObject(value).toString();
43
+ String json;
44
+ if (value instanceof JSONObject) {
45
+ json = ((JSONObject) value).toString();
46
+ } else {
47
+ // Use reflection to convert object to JSON string
48
+ try {
49
+ json = objectToJsonString(value);
50
+ } catch (Exception e) {
51
+ // Fallback to string representation
52
+ json = value.toString();
53
+ }
54
+ }
44
55
  editor.putString(key, json);
45
56
  }
46
57
  return editor.commit();
@@ -83,19 +94,19 @@ public class SharedPreferencesStorage {
83
94
  return editor.commit();
84
95
  }
85
96
 
86
- public Set<String> keys() {
97
+ public List<String> keys() {
87
98
  return keys(null);
88
99
  }
89
100
 
90
- public Set<String> keys(String pattern) {
101
+ public List<String> keys(String pattern) {
91
102
  Set<String> allKeys = prefs.getAll().keySet();
92
103
 
93
104
  if (pattern == null) {
94
- return allKeys;
105
+ return new ArrayList<>(allKeys);
95
106
  }
96
107
 
97
108
  // Filter keys by pattern
98
- Set<String> filteredKeys = new HashSet<>();
109
+ List<String> filteredKeys = new ArrayList<>();
99
110
  for (String key : allKeys) {
100
111
  if (key.startsWith(pattern) || key.contains(pattern)) {
101
112
  filteredKeys.add(key);
@@ -111,4 +122,81 @@ public class SharedPreferencesStorage {
111
122
  public Map<String, ?> getAll() {
112
123
  return prefs.getAll();
113
124
  }
125
+
126
+ public SizeInfo size() {
127
+ Map<String, ?> all = prefs.getAll();
128
+ long totalSize = 0;
129
+ int count = all.size();
130
+
131
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
132
+ String key = entry.getKey();
133
+ Object value = entry.getValue();
134
+
135
+ // Estimate size (key + value in bytes)
136
+ totalSize += key.getBytes().length;
137
+ if (value != null) {
138
+ totalSize += value.toString().getBytes().length;
139
+ }
140
+ }
141
+
142
+ return new SizeInfo(totalSize, count);
143
+ }
144
+
145
+ /**
146
+ * Convert an object to JSON string using reflection
147
+ */
148
+ private String objectToJsonString(Object obj) throws Exception {
149
+ if (obj == null) {
150
+ return "null";
151
+ }
152
+
153
+ if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
154
+ return obj.toString();
155
+ }
156
+
157
+ if (obj instanceof Map) {
158
+ JSONObject jsonObj = new JSONObject();
159
+ Map<?, ?> map = (Map<?, ?>) obj;
160
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
161
+ String key = entry.getKey().toString();
162
+ jsonObj.put(key, entry.getValue());
163
+ }
164
+ return jsonObj.toString();
165
+ }
166
+
167
+ if (obj instanceof List || obj.getClass().isArray()) {
168
+ JSONArray jsonArray = new JSONArray();
169
+ if (obj instanceof List) {
170
+ List<?> list = (List<?>) obj;
171
+ for (Object item : list) {
172
+ jsonArray.put(item);
173
+ }
174
+ } else {
175
+ Object[] array = (Object[]) obj;
176
+ for (Object item : array) {
177
+ jsonArray.put(item);
178
+ }
179
+ }
180
+ return jsonArray.toString();
181
+ }
182
+
183
+ // For other objects, create a simple JSON object with their string representation
184
+ JSONObject jsonObj = new JSONObject();
185
+ jsonObj.put("value", obj.toString());
186
+ jsonObj.put("type", obj.getClass().getSimpleName());
187
+ return jsonObj.toString();
188
+ }
189
+
190
+ /**
191
+ * Size information class
192
+ */
193
+ public static class SizeInfo {
194
+ public final long total;
195
+ public final int count;
196
+
197
+ public SizeInfo(long total, int count) {
198
+ this.total = total;
199
+ this.count = count;
200
+ }
201
+ }
114
202
  }
@@ -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.4.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
  }