craft-native 0.0.496 → 0.0.499

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.
@@ -13,11 +13,22 @@ enum CraftPersistentStorage {
13
13
  let digest = SHA256.hash(data: Data(key.utf8)).map { String(format: "%02x", $0) }.joined()
14
14
  return directory.appendingPathComponent(digest + ".json")
15
15
  }
16
+ /// What an entry file holds, or nil when it is there and is not an entry.
17
+ ///
18
+ /// A file that cannot be parsed is a corrupt value, as one that parses to
19
+ /// the wrong thing is, and an app starts that value again. Only a file
20
+ /// that cannot be read at all is the disk failing, which an app retries;
21
+ /// that still throws. The two used to share the disk's answer.
22
+ private static func entry(at source: URL) throws -> [String: Any]? {
23
+ let contents = try Data(contentsOf: source)
24
+ return (try? JSONSerialization.jsonObject(with: contents)) as? [String: Any]
25
+ }
26
+
16
27
  static func get(_ key: String, prefix: String, storageDirectory: URL? = nil) throws -> Data? {
17
28
  lock.lock(); defer { lock.unlock() }
18
29
  let source = path(key, directory: storageDirectory ?? directory)
19
30
  if FileManager.default.fileExists(atPath: source.path) {
20
- guard let saved = try JSONSerialization.jsonObject(with: Data(contentsOf: source)) as? [String: Any], saved["key"] as? String == key else {
31
+ guard let saved = try entry(at: source), saved["key"] as? String == key else {
21
32
  throw CraftNativeActionError(code: "STORAGE_CORRUPT", message: "Invalid stored value")
22
33
  }
23
34
  if saved["deleted"] as? Bool == true { return nil }
@@ -26,7 +37,9 @@ enum CraftPersistentStorage {
26
37
  }
27
38
  guard let legacy = UserDefaults.standard.data(forKey: prefix + key) else { return nil }
28
39
  // Only valid legacy JSON qualifies for deferred migration on I/O failure.
29
- _ = try JSONSerialization.jsonObject(with: legacy, options: .fragmentsAllowed)
40
+ guard (try? JSONSerialization.jsonObject(with: legacy, options: .fragmentsAllowed)) != nil else {
41
+ throw CraftNativeActionError(code: "STORAGE_CORRUPT", message: "Stored value is not valid JSON")
42
+ }
30
43
  // Reading a valid legacy checkpoint must survive a full disk. Migration
31
44
  // can be retried on a later read; only a successful set promises a new
32
45
  // durable checkpoint and permits the application to acknowledge it.
@@ -84,9 +97,11 @@ enum CraftPersistentStorage {
84
97
  var result = Set(UserDefaults.standard.dictionaryRepresentation().keys.filter { $0.hasPrefix(prefix) }.map { String($0.dropFirst(prefix.count)) })
85
98
  if FileManager.default.fileExists(atPath: directory.path) {
86
99
  for source in try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) where source.pathExtension == "json" {
87
- guard let entry = try JSONSerialization.jsonObject(with: Data(contentsOf: source)) as? [String: Any], let key = entry["key"] as? String else {
88
- throw CraftNativeActionError(code: "STORAGE_CORRUPT", message: "Invalid stored value")
89
- }
100
+ // A file that is not an entry names no key. It used to fail
101
+ // the whole listing, and `clear` with it, over a value
102
+ // neither call had been asked about. `get` of its key still
103
+ // answers STORAGE_CORRUPT.
104
+ guard let entry = try entry(at: source), let key = entry["key"] as? String else { continue }
90
105
  if entry["deleted"] as? Bool == true { result.remove(key) } else { result.insert(key) }
91
106
  }
92
107
  }
@@ -95,5 +110,13 @@ enum CraftPersistentStorage {
95
110
  static func clear(prefix: String) throws {
96
111
  lock.lock(); defer { lock.unlock() }
97
112
  for key in try keys(prefix: prefix) { try set(key, data: nil, prefix: prefix) }
113
+ // Clearing a key writes over its file, so what is still not an entry
114
+ // belongs to no key this store knows, and cannot be marked removed.
115
+ // It is deleted. No earlier value can come back in its place: every
116
+ // one this store mirrors was removed just above.
117
+ guard FileManager.default.fileExists(atPath: directory.path) else { return }
118
+ for source in try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) where source.pathExtension == "json" {
119
+ if try entry(at: source)?["key"] as? String == nil { try FileManager.default.removeItem(at: source) }
120
+ }
98
121
  }
99
122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.496",
3
+ "version": "0.0.499",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",