Skip to content

Instantly share code, notes, and snippets.

@arivictor
Last active September 4, 2025 06:29
Show Gist options
  • Select an option

  • Save arivictor/47ec87b4a32ee048b0c38e9ece72290d to your computer and use it in GitHub Desktop.

Select an option

Save arivictor/47ec87b4a32ee048b0c38e9ece72290d to your computer and use it in GitHub Desktop.
import SwiftUI
import Combine
import CoreData
// MARK: - Domain
protocol EventBus {
func publish<T: DomainEvent>(_ event: T)
func subscribe<T: DomainEvent>(_ type: T.Type, handler: @escaping (T) -> Void) -> AnyCancellable
}
struct NoteEntity: Identifiable {
let id: UUID
var preview: String
var body: String
var pinned: Bool = false
var updatedAt: Date
var createdAt: Date
init(id: UUID = UUID(), body: String, pinned: Bool = false, createdAt: Date = Date(), updatedAt: Date = Date(), preview: String = "") {
self.id = id
self.body = body
self.preview = preview ?? NotePreviewGenerator.generate(from: body)
self.pinned = pinned
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}
protocol NoteRepository {
func create(body: String) -> NoteEntity
func update(id: UUID, body: String) -> NoteEntity?
func pin(id: UUID, pinned: Bool) -> NoteEntity?
func get(id: UUID) -> NoteEntity?
func getAll() -> [NoteEntity]
func delete(id: UUID)
}
struct NotePreviewGenerator {
static func generate(from body: String) -> String {
let lines = body.components(separatedBy: .newlines)
return lines
.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
.prefix(3)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.joined(separator: "\n")
}
}
protocol DomainEvent {
var eventId: UUID { get }
var timestamp: Date { get }
var eventType: String { get }
}
struct NotePinnedEvent: DomainEvent {
let eventId = UUID()
let timestamp = Date()
let eventType = "NotePinned"
let noteId: UUID
let pinned: Bool
}
struct NoteDeletedEvent: DomainEvent {
let eventId = UUID()
let timestamp = Date()
let eventType = "NoteDeleted"
let noteId: UUID
}
protocol EventHandler {
associatedtype EventType: DomainEvent
func handle(_ event: EventType)
}
final class NotePinnedEventHandler: EventHandler {
func handle(_ event: NotePinnedEvent) {
print("➡️ [Event Handler] NotePinnedEventHandler reacting to event for note \(event.noteId)")
// Do something..
}
}
final class NoteDeletedEventHandler: EventHandler {
func handle(_ event: NoteDeletedEvent) {
print("➡️ [Event Handler] NoteDeletedEventHandler reacting to event for note \(event.noteId)")
// Do something..
}
}
protocol Command {
var commandId: UUID { get }
}
struct PinNoteCommand: Command {
let commandId = UUID()
let noteId: UUID
let pinned: Bool
}
struct DeleteNoteCommand: Command {
let commandId = UUID()
let noteId: UUID
}
protocol CommandHandler {
associatedtype CommandType: Command
func handle(_ command: CommandType)
}
class PinNoteCommandHandler: CommandHandler {
typealias CommandType = PinNoteCommand
private let eventBus: EventBus
private let database: NoteRepository
init(eventBus: EventBus, database: NoteRepository) {
self.eventBus = eventBus
self.database = database
}
func handle(_ command: PinNoteCommand) {
print("🔨 [Command Handler] PinNoteCommand send for note \(command.noteId)")
if let note = database.pin(id: command.noteId, pinned: command.pinned) {
let event = NotePinnedEvent(noteId: note.id, pinned: command.pinned)
eventBus.publish(event)
} else {
print("❗ [Command Handler] Note with ID \(command.noteId) not found.")
}
}
}
class DeleteNoteCommandHandler: CommandHandler {
typealias CommandType = DeleteNoteCommand
private let eventBus: EventBus
private let database: NoteRepository
init(eventBus: EventBus, database: NoteRepository) {
self.eventBus = eventBus
self.database = database
}
func handle(_ command: DeleteNoteCommand) {
print("🔨 [Command Handler] DeleteNoteCommand send for note \(command.noteId)")
database.delete(id: command.noteId)
let event = NoteDeletedEvent(noteId: command.noteId)
eventBus.publish(event)
}
}
// MARK: Infrastructure
final class InMemoryNoteRepository: NoteRepository {
private var notes: [UUID: NoteEntity] = [:]
func create(body: String) -> NoteEntity {
var note = NoteEntity(body: body)
let now = Date()
note.createdAt = now
note.updatedAt = now
note.preview = NotePreviewGenerator.generate(from: body)
notes[note.id] = note
return note
}
func update(id: UUID, body: String) -> NoteEntity? {
guard var note = notes[id] else { return nil }
note.body = body
note.preview = NotePreviewGenerator.generate(from: body)
note.updatedAt = Date()
notes[id] = note
return note
}
func pin(id: UUID, pinned: Bool) -> NoteEntity? {
guard var note = notes[id] else { return nil }
note.pinned = pinned
note.updatedAt = Date()
notes[id] = note
return note
}
func get(id: UUID) -> NoteEntity? {
notes[id]
}
func getAll() -> [NoteEntity] {
Array(notes.values)
}
func delete(id: UUID) {
notes.removeValue(forKey: id)
}
}
class InMemoryEventBus: EventBus, ObservableObject {
private let subject = PassthroughSubject<DomainEvent, Never>()
func publish<T: DomainEvent>(_ event: T) {
print("📢 [Event Bus]: \(event.eventType) at \(event.timestamp)")
subject.send(event)
}
func subscribe<T: DomainEvent>(_ type: T.Type, handler: @escaping (T) -> Void) -> AnyCancellable {
subject
.compactMap { $0 as? T }
.sink(receiveValue: handler)
}
}
// MARK: - Presentation
extension Date {
func relativeDescription() -> String {
let now = Date()
let interval = now.timeIntervalSince(self)
if interval < 60 {
return "Just now"
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: self, relativeTo: now)
}
static func randomWithin7Days() -> Date {
let now = Date()
let randomSeconds = TimeInterval(Int.random(in: 0..<(7 * 24 * 60 * 60)))
return now.addingTimeInterval(-randomSeconds)
}
}
struct ContentView: View {
let database: NoteRepository
let eventBus: EventBus
@State private var notes: [NoteEntity] = []
@State private var cancellables = Set<AnyCancellable>()
init(database: NoteRepository, eventBus: EventBus) {
self.database = database
self.eventBus = eventBus
}
private func toggleSidebar() {
NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
private func subscribeToPinned() {
let cancellable = eventBus.subscribe(NotePinnedEvent.self) { event in
DispatchQueue.main.async {
if let idx = self.notes.firstIndex(where: { $0.id == event.noteId }) {
self.notes[idx].pinned = event.pinned
}
}
}
cancellables.insert(cancellable)
}
private func subscribeToDeleted() {
let cancellable = eventBus.subscribe(NoteDeletedEvent.self) { event in
DispatchQueue.main.async {
self.notes.removeAll { $0.id == event.noteId }
}
}
cancellables.insert(cancellable)
}
private func actionPinNote(noteId: UUID, pinned: Bool) {
print("👤 [User Action] Pin note \(noteId) set to \(pinned)")
let cmd = PinNoteCommand(noteId: noteId, pinned: pinned)
let pinHandler = PinNoteCommandHandler(eventBus: eventBus, database: database)
pinHandler.handle(cmd)
}
private func actionDeleteNote(noteId: UUID) {
print("👤 [User Action] Delete note \(noteId)")
let cmd = DeleteNoteCommand(noteId: noteId)
let deleteHandler = DeleteNoteCommandHandler(eventBus: eventBus, database: database)
deleteHandler.handle(cmd)
}
private func get_notes() -> [NoteEntity] {
return database.getAll().sorted { $0.updatedAt > $1.updatedAt }
}
private func bootstrap() {
notes = get_notes()
subscribeToPinned()
subscribeToDeleted()
}
var body: some View {
NavigationView {
VStack {
List(notes) { note in
NavigationLink(
destination: Text(note.body)
.padding()
) {
Text(note.preview)
.lineLimit(1)
if note.pinned {
Image(systemName: "pin.fill")
.font(.caption)
.foregroundColor(.primary)
}
}
.contextMenu {
Button(action: {
actionPinNote(noteId: note.id, pinned: !note.pinned)
}) {
Text("Pin")
Image(systemName: "pin")
}
Button(action: {
actionDeleteNote(noteId: note.id)
}) {
Text("Delete")
Image(systemName: "trash")
}
}
}
.onAppear {
bootstrap()
}
}
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: toggleSidebar) {
Image(systemName: "sidebar.leading")
}
}
}
}
}
}
// MARK: - App
func BootstrappedContentView() -> some View {
let database = InMemoryNoteRepository()
let eventBus = InMemoryEventBus()
// Seed database with initial notes
_ = database.create(body: "Sample note")
// Global event handlers
_ = eventBus.subscribe(NotePinnedEvent.self) { event in
NotePinnedEventHandler().handle(event) // logging / background work
}
_ = eventBus.subscribe(NoteDeletedEvent.self) { event in
NoteDeletedEventHandler().handle(event) // logging / background work
}
return ContentView(
database: database,
eventBus: eventBus
)
}
#Preview {
return BootstrappedContentView()
}
@main
struct HyperNoteApp: App {
var body: some Scene {
WindowGroup {
BootstrappedContentView()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment