@@ -23,7 +23,13 @@ import {
2323} from '../constants/base'
2424import { DatabaseClient , EventId } from '../@types/base'
2525import { DBEvent , Event } from '../@types/event'
26- import { EventPurgeCounts , EventRetentionOptions , IEventRepository , IQueryResult } from '../@types/repositories'
26+ import {
27+ EventPurgeCounts ,
28+ EventRetentionOptions ,
29+ IEventRepository ,
30+ IQueryResult ,
31+ ReconciliationItem ,
32+ } from '../@types/repositories'
2733import { toBuffer , toJSON } from '../utils/transform'
2834import { createLogger } from '../factories/logger-factory'
2935import { isGenericTagQuery , isGeohashPrefixCriterion , stripGeohashPrefixWildcard } from '../utils/filter'
@@ -128,6 +134,31 @@ export class EventRepository implements IEventRepository {
128134 return Number ( result ?. count ?? 0 )
129135 }
130136
137+ /**
138+ * Every live event matching the filter as (created_at, id), oldest first, for NIP-77 set
139+ * reconciliation. The filter's limit is ignored on purpose: reconciliation compares whole sets, so
140+ * truncating one side would report the cut-off events as missing. Deleted and expired events are
141+ * excluded because they must never be advertised to a peer.
142+ */
143+ public findReconciliationItems ( filter : SubscriptionFilter ) : IQueryResult < ReconciliationItem [ ] > {
144+ logger ( 'querying reconciliation items for %o' , filter )
145+
146+ const now = Math . floor ( Date . now ( ) / 1000 )
147+ const builder = this . readReplicaDbClient < DBEvent > ( 'events' ) . select ( 'events.event_created_at' , 'events.event_id' )
148+
149+ this . applyFilterConditions ( builder , filter )
150+
151+ builder
152+ . whereNull ( 'events.deleted_at' )
153+ . andWhere ( ( bd ) => {
154+ bd . whereNull ( 'events.expires_at' ) . orWhere ( 'events.expires_at' , '>' , now )
155+ } )
156+ . orderBy ( 'events.event_created_at' , 'asc' )
157+ . orderBy ( 'events.event_id' , 'asc' )
158+
159+ return builder as unknown as IQueryResult < ReconciliationItem [ ] >
160+ }
161+
131162 // Wraps every query so each keeps its own ORDER BY and LIMIT. first.union(rest) would put the
132163 // first query's ORDER BY and LIMIT after the UNION, applying them to the whole result.
133164 private unionFilterQueries ( queries : any [ ] ) : any {
0 commit comments