refactor(notifications): observer takes a single predicate
Filter.match is itself just a boolean predicate, so carrying a Filter and a separate composition predicate on NewEventMatchingFilter duplicated the abstraction. Collapse to one predicate: - NewEventMatchingFilter(predicate, onNew): drops the Filter parameter. - LocalCache.observeNewEvents(predicate): primary API. - LocalCache.observeNewEvents(filter): kept as a one-liner that forwards filter::match, so existing feed/DAL callers are unchanged. In the dispatcher the freshness check, kind check, p-tag match, and since cutoff now live in a single lambda, short-circuiting on cheap kind comparison first. NOTIFICATION_KINDS is now a Set<Int> for O(1) membership instead of O(n) List.contains — cheap, but on a hot path that runs for every new cache insertion it pays for itself. https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
This commit is contained in:
+11
-14
@@ -22,24 +22,21 @@ package com.vitorpamplona.amethyst.commons.model.observables
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
|
||||
/**
|
||||
* Emits each new event that matches the given Nostr filter, one at a time.
|
||||
* Emits each new event for which [predicate] returns true, one at a time,
|
||||
* as it is inserted into the cache. Unlike [EventListMatchingFilter] /
|
||||
* [NoteListMatchingFilter], this does not accumulate a list — it simply
|
||||
* calls [onNew] per matching event. Useful for reactive event-triggered
|
||||
* pipelines like notifications.
|
||||
*
|
||||
* Unlike [EventListMatchingFilter] / [NoteListMatchingFilter], this does not
|
||||
* accumulate a list — it simply calls [onNew] per matching event as it is
|
||||
* inserted into the cache. Useful for reactive event-triggered pipelines
|
||||
* (e.g. notifications) that need per-event delivery without list overhead.
|
||||
*
|
||||
* The optional [predicate] runs after the protocol [filter] matches; use it
|
||||
* for checks the Nostr [Filter] grammar can't express (e.g. rolling age
|
||||
* cutoffs, arbitrary tag shapes, derived content fields). The predicate has
|
||||
* to be fast — it runs on every new cache insertion.
|
||||
* The predicate has to be fast — it runs on every new cache insertion.
|
||||
* Callers with a Nostr [com.vitorpamplona.quartz.nip01Core.relay.filters.Filter]
|
||||
* can pass `filter::match` as the predicate and compose additional checks
|
||||
* with `&&`.
|
||||
*/
|
||||
class NewEventMatchingFilter<T : Event>(
|
||||
private val filter: Filter,
|
||||
private val predicate: (Event) -> Boolean = { true },
|
||||
private val predicate: (Event) -> Boolean,
|
||||
private val onNew: (T) -> Unit,
|
||||
) : Observable {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -47,7 +44,7 @@ class NewEventMatchingFilter<T : Event>(
|
||||
event: Event,
|
||||
note: Note,
|
||||
) {
|
||||
if (filter.match(event) && predicate(event)) {
|
||||
if (predicate(event)) {
|
||||
onNew(event as T)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user