query-events.go raw

   1  //go:build js && wasm
   2  
   3  package wasmdb
   4  
   5  import (
   6  	"bytes"
   7  	"context"
   8  	"errors"
   9  	"sort"
  10  	"strconv"
  11  	"time"
  12  
  13  	"github.com/aperturerobotics/go-indexeddb/idb"
  14  	"next.orly.dev/pkg/lol/chk"
  15  	sha256 "github.com/minio/sha256-simd"
  16  
  17  	"next.orly.dev/pkg/nostr/encoders/event"
  18  	"next.orly.dev/pkg/nostr/encoders/filter"
  19  	"next.orly.dev/pkg/nostr/encoders/hex"
  20  	"next.orly.dev/pkg/nostr/encoders/ints"
  21  	"next.orly.dev/pkg/nostr/encoders/kind"
  22  	"next.orly.dev/pkg/nostr/encoders/tag"
  23  	"next.orly.dev/pkg/database"
  24  	"next.orly.dev/pkg/database/indexes/types"
  25  	"next.orly.dev/pkg/interfaces/store"
  26  	"next.orly.dev/pkg/utils"
  27  )
  28  
  29  // CheckExpiration checks if an event has expired based on its "expiration" tag
  30  func CheckExpiration(ev *event.E) (expired bool) {
  31  	var err error
  32  	expTag := ev.Tags.GetFirst([]byte("expiration"))
  33  	if expTag != nil {
  34  		expTS := ints.New(0)
  35  		if _, err = expTS.Unmarshal(expTag.Value()); err == nil {
  36  			if int64(expTS.N) < time.Now().Unix() {
  37  				return true
  38  			}
  39  		}
  40  	}
  41  	return
  42  }
  43  
  44  // GetSerialsByRange retrieves serials from an index range using cursor iteration.
  45  // The index keys must end with a 5-byte serial number.
  46  func (w *W) GetSerialsByRange(idx database.Range) (sers types.Uint40s, err error) {
  47  	if len(idx.Start) < 3 {
  48  		return nil, errors.New("invalid range: start key too short")
  49  	}
  50  
  51  	// Extract the object store name from the 3-byte prefix
  52  	storeName := string(idx.Start[:3])
  53  
  54  	// Open a read transaction
  55  	tx, err := w.db.Transaction(idb.TransactionReadOnly, storeName)
  56  	if err != nil {
  57  		return nil, err
  58  	}
  59  
  60  	objStore, err := tx.ObjectStore(storeName)
  61  	if err != nil {
  62  		return nil, err
  63  	}
  64  
  65  	// Open cursor in reverse order (newest first like Badger)
  66  	cursorReq, err := objStore.OpenCursor(idb.CursorPrevious)
  67  	if err != nil {
  68  		return nil, err
  69  	}
  70  
  71  	// Pre-allocate slice
  72  	sers = make(types.Uint40s, 0, 100)
  73  
  74  	// Create end boundary with 0xff suffix for inclusive range
  75  	endBoundary := make([]byte, len(idx.End)+5)
  76  	copy(endBoundary, idx.End)
  77  	for i := len(idx.End); i < len(endBoundary); i++ {
  78  		endBoundary[i] = 0xff
  79  	}
  80  
  81  	err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error {
  82  		keyVal, keyErr := cursor.Key()
  83  		if keyErr != nil {
  84  			return keyErr
  85  		}
  86  
  87  		key := safeValueToBytes(keyVal)
  88  		if len(key) < 8 { // minimum: 3 prefix + 5 serial
  89  			return cursor.Continue()
  90  		}
  91  
  92  		// Check if key is within range
  93  		keyWithoutSerial := key[:len(key)-5]
  94  
  95  		// Compare with start (lower bound)
  96  		cmp := bytes.Compare(keyWithoutSerial, idx.Start)
  97  		if cmp < 0 {
  98  			// Key is before range start, stop iteration
  99  			return errors.New("done")
 100  		}
 101  
 102  		// Compare with end boundary
 103  		if bytes.Compare(key, endBoundary) > 0 {
 104  			// Key is after range end, continue to find keys in range
 105  			return cursor.Continue()
 106  		}
 107  
 108  		// Extract serial from last 5 bytes
 109  		ser := new(types.Uint40)
 110  		if err := ser.UnmarshalRead(bytes.NewReader(key[len(key)-5:])); err == nil {
 111  			sers = append(sers, ser)
 112  		}
 113  
 114  		return cursor.Continue()
 115  	})
 116  
 117  	if err != nil && err.Error() != "done" {
 118  		return nil, err
 119  	}
 120  
 121  	// Sort by serial (ascending)
 122  	sort.Slice(sers, func(i, j int) bool {
 123  		return sers[i].Get() < sers[j].Get()
 124  	})
 125  
 126  	return sers, nil
 127  }
 128  
 129  // QueryForIds retrieves IdPkTs records based on a filter.
 130  // Results are sorted by timestamp in reverse chronological order.
 131  func (w *W) QueryForIds(c context.Context, f *filter.F) (idPkTs []*store.IdPkTs, err error) {
 132  	if f.Ids != nil && f.Ids.Len() > 0 {
 133  		err = errors.New("query for Ids is invalid for a filter with Ids")
 134  		return
 135  	}
 136  
 137  	var idxs []database.Range
 138  	if idxs, err = database.GetIndexesFromFilter(f); chk.E(err) {
 139  		return
 140  	}
 141  
 142  	var results []*store.IdPkTs
 143  	results = make([]*store.IdPkTs, 0, len(idxs)*100)
 144  
 145  	// Track match counts for search ranking
 146  	counts := make(map[uint64]int)
 147  
 148  	for _, idx := range idxs {
 149  		var founds types.Uint40s
 150  		if founds, err = w.GetSerialsByRange(idx); err != nil {
 151  			w.Logger.Warnf("QueryForIds: GetSerialsByRange error: %v", err)
 152  			continue
 153  		}
 154  
 155  		var tmp []*store.IdPkTs
 156  		if tmp, err = w.GetFullIdPubkeyBySerials(founds); err != nil {
 157  			w.Logger.Warnf("QueryForIds: GetFullIdPubkeyBySerials error: %v", err)
 158  			continue
 159  		}
 160  
 161  		// Track match counts for search queries
 162  		if len(f.Search) > 0 {
 163  			for _, v := range tmp {
 164  				counts[v.Ser]++
 165  			}
 166  		}
 167  		results = append(results, tmp...)
 168  	}
 169  
 170  	// Deduplicate results
 171  	seen := make(map[uint64]struct{}, len(results))
 172  	idPkTs = make([]*store.IdPkTs, 0, len(results))
 173  	for _, idpk := range results {
 174  		if _, ok := seen[idpk.Ser]; !ok {
 175  			seen[idpk.Ser] = struct{}{}
 176  			idPkTs = append(idPkTs, idpk)
 177  		}
 178  	}
 179  
 180  	// For search queries combined with other filters, verify matches
 181  	if len(f.Search) > 0 && ((f.Authors != nil && f.Authors.Len() > 0) ||
 182  		(f.Kinds != nil && f.Kinds.Len() > 0) ||
 183  		(f.Tags != nil && f.Tags.Len() > 0)) {
 184  		// Build serial list for fetching
 185  		serials := make([]*types.Uint40, 0, len(idPkTs))
 186  		for _, v := range idPkTs {
 187  			s := new(types.Uint40)
 188  			s.Set(v.Ser)
 189  			serials = append(serials, s)
 190  		}
 191  
 192  		var evs map[uint64]*event.E
 193  		if evs, err = w.FetchEventsBySerials(serials); chk.E(err) {
 194  			return
 195  		}
 196  
 197  		filtered := make([]*store.IdPkTs, 0, len(idPkTs))
 198  		for _, v := range idPkTs {
 199  			ev, ok := evs[v.Ser]
 200  			if !ok || ev == nil {
 201  				continue
 202  			}
 203  
 204  			matchesAll := true
 205  			if f.Authors != nil && f.Authors.Len() > 0 && !f.Authors.Contains(ev.Pubkey) {
 206  				matchesAll = false
 207  			}
 208  			if matchesAll && f.Kinds != nil && f.Kinds.Len() > 0 && !f.Kinds.Contains(ev.Kind) {
 209  				matchesAll = false
 210  			}
 211  			if matchesAll && f.Tags != nil && f.Tags.Len() > 0 {
 212  				tagOK := true
 213  				for _, t := range *f.Tags {
 214  					if t.Len() < 2 {
 215  						continue
 216  					}
 217  					key := t.Key()
 218  					values := t.T[1:]
 219  					if !ev.Tags.ContainsAny(key, values) {
 220  						tagOK = false
 221  						break
 222  					}
 223  				}
 224  				if !tagOK {
 225  					matchesAll = false
 226  				}
 227  			}
 228  			if matchesAll {
 229  				filtered = append(filtered, v)
 230  			}
 231  		}
 232  		idPkTs = filtered
 233  	}
 234  
 235  	// Sort by timestamp (newest first)
 236  	if len(f.Search) == 0 {
 237  		sort.Slice(idPkTs, func(i, j int) bool {
 238  			return idPkTs[i].Ts > idPkTs[j].Ts
 239  		})
 240  	} else {
 241  		// Search ranking: blend match count with recency
 242  		var maxCount int
 243  		var minTs, maxTs int64
 244  		if len(idPkTs) > 0 {
 245  			minTs, maxTs = idPkTs[0].Ts, idPkTs[0].Ts
 246  		}
 247  		for _, v := range idPkTs {
 248  			if c := counts[v.Ser]; c > maxCount {
 249  				maxCount = c
 250  			}
 251  			if v.Ts < minTs {
 252  				minTs = v.Ts
 253  			}
 254  			if v.Ts > maxTs {
 255  				maxTs = v.Ts
 256  			}
 257  		}
 258  		tsSpan := maxTs - minTs
 259  		if tsSpan <= 0 {
 260  			tsSpan = 1
 261  		}
 262  		if maxCount <= 0 {
 263  			maxCount = 1
 264  		}
 265  		sort.Slice(idPkTs, func(i, j int) bool {
 266  			ci := float64(counts[idPkTs[i].Ser]) / float64(maxCount)
 267  			cj := float64(counts[idPkTs[j].Ser]) / float64(maxCount)
 268  			ai := float64(idPkTs[i].Ts-minTs) / float64(tsSpan)
 269  			aj := float64(idPkTs[j].Ts-minTs) / float64(tsSpan)
 270  			si := 0.5*ci + 0.5*ai
 271  			sj := 0.5*cj + 0.5*aj
 272  			if si == sj {
 273  				return idPkTs[i].Ts > idPkTs[j].Ts
 274  			}
 275  			return si > sj
 276  		})
 277  	}
 278  
 279  	// Apply limit
 280  	if f.Limit != nil && len(idPkTs) > int(*f.Limit) {
 281  		idPkTs = idPkTs[:*f.Limit]
 282  	}
 283  
 284  	return
 285  }
 286  
 287  // QueryForSerials takes a filter and returns matching event serials
 288  func (w *W) QueryForSerials(c context.Context, f *filter.F) (sers types.Uint40s, err error) {
 289  	var founds []*types.Uint40
 290  	var idPkTs []*store.IdPkTs
 291  
 292  	if f.Ids != nil && f.Ids.Len() > 0 {
 293  		// Use batch lookup for IDs
 294  		var serialMap map[string]*types.Uint40
 295  		if serialMap, err = w.GetSerialsByIds(f.Ids); chk.E(err) {
 296  			return
 297  		}
 298  		for _, ser := range serialMap {
 299  			founds = append(founds, ser)
 300  		}
 301  		var tmp []*store.IdPkTs
 302  		if tmp, err = w.GetFullIdPubkeyBySerials(founds); chk.E(err) {
 303  			return
 304  		}
 305  		idPkTs = append(idPkTs, tmp...)
 306  	} else {
 307  		if idPkTs, err = w.QueryForIds(c, f); chk.E(err) {
 308  			return
 309  		}
 310  	}
 311  
 312  	// Extract serials
 313  	for _, idpk := range idPkTs {
 314  		ser := new(types.Uint40)
 315  		if err = ser.Set(idpk.Ser); chk.E(err) {
 316  			continue
 317  		}
 318  		sers = append(sers, ser)
 319  	}
 320  	return
 321  }
 322  
 323  // QueryEvents queries events based on a filter
 324  func (w *W) QueryEvents(c context.Context, f *filter.F) (evs event.S, err error) {
 325  	return w.QueryEventsWithOptions(c, f, true, false)
 326  }
 327  
 328  // QueryAllVersions queries events and returns all versions of replaceable events
 329  func (w *W) QueryAllVersions(c context.Context, f *filter.F) (evs event.S, err error) {
 330  	return w.QueryEventsWithOptions(c, f, true, true)
 331  }
 332  
 333  // QueryEventsWithOptions queries events with additional options for deletion and versioning
 334  func (w *W) QueryEventsWithOptions(c context.Context, f *filter.F, includeDeleteEvents bool, showAllVersions bool) (evs event.S, err error) {
 335  	wantMultipleVersions := showAllVersions || (f.Limit != nil && *f.Limit > 1)
 336  
 337  	var expDeletes types.Uint40s
 338  	var expEvs event.S
 339  
 340  	// Handle ID-based queries
 341  	if f.Ids != nil && f.Ids.Len() > 0 {
 342  		w.Logger.Debugf("QueryEvents: ids path, count=%d", f.Ids.Len())
 343  
 344  		serials, idErr := w.GetSerialsByIds(f.Ids)
 345  		if idErr != nil {
 346  			w.Logger.Warnf("QueryEvents: error looking up ids: %v", idErr)
 347  		}
 348  
 349  		// Convert to slice for batch fetch
 350  		var serialsSlice []*types.Uint40
 351  		idHexToSerial := make(map[uint64]string, len(serials))
 352  		for idHex, ser := range serials {
 353  			serialsSlice = append(serialsSlice, ser)
 354  			idHexToSerial[ser.Get()] = idHex
 355  		}
 356  
 357  		// Batch fetch events
 358  		var fetchedEvents map[uint64]*event.E
 359  		if fetchedEvents, err = w.FetchEventsBySerials(serialsSlice); err != nil {
 360  			w.Logger.Warnf("QueryEvents: batch fetch failed: %v", err)
 361  			return
 362  		}
 363  
 364  		// Process fetched events
 365  		for serialValue, ev := range fetchedEvents {
 366  			idHex := idHexToSerial[serialValue]
 367  
 368  			ser := new(types.Uint40)
 369  			if err = ser.Set(serialValue); err != nil {
 370  				continue
 371  			}
 372  
 373  			// Check expiration
 374  			if CheckExpiration(ev) {
 375  				w.Logger.Debugf("QueryEvents: id=%s filtered out due to expiration", idHex)
 376  				expDeletes = append(expDeletes, ser)
 377  				expEvs = append(expEvs, ev)
 378  				continue
 379  			}
 380  
 381  			// Check for deletion
 382  			if derr := w.CheckForDeleted(ev, nil); derr != nil {
 383  				w.Logger.Debugf("QueryEvents: id=%s filtered out due to deletion: %v", idHex, derr)
 384  				continue
 385  			}
 386  
 387  			evs = append(evs, ev)
 388  		}
 389  
 390  		// Sort and apply limit
 391  		sort.Slice(evs, func(i, j int) bool {
 392  			return evs[i].CreatedAt > evs[j].CreatedAt
 393  		})
 394  		if f.Limit != nil && len(evs) > int(*f.Limit) {
 395  			evs = evs[:*f.Limit]
 396  		}
 397  	} else {
 398  		// Non-IDs path
 399  		var idPkTs []*store.IdPkTs
 400  		if idPkTs, err = w.QueryForIds(c, f); chk.E(err) {
 401  			return
 402  		}
 403  
 404  		// Maps for replaceable event handling
 405  		replaceableEvents := make(map[string]*event.E)
 406  		replaceableEventVersions := make(map[string]event.S)
 407  		paramReplaceableEvents := make(map[string]map[string]*event.E)
 408  		paramReplaceableEventVersions := make(map[string]map[string]event.S)
 409  		var regularEvents event.S
 410  
 411  		// Deletion tracking maps
 412  		deletionsByKindPubkey := make(map[string]bool)
 413  		deletionsByKindPubkeyDTag := make(map[string]map[string]int64)
 414  		deletedEventIds := make(map[string]bool)
 415  
 416  		// Query for deletion events if we have authors
 417  		if f.Authors != nil && f.Authors.Len() > 0 {
 418  			deletionFilter := &filter.F{
 419  				Kinds:   kind.NewS(kind.New(5)),
 420  				Authors: f.Authors,
 421  			}
 422  			var deletionIdPkTs []*store.IdPkTs
 423  			if deletionIdPkTs, err = w.QueryForIds(c, deletionFilter); err == nil {
 424  				idPkTs = append(idPkTs, deletionIdPkTs...)
 425  			}
 426  		}
 427  
 428  		// Prepare serials for batch fetch
 429  		var allSerials []*types.Uint40
 430  		serialToIdPk := make(map[uint64]*store.IdPkTs, len(idPkTs))
 431  		for _, idpk := range idPkTs {
 432  			ser := new(types.Uint40)
 433  			if err = ser.Set(idpk.Ser); err != nil {
 434  				continue
 435  			}
 436  			allSerials = append(allSerials, ser)
 437  			serialToIdPk[ser.Get()] = idpk
 438  		}
 439  
 440  		// Batch fetch all events
 441  		var allEvents map[uint64]*event.E
 442  		if allEvents, err = w.FetchEventsBySerials(allSerials); err != nil {
 443  			w.Logger.Warnf("QueryEvents: batch fetch failed in non-IDs path: %v", err)
 444  			return
 445  		}
 446  
 447  		// First pass: collect deletion events
 448  		for serialValue, ev := range allEvents {
 449  			ser := new(types.Uint40)
 450  			if err = ser.Set(serialValue); err != nil {
 451  				continue
 452  			}
 453  
 454  			if CheckExpiration(ev) {
 455  				expDeletes = append(expDeletes, ser)
 456  				expEvs = append(expEvs, ev)
 457  				continue
 458  			}
 459  
 460  			if ev.Kind == kind.Deletion.K {
 461  				// Process e-tags and a-tags for deletion tracking
 462  				aTags := ev.Tags.GetAll([]byte("a"))
 463  				for _, aTag := range aTags {
 464  					if aTag.Len() < 2 {
 465  						continue
 466  					}
 467  					split := bytes.Split(aTag.Value(), []byte{':'})
 468  					if len(split) < 2 {
 469  						continue
 470  					}
 471  					kindInt, parseErr := strconv.Atoi(string(split[0]))
 472  					if parseErr != nil {
 473  						continue
 474  					}
 475  					kk := kind.New(uint16(kindInt))
 476  					if !kind.IsReplaceable(kk.K) {
 477  						continue
 478  					}
 479  					var pk []byte
 480  					if pk, err = hex.DecAppend(nil, split[1]); err != nil {
 481  						continue
 482  					}
 483  					if !utils.FastEqual(pk, ev.Pubkey) {
 484  						continue
 485  					}
 486  					key := hex.Enc(pk) + ":" + strconv.Itoa(int(kk.K))
 487  
 488  					if kind.IsParameterizedReplaceable(kk.K) {
 489  						if len(split) < 3 {
 490  							continue
 491  						}
 492  						if _, exists := deletionsByKindPubkeyDTag[key]; !exists {
 493  							deletionsByKindPubkeyDTag[key] = make(map[string]int64)
 494  						}
 495  						dValue := string(split[2])
 496  						if ts, ok := deletionsByKindPubkeyDTag[key][dValue]; !ok || ev.CreatedAt > ts {
 497  							deletionsByKindPubkeyDTag[key][dValue] = ev.CreatedAt
 498  						}
 499  					} else {
 500  						deletionsByKindPubkey[key] = true
 501  					}
 502  				}
 503  
 504  				// Process e-tags for specific event deletions
 505  				eTags := ev.Tags.GetAll([]byte("e"))
 506  				for _, eTag := range eTags {
 507  					eTagHex := eTag.ValueHex()
 508  					if len(eTagHex) != 64 {
 509  						continue
 510  					}
 511  					evId := make([]byte, sha256.Size)
 512  					if _, hexErr := hex.DecBytes(evId, eTagHex); hexErr != nil {
 513  						continue
 514  					}
 515  
 516  					// Look for target in current batch
 517  					var targetEv *event.E
 518  					for _, candidateEv := range allEvents {
 519  						if utils.FastEqual(candidateEv.ID, evId) {
 520  							targetEv = candidateEv
 521  							break
 522  						}
 523  					}
 524  
 525  					// Try to fetch if not in batch
 526  					if targetEv == nil {
 527  						ser, serErr := w.GetSerialById(evId)
 528  						if serErr != nil || ser == nil {
 529  							continue
 530  						}
 531  						targetEv, serErr = w.FetchEventBySerial(ser)
 532  						if serErr != nil || targetEv == nil {
 533  							continue
 534  						}
 535  					}
 536  
 537  					if !utils.FastEqual(targetEv.Pubkey, ev.Pubkey) {
 538  						continue
 539  					}
 540  					deletedEventIds[hex.Enc(targetEv.ID)] = true
 541  				}
 542  			}
 543  		}
 544  
 545  		// Second pass: process all events, filtering deleted ones
 546  		for _, ev := range allEvents {
 547  			// Tag filter verification
 548  			if f.Tags != nil && f.Tags.Len() > 0 {
 549  				tagMatches := 0
 550  				for _, filterTag := range *f.Tags {
 551  					if filterTag.Len() >= 2 {
 552  						filterKey := filterTag.Key()
 553  						var actualKey []byte
 554  						if len(filterKey) == 2 && filterKey[0] == '#' {
 555  							actualKey = filterKey[1:]
 556  						} else {
 557  							actualKey = filterKey
 558  						}
 559  						eventHasTag := false
 560  						if ev.Tags != nil {
 561  							for _, eventTag := range *ev.Tags {
 562  								if eventTag.Len() >= 2 && bytes.Equal(eventTag.Key(), actualKey) {
 563  									for _, filterValue := range filterTag.T[1:] {
 564  										if database.TagValuesMatchUsingTagMethods(eventTag, filterValue) {
 565  											eventHasTag = true
 566  											break
 567  										}
 568  									}
 569  									if eventHasTag {
 570  										break
 571  									}
 572  								}
 573  							}
 574  						}
 575  						if eventHasTag {
 576  							tagMatches++
 577  						}
 578  					}
 579  				}
 580  				if tagMatches < f.Tags.Len() {
 581  					continue
 582  				}
 583  			}
 584  
 585  			// Skip deletion events unless explicitly requested
 586  			if ev.Kind == kind.Deletion.K {
 587  				kind5Requested := false
 588  				if f.Kinds != nil && f.Kinds.Len() > 0 {
 589  					for i := 0; i < f.Kinds.Len(); i++ {
 590  						if f.Kinds.K[i].K == kind.Deletion.K {
 591  							kind5Requested = true
 592  							break
 593  						}
 594  					}
 595  				}
 596  				if !kind5Requested {
 597  					continue
 598  				}
 599  			}
 600  
 601  			// Check if event ID is in filter
 602  			isIdInFilter := false
 603  			if f.Ids != nil && f.Ids.Len() > 0 {
 604  				for i := 0; i < f.Ids.Len(); i++ {
 605  					if utils.FastEqual(ev.ID, (*f.Ids).T[i]) {
 606  						isIdInFilter = true
 607  						break
 608  					}
 609  				}
 610  			}
 611  
 612  			// Check if specifically deleted
 613  			eventIdHex := hex.Enc(ev.ID)
 614  			if deletedEventIds[eventIdHex] {
 615  				continue
 616  			}
 617  
 618  			// Handle replaceable events
 619  			if kind.IsReplaceable(ev.Kind) {
 620  				key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
 621  				if deletionsByKindPubkey[key] && !isIdInFilter {
 622  					continue
 623  				} else if wantMultipleVersions {
 624  					replaceableEventVersions[key] = append(replaceableEventVersions[key], ev)
 625  				} else {
 626  					existing, exists := replaceableEvents[key]
 627  					if !exists || ev.CreatedAt > existing.CreatedAt {
 628  						replaceableEvents[key] = ev
 629  					}
 630  				}
 631  			} else if kind.IsParameterizedReplaceable(ev.Kind) {
 632  				key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
 633  				dTag := ev.Tags.GetFirst([]byte("d"))
 634  				var dValue string
 635  				if dTag != nil && dTag.Len() > 1 {
 636  					dValue = string(dTag.Value())
 637  				}
 638  
 639  				if deletionMap, exists := deletionsByKindPubkeyDTag[key]; exists {
 640  					if delTs, ok := deletionMap[dValue]; ok && ev.CreatedAt < delTs && !isIdInFilter {
 641  						continue
 642  					}
 643  				}
 644  
 645  				if wantMultipleVersions {
 646  					if _, exists := paramReplaceableEventVersions[key]; !exists {
 647  						paramReplaceableEventVersions[key] = make(map[string]event.S)
 648  					}
 649  					paramReplaceableEventVersions[key][dValue] = append(paramReplaceableEventVersions[key][dValue], ev)
 650  				} else {
 651  					if _, exists := paramReplaceableEvents[key]; !exists {
 652  						paramReplaceableEvents[key] = make(map[string]*event.E)
 653  					}
 654  					existing, exists := paramReplaceableEvents[key][dValue]
 655  					if !exists || ev.CreatedAt > existing.CreatedAt {
 656  						paramReplaceableEvents[key][dValue] = ev
 657  					}
 658  				}
 659  			} else {
 660  				regularEvents = append(regularEvents, ev)
 661  			}
 662  		}
 663  
 664  		// Collect results
 665  		if wantMultipleVersions {
 666  			for _, versions := range replaceableEventVersions {
 667  				sort.Slice(versions, func(i, j int) bool {
 668  					return versions[i].CreatedAt > versions[j].CreatedAt
 669  				})
 670  				limit := len(versions)
 671  				if f.Limit != nil && int(*f.Limit) < limit {
 672  					limit = int(*f.Limit)
 673  				}
 674  				for i := 0; i < limit; i++ {
 675  					evs = append(evs, versions[i])
 676  				}
 677  			}
 678  		} else {
 679  			for _, ev := range replaceableEvents {
 680  				evs = append(evs, ev)
 681  			}
 682  		}
 683  
 684  		if wantMultipleVersions {
 685  			for _, dTagMap := range paramReplaceableEventVersions {
 686  				for _, versions := range dTagMap {
 687  					sort.Slice(versions, func(i, j int) bool {
 688  						return versions[i].CreatedAt > versions[j].CreatedAt
 689  					})
 690  					limit := len(versions)
 691  					if f.Limit != nil && int(*f.Limit) < limit {
 692  						limit = int(*f.Limit)
 693  					}
 694  					for i := 0; i < limit; i++ {
 695  						evs = append(evs, versions[i])
 696  					}
 697  				}
 698  			}
 699  		} else {
 700  			for _, innerMap := range paramReplaceableEvents {
 701  				for _, ev := range innerMap {
 702  					evs = append(evs, ev)
 703  				}
 704  			}
 705  		}
 706  
 707  		evs = append(evs, regularEvents...)
 708  
 709  		// Sort and limit
 710  		sort.Slice(evs, func(i, j int) bool {
 711  			return evs[i].CreatedAt > evs[j].CreatedAt
 712  		})
 713  		if f.Limit != nil && len(evs) > int(*f.Limit) {
 714  			evs = evs[:*f.Limit]
 715  		}
 716  
 717  		// Delete expired events in background
 718  		go func() {
 719  			for i, ser := range expDeletes {
 720  				w.DeleteEventBySerial(context.Background(), ser, expEvs[i])
 721  			}
 722  		}()
 723  	}
 724  
 725  	return
 726  }
 727  
 728  // QueryDeleteEventsByTargetId queries for delete events targeting a specific event ID
 729  func (w *W) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (evs event.S, err error) {
 730  	f := &filter.F{
 731  		Kinds: kind.NewS(kind.Deletion),
 732  		Tags: tag.NewS(
 733  			tag.NewFromAny("#e", hex.Enc(targetEventId)),
 734  		),
 735  	}
 736  	return w.QueryEventsWithOptions(c, f, true, false)
 737  }
 738  
 739  // CountEvents counts events matching a filter
 740  func (w *W) CountEvents(c context.Context, f *filter.F) (count int, approx bool, err error) {
 741  	approx = false
 742  	if f == nil {
 743  		return 0, false, nil
 744  	}
 745  
 746  	// For ID-based queries, count resolved IDs
 747  	if f.Ids != nil && f.Ids.Len() > 0 {
 748  		serials, idErr := w.GetSerialsByIds(f.Ids)
 749  		if idErr != nil {
 750  			return 0, false, idErr
 751  		}
 752  		return len(serials), false, nil
 753  	}
 754  
 755  	// For other queries, get serials and count
 756  	var sers types.Uint40s
 757  	if sers, err = w.QueryForSerials(c, f); err != nil {
 758  		return 0, false, err
 759  	}
 760  
 761  	return len(sers), false, nil
 762  }
 763  
 764  // GetSerialsFromFilter is an alias for QueryForSerials for interface compatibility
 765  func (w *W) GetSerialsFromFilter(f *filter.F) (serials types.Uint40s, err error) {
 766  	return w.QueryForSerials(w.ctx, f)
 767  }
 768