commit 0ec2b050706ec508b355a134fd2fdd859f802ce7
parent 130bbfafb4aa47c327e15e6bb99e0eac91396cf6
Author: Daniel D’Aquino <daniel@daquino.me>
Date: Wed, 26 Mar 2025 10:27:56 -0300
Implement safe interface for unowned NdbNotes
This commit introduces a new interface that makes it easier and safer to
handle unowned NostrDB notes, by leveraging new non-copyable and borrow
features from modern Swift.
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Diffstat:
3 files changed, 92 insertions(+), 0 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -1090,6 +1090,10 @@
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; };
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; };
+ D733F9E52D92C76100317B11 /* UnownedNdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */; };
+ D733F9E62D92C76100317B11 /* UnownedNdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */; };
+ D733F9E72D92C76100317B11 /* UnownedNdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */; };
+ D733F9E82D92C76100317B11 /* UnownedNdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */; };
D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */; };
D734B1462CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */; };
D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; };
@@ -2477,6 +2481,7 @@
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrFilterTests.swift; sourceTree = "<group>"; };
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = "<group>"; };
+ D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnownedNdbNote.swift; sourceTree = "<group>"; };
D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusFullScreenCover.swift; sourceTree = "<group>"; };
D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = "<group>"; };
D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNewUserOnboardingView.swift; sourceTree = "<group>"; };
@@ -3359,6 +3364,7 @@
4C9054862A6AEB4500811EEC /* nostrdb */ = {
isa = PBXGroup;
children = (
+ D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */,
4C47928D2A9939BD00489948 /* flatcc */,
4C478E2A2A9935D300489948 /* bindings */,
4CE9FBBB2A6B3D9C007E485C /* Test */,
@@ -4927,6 +4933,7 @@
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */,
D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */,
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
+ D733F9E82D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
D74EA0902D2E271E002290DD /* ErrorView.swift in Sources */,
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */,
@@ -5292,6 +5299,7 @@
82D6FBBA2CD99F7900C925F4 /* NostrRequest.swift in Sources */,
82D6FBBB2CD99F7900C925F4 /* Profiles.swift in Sources */,
82D6FBBC2CD99F7900C925F4 /* NostrKind.swift in Sources */,
+ D733F9E62D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
82D6FBBD2CD99F7900C925F4 /* NostrLink.swift in Sources */,
82D6FBBE2CD99F7900C925F4 /* WebSocket.swift in Sources */,
82D6FBBF2CD99F7900C925F4 /* ReferencedId.swift in Sources */,
@@ -5604,6 +5612,7 @@
D73E5E882C6A97F4007EB227 /* StoreObserver.swift in Sources */,
D73E5E892C6A97F4007EB227 /* DamusPurpleURL.swift in Sources */,
D73E5E8A2C6A97F4007EB227 /* PurpleStoreKitManager.swift in Sources */,
+ D733F9E72D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
D73E5E8E2C6A97F4007EB227 /* ImageResizer.swift in Sources */,
D78F080E2D7F78EF00FC6C75 /* Request.swift in Sources */,
D73E5E8F2C6A97F4007EB227 /* PhotoCaptureProcessor.swift in Sources */,
@@ -5989,6 +5998,7 @@
buildActionMask = 2147483647;
files = (
4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */,
+ D733F9E52D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */,
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */,
D71AD9002CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
diff --git a/damus/NIP65/NIP65.swift b/damus/NIP65/NIP65.swift
@@ -20,6 +20,10 @@ extension NIP65 {
// MARK: - Initialization
init(event: NdbNote) throws(NIP65DecodingError) {
+ try self.init(event: UnownedNdbNote(event))
+ }
+
+ init(event: borrowing UnownedNdbNote) throws(NIP65DecodingError) {
guard event.known_kind == .relay_list else { throw .notRelayList }
var relays: [RelayItem] = []
for tag in event.tags {
diff --git a/nostrdb/UnownedNdbNote.swift b/nostrdb/UnownedNdbNote.swift
@@ -0,0 +1,78 @@
+//
+// UnownedNdbNote.swift
+// damus
+//
+// Created by Daniel D’Aquino on 2025-03-25.
+//
+
+/// A function that allows an unowned NdbNote to be lent out temporarily
+///
+/// Use this to provide access to NostrDB unowned notes in a way that has much better compile-time safety guarantees.
+///
+/// # Usage examples
+///
+/// ## Lending out or providing Ndb notes
+///
+/// ```swift
+/// // Define the lender
+/// let lender: NdbNoteLender = { lend in
+/// guard let ndbNoteTxn = ndb.lookup_note(noteId) else { // Note: Must have access to `Ndb`
+/// throw NdbNoteLenderError.errorLoadingNote // Throw errors if loading fails
+/// }
+/// guard let unownedNote = UnownedNdbNote(ndbNoteTxn) else {
+/// throw NdbNoteLenderError.errorLoadingNote
+/// }
+/// lend(unownedNote) // Lend out the Unowned Ndb note
+/// }
+/// return lender // Return or pass the lender to another class
+/// ```
+///
+/// ## Borrowing Ndb notes
+///
+/// Assuming you are given a lender, here is how you can use it:
+///
+/// ```swift
+/// let borrow: NdbNoteLender = functionThatProvidesALender()
+/// try? borrow { note in // You can optionally handle errors if borrowing fails
+/// self.date = note.createdAt // You can do things with the note without copying it over
+/// // self.note = note // Not allowed by the compiler
+/// self.note = note.toOwned() // You can copy the note if needed
+/// }
+/// ```
+typealias NdbNoteLender = ((_: borrowing UnownedNdbNote) -> Void) throws -> Void
+
+enum NdbNoteLenderError: Error {
+ case errorLoadingNote
+}
+
+
+/// A wrapper to NdbNote that allows unowned NdbNotes to be safely handled
+struct UnownedNdbNote: ~Copyable {
+ private let _ndbNote: NdbNote
+
+ init(_ txn: NdbTxn<NdbNote>) {
+ self._ndbNote = txn.unsafeUnownedValue
+ }
+
+ init?(_ txn: NdbTxn<NdbNote?>) {
+ guard let note = txn.unsafeUnownedValue else { return nil }
+ self._ndbNote = note
+ }
+
+ init(_ ndbNote: NdbNote) {
+ self._ndbNote = ndbNote
+ }
+
+ var kind: UInt32 { _ndbNote.kind }
+ var known_kind: NostrKind? { _ndbNote.known_kind }
+ var content: String { _ndbNote.content }
+ var tags: TagsSequence { _ndbNote.tags }
+ var pubkey: Pubkey { _ndbNote.pubkey }
+ var createdAt: UInt32 { _ndbNote.created_at }
+ var id: NoteId { _ndbNote.id }
+ var sig: Signature { _ndbNote.sig }
+
+ func toOwned() -> NdbNote {
+ return _ndbNote.to_owned()
+ }
+}