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>
This commit is contained in:
Daniel D’Aquino
2025-03-26 10:27:56 -03:00
parent 130bbfafb4
commit 0ec2b05070
3 changed files with 92 additions and 0 deletions

View File

@@ -1090,6 +1090,10 @@
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; }; D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; };
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; }; D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.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 */; }; D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */; };
D734B1462CCC19B1000B5C97 /* 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 */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNewUserOnboardingView.swift; sourceTree = "<group>"; };
@@ -3359,6 +3364,7 @@
4C9054862A6AEB4500811EEC /* nostrdb */ = { 4C9054862A6AEB4500811EEC /* nostrdb */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */,
4C47928D2A9939BD00489948 /* flatcc */, 4C47928D2A9939BD00489948 /* flatcc */,
4C478E2A2A9935D300489948 /* bindings */, 4C478E2A2A9935D300489948 /* bindings */,
4CE9FBBB2A6B3D9C007E485C /* Test */, 4CE9FBBB2A6B3D9C007E485C /* Test */,
@@ -4927,6 +4933,7 @@
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */, 4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */,
D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */, D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */,
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */, 4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
D733F9E82D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
D74EA0902D2E271E002290DD /* ErrorView.swift in Sources */, D74EA0902D2E271E002290DD /* ErrorView.swift in Sources */,
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */, 4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */, BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */,
@@ -5292,6 +5299,7 @@
82D6FBBA2CD99F7900C925F4 /* NostrRequest.swift in Sources */, 82D6FBBA2CD99F7900C925F4 /* NostrRequest.swift in Sources */,
82D6FBBB2CD99F7900C925F4 /* Profiles.swift in Sources */, 82D6FBBB2CD99F7900C925F4 /* Profiles.swift in Sources */,
82D6FBBC2CD99F7900C925F4 /* NostrKind.swift in Sources */, 82D6FBBC2CD99F7900C925F4 /* NostrKind.swift in Sources */,
D733F9E62D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
82D6FBBD2CD99F7900C925F4 /* NostrLink.swift in Sources */, 82D6FBBD2CD99F7900C925F4 /* NostrLink.swift in Sources */,
82D6FBBE2CD99F7900C925F4 /* WebSocket.swift in Sources */, 82D6FBBE2CD99F7900C925F4 /* WebSocket.swift in Sources */,
82D6FBBF2CD99F7900C925F4 /* ReferencedId.swift in Sources */, 82D6FBBF2CD99F7900C925F4 /* ReferencedId.swift in Sources */,
@@ -5604,6 +5612,7 @@
D73E5E882C6A97F4007EB227 /* StoreObserver.swift in Sources */, D73E5E882C6A97F4007EB227 /* StoreObserver.swift in Sources */,
D73E5E892C6A97F4007EB227 /* DamusPurpleURL.swift in Sources */, D73E5E892C6A97F4007EB227 /* DamusPurpleURL.swift in Sources */,
D73E5E8A2C6A97F4007EB227 /* PurpleStoreKitManager.swift in Sources */, D73E5E8A2C6A97F4007EB227 /* PurpleStoreKitManager.swift in Sources */,
D733F9E72D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
D73E5E8E2C6A97F4007EB227 /* ImageResizer.swift in Sources */, D73E5E8E2C6A97F4007EB227 /* ImageResizer.swift in Sources */,
D78F080E2D7F78EF00FC6C75 /* Request.swift in Sources */, D78F080E2D7F78EF00FC6C75 /* Request.swift in Sources */,
D73E5E8F2C6A97F4007EB227 /* PhotoCaptureProcessor.swift in Sources */, D73E5E8F2C6A97F4007EB227 /* PhotoCaptureProcessor.swift in Sources */,
@@ -5989,6 +5998,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */, 4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */,
D733F9E52D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */, D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */,
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */, D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */,
D71AD9002CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */, D71AD9002CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,

View File

@@ -20,6 +20,10 @@ extension NIP65 {
// MARK: - Initialization // MARK: - Initialization
init(event: NdbNote) throws(NIP65DecodingError) { 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 } guard event.known_kind == .relay_list else { throw .notRelayList }
var relays: [RelayItem] = [] var relays: [RelayItem] = []
for tag in event.tags { for tag in event.tags {

View File

@@ -0,0 +1,78 @@
//
// UnownedNdbNote.swift
// damus
//
// Created by Daniel DAquino 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()
}
}