query-events.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"bytes"
   7  	"context"
   8  	"sort"
   9  	"strconv"
  10  	"time"
  11  
  12  	"next.orly.dev/pkg/lol/chk"
  13  	"next.orly.dev/pkg/lol/log"
  14  	"github.com/minio/sha256-simd"
  15  	"next.orly.dev/pkg/database/indexes/types"
  16  	"next.orly.dev/pkg/mode"
  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/interfaces/store"
  24  	"next.orly.dev/pkg/utils"
  25  )
  26  
  27  func CheckExpiration(ev *event.E) (expired bool) {
  28  	var err error
  29  	expTag := ev.Tags.GetFirst([]byte("expiration"))
  30  	if expTag != nil {
  31  		expTS := ints.New(0)
  32  		if _, err = expTS.Unmarshal(expTag.Value()); !chk.E(err) {
  33  			if int64(expTS.N) < time.Now().Unix() {
  34  				return true
  35  			}
  36  		}
  37  	}
  38  	return
  39  }
  40  
  41  func (d *D) QueryEvents(c context.Context, f *filter.F) (
  42  	evs event.S, err error,
  43  ) {
  44  	return d.QueryEventsWithOptions(c, f, true, false)
  45  }
  46  
  47  // QueryAllVersions queries events and returns all versions of replaceable events
  48  func (d *D) QueryAllVersions(c context.Context, f *filter.F) (
  49  	evs event.S, err error,
  50  ) {
  51  	return d.QueryEventsWithOptions(c, f, true, true)
  52  }
  53  
  54  func (d *D) QueryEventsWithOptions(c context.Context, f *filter.F, includeDeleteEvents bool, showAllVersions bool) (
  55  	evs event.S, err error,
  56  ) {
  57  	// Acquire query slot to limit concurrent queries and prevent memory exhaustion
  58  	if !d.AcquireQuerySlot(c) {
  59  		err = c.Err()
  60  		return
  61  	}
  62  	defer d.ReleaseQuerySlot()
  63  
  64  	// Fast path for NIP-33 addressable event queries (kind + author + d-tag)
  65  	// This provides O(1) direct lookup instead of index scanning
  66  	if !showAllVersions && IsAddressableEventQuery(f) {
  67  		var serial *types.Uint40
  68  		if serial, err = d.QueryForAddressableEvent(f); err != nil {
  69  			log.W.F("QueryEvents: addressable event fast path error: %v", err)
  70  			// Fall through to regular query path
  71  			err = nil
  72  		} else if serial != nil {
  73  			// Found via fast path - fetch the event
  74  			ev, fetchErr := d.FetchEventBySerial(serial)
  75  			if fetchErr == nil && ev != nil {
  76  				// Apply time filters if present
  77  				if f.Since != nil && ev.CreatedAt < f.Since.V {
  78  					log.T.F("QueryEvents: addressable event fast path - event before 'since' filter")
  79  					return // Return empty result
  80  				}
  81  				if f.Until != nil && ev.CreatedAt > f.Until.V {
  82  					log.T.F("QueryEvents: addressable event fast path - event after 'until' filter")
  83  					return // Return empty result
  84  				}
  85  				// Check deletion status (skip in open relay mode)
  86  				if !mode.IsOpen() {
  87  					if derr := d.CheckForDeleted(ev, nil); derr != nil {
  88  						log.T.F("QueryEvents: addressable event fast path - event deleted: %v", derr)
  89  						return // Return empty result
  90  					}
  91  				}
  92  				// Check expiration
  93  				if !mode.IsOpen() && CheckExpiration(ev) {
  94  					log.T.F("QueryEvents: addressable event fast path - event expired")
  95  					return // Return empty result
  96  				}
  97  				log.T.F("QueryEvents: addressable event fast path SUCCESS - kind=%d d=%s",
  98  					ev.Kind, string(ev.Tags.GetFirst([]byte("d")).Value()))
  99  				evs = append(evs, ev)
 100  				return
 101  			}
 102  			// Fetch failed - fall through to regular path
 103  			log.T.F("QueryEvents: addressable event fast path - fetch failed, falling back")
 104  		}
 105  		// Not found via fast path - fall through to regular query path
 106  		// This handles the case where the index doesn't exist yet (migration)
 107  	}
 108  
 109  	// Determine if we should return multiple versions of replaceable events
 110  	// based on the limit parameter
 111  	wantMultipleVersions := showAllVersions || (f.Limit != nil && *f.Limit > 1)
 112  
 113  	// if there is Ids in the query, this overrides anything else
 114  	var expDeletes types.Uint40s
 115  	var expEvs event.S
 116  	if f.Ids != nil && f.Ids.Len() > 0 {
 117  		// Get all serials for the requested IDs in a single batch operation
 118  		log.T.F("QueryEvents: ids path, count=%d", f.Ids.Len())
 119  
 120  		// Use GetSerialsByIds to batch process all IDs at once
 121  		serials, idErr := d.GetSerialsByIds(f.Ids)
 122  		if idErr != nil {
 123  			log.E.F("QueryEvents: error looking up ids: %v", idErr)
 124  			// Continue with whatever IDs we found
 125  		}
 126  
 127  		// Convert serials map to slice for batch fetch
 128  		var serialsSlice []*types.Uint40
 129  		serialsSlice = make([]*types.Uint40, 0, len(serials))
 130  		idHexToSerial := make(map[uint64]string, len(serials)) // Map serial value back to original ID hex
 131  		for idHex, ser := range serials {
 132  			serialsSlice = append(serialsSlice, ser)
 133  			idHexToSerial[ser.Get()] = idHex
 134  		}
 135  
 136  		// Fetch all events in a single batch operation
 137  		var fetchedEvents map[uint64]*event.E
 138  		if fetchedEvents, err = d.FetchEventsBySerials(serialsSlice); err != nil {
 139  			log.E.F("QueryEvents: batch fetch failed: %v", err)
 140  			return
 141  		}
 142  
 143  		// Process each successfully fetched event and apply filters
 144  		for serialValue, ev := range fetchedEvents {
 145  			idHex := idHexToSerial[serialValue]
 146  
 147  			// Convert serial value back to Uint40 for expiration handling
 148  			ser := new(types.Uint40)
 149  			if err = ser.Set(serialValue); err != nil {
 150  				log.T.F(
 151  					"QueryEvents: error converting serial %d: %v", serialValue,
 152  					err,
 153  				)
 154  				continue
 155  			}
 156  
 157  			// check for an expiration tag and delete after returning the result
 158  			// Skip expiration check when ACL is "none" (open relay mode)
 159  			if !mode.IsOpen() && CheckExpiration(ev) {
 160  				log.T.F(
 161  					"QueryEvents: id=%s filtered out due to expiration", idHex,
 162  				)
 163  				expDeletes = append(expDeletes, ser)
 164  				expEvs = append(expEvs, ev)
 165  				continue
 166  			}
 167  
 168  			// skip events that have been deleted by a proper deletion event
 169  			// Skip deletion check when ACL is "none" (open relay mode)
 170  			if !mode.IsOpen() {
 171  				if derr := d.CheckForDeleted(ev, nil); derr != nil {
 172  					log.T.F("QueryEvents: id=%s filtered out due to deletion: %v", idHex, derr)
 173  					continue
 174  				}
 175  			}
 176  
 177  			// Add the event to the results
 178  			evs = append(evs, ev)
 179  			// log.T.F("QueryEvents: id=%s SUCCESSFULLY FOUND, adding to results", idHex)
 180  		}
 181  
 182  		// sort the events by timestamp
 183  		sort.Slice(
 184  			evs, func(i, j int) bool {
 185  				return evs[i].CreatedAt > evs[j].CreatedAt
 186  			},
 187  		)
 188  		// Apply limit after processing
 189  		if f.Limit != nil && len(evs) > int(*f.Limit) {
 190  			evs = evs[:*f.Limit]
 191  		}
 192  	} else {
 193  		// non-IDs path
 194  		var idPkTs []*store.IdPkTs
 195  		// if f.Authors != nil && f.Authors.Len() > 0 && f.Kinds != nil && f.Kinds.Len() > 0 {
 196  		// log.T.F("QueryEvents: authors+kinds path, authors=%d kinds=%d", f.Authors.Len(), f.Kinds.Len())
 197  		// }
 198  		if idPkTs, err = d.QueryForIds(c, f); chk.E(err) {
 199  			return
 200  		}
 201  		// log.T.F("QueryEvents: QueryForIds returned %d candidates", len(idPkTs))
 202  		// Create a map to store versions of replaceable events
 203  		// If wantMultipleVersions is true, we keep multiple versions (sorted by timestamp)
 204  		// Otherwise, we keep only the latest
 205  		replaceableEvents := make(map[string]*event.E)
 206  		replaceableEventVersions := make(map[string]event.S) // For multiple versions
 207  		// Create a map to store the latest version of parameterized replaceable
 208  		// events
 209  		paramReplaceableEvents := make(map[string]map[string]*event.E)
 210  		paramReplaceableEventVersions := make(map[string]map[string]event.S) // For multiple versions
 211  		// Regular events that are not replaceable
 212  		var regularEvents event.S
 213  		// Map to track deletion events by kind and pubkey (for replaceable
 214  		// events)
 215  		deletionsByKindPubkey := make(map[string]bool)
 216  		// Map to track deletion events by kind, pubkey, and d-tag (for
 217  		// parameterized replaceable events). We store the newest delete timestamp per d-tag.
 218  		deletionsByKindPubkeyDTag := make(map[string]map[string]int64)
 219  		// Map to track specific event IDs that have been deleted
 220  		deletedEventIds := make(map[string]bool)
 221  		// Query for deletion events separately if we have authors in the filter
 222  		// We always need to fetch deletion events to build deletion maps, even if
 223  		// they're not explicitly requested in the kind filter
 224  		if f.Authors != nil && f.Authors.Len() > 0 {
 225  			// Create a filter for deletion events with the same authors
 226  			deletionFilter := &filter.F{
 227  				Kinds:   kind.NewS(kind.New(5)), // Kind 5 is deletion
 228  				Authors: f.Authors,
 229  			}
 230  
 231  			var deletionIdPkTs []*store.IdPkTs
 232  			if deletionIdPkTs, err = d.QueryForIds(
 233  				c, deletionFilter,
 234  			); chk.E(err) {
 235  				return
 236  			}
 237  
 238  			// Add deletion events to the list of events to process
 239  			idPkTs = append(idPkTs, deletionIdPkTs...)
 240  		}
 241  		// Prepare serials for batch fetch
 242  		var allSerials []*types.Uint40
 243  		allSerials = make([]*types.Uint40, 0, len(idPkTs))
 244  		serialToIdPk := make(map[uint64]*store.IdPkTs, len(idPkTs))
 245  		for _, idpk := range idPkTs {
 246  			ser := new(types.Uint40)
 247  			if err = ser.Set(idpk.Ser); err != nil {
 248  				continue
 249  			}
 250  			allSerials = append(allSerials, ser)
 251  			serialToIdPk[ser.Get()] = idpk
 252  		}
 253  
 254  		// Fetch all events in batch
 255  		var allEvents map[uint64]*event.E
 256  		if allEvents, err = d.FetchEventsBySerials(allSerials); err != nil {
 257  			log.E.F("QueryEvents: batch fetch failed in non-IDs path: %v", err)
 258  			return
 259  		}
 260  
 261  		// First pass: collect all deletion events
 262  		for serialValue, ev := range allEvents {
 263  			// Convert serial value back to Uint40 for expiration handling
 264  			ser := new(types.Uint40)
 265  			if err = ser.Set(serialValue); err != nil {
 266  				continue
 267  			}
 268  
 269  			// check for an expiration tag and delete after returning the result
 270  			// Skip expiration check when ACL is "none" (open relay mode)
 271  			if !mode.IsOpen() && CheckExpiration(ev) {
 272  				expDeletes = append(expDeletes, ser)
 273  				expEvs = append(expEvs, ev)
 274  				continue
 275  			}
 276  			// Process deletion events to build our deletion maps
 277  			// Skip deletion processing when ACL is "none" (open relay mode)
 278  			if !mode.IsOpen() && ev.Kind == kind.Deletion.K {
 279  				// Check for 'e' tags that directly reference event IDs
 280  				eTags := ev.Tags.GetAll([]byte("e"))
 281  				for _, eTag := range eTags {
 282  					if eTag.Len() < 2 {
 283  						continue
 284  					}
 285  					// We don't need to do anything with direct event ID
 286  					// references as we will filter those out in the second pass
 287  				}
 288  				// Check for 'a' tags that reference replaceable events
 289  				aTags := ev.Tags.GetAll([]byte("a"))
 290  				for _, aTag := range aTags {
 291  					if aTag.Len() < 2 {
 292  						continue
 293  					}
 294  					// Parse the 'a' tag value: kind:pubkey:d-tag (for parameterized) or kind:pubkey (for regular)
 295  					split := bytes.Split(aTag.Value(), []byte{':'})
 296  					if len(split) < 2 {
 297  						continue
 298  					}
 299  					// Parse the kind
 300  					kindStr := string(split[0])
 301  					kindInt, err := strconv.Atoi(kindStr)
 302  					if err != nil {
 303  						continue
 304  					}
 305  					kk := kind.New(uint16(kindInt))
 306  					// Process both regular and parameterized replaceable events
 307  					if !kind.IsReplaceable(kk.K) {
 308  						continue
 309  					}
 310  					// Parse the pubkey
 311  					var pk []byte
 312  					if pk, err = hex.DecAppend(nil, split[1]); err != nil {
 313  						continue
 314  					}
 315  					// Only allow users to delete their own events
 316  					if !utils.FastEqual(pk, ev.Pubkey) {
 317  						continue
 318  					}
 319  					// Create the key for the deletion map using hex
 320  					// representation of pubkey
 321  					key := hex.Enc(pk) + ":" + strconv.Itoa(int(kk.K))
 322  
 323  					if kind.IsParameterizedReplaceable(kk.K) {
 324  						// For parameterized replaceable events, use d-tag specific deletion
 325  						if len(split) < 3 {
 326  							continue
 327  						}
 328  						// Initialize the inner map if it doesn't exist
 329  						if _, exists := deletionsByKindPubkeyDTag[key]; !exists {
 330  							deletionsByKindPubkeyDTag[key] = make(map[string]int64)
 331  						}
 332  						// Record the newest delete timestamp for this d-tag
 333  						dValue := string(split[2])
 334  						if ts, ok := deletionsByKindPubkeyDTag[key][dValue]; !ok || ev.CreatedAt > ts {
 335  							deletionsByKindPubkeyDTag[key][dValue] = ev.CreatedAt
 336  						}
 337  					} else {
 338  						// For regular replaceable events, mark as deleted by kind/pubkey
 339  						deletionsByKindPubkey[key] = true
 340  					}
 341  				}
 342  				// For replaceable events, we need to check if there are any
 343  				// e-tags that reference events with the same kind and pubkey
 344  				for _, eTag := range eTags {
 345  					// Use ValueHex() to handle both binary and hex storage formats
 346  					eTagHex := eTag.ValueHex()
 347  					if len(eTagHex) != 64 {
 348  						continue
 349  					}
 350  					// Get the event ID from the e-tag
 351  					evId := make([]byte, sha256.Size)
 352  					if _, err = hex.DecBytes(evId, eTagHex); err != nil {
 353  						continue
 354  					}
 355  
 356  					// Look for the target event in our current batch instead of querying
 357  					var targetEv *event.E
 358  					for _, candidateEv := range allEvents {
 359  						if utils.FastEqual(candidateEv.ID, evId) {
 360  							targetEv = candidateEv
 361  							break
 362  						}
 363  					}
 364  
 365  					// DISABLED: Fetching target events not in current batch causes memory
 366  					// exhaustion under high concurrent load. Each GetSerialById call creates
 367  					// a Badger iterator that consumes significant memory. With many connections
 368  					// processing deletion events, this explodes memory usage (~300MB/connection).
 369  					// The deletion will still work when the target event is directly queried.
 370  					if targetEv == nil {
 371  						continue
 372  					}
 373  
 374  					// Only allow users to delete their own events
 375  					if !utils.FastEqual(targetEv.Pubkey, ev.Pubkey) {
 376  						continue
 377  					}
 378  					// Mark the specific event ID as deleted
 379  					deletedEventIds[hex.Enc(targetEv.ID)] = true
 380  					// Note: For e-tag deletions, we only mark the specific event as deleted,
 381  					// not all events of the same kind/pubkey
 382  				}
 383  			}
 384  		}
 385  		// Second pass: process all events, filtering out deleted ones
 386  		for _, ev := range allEvents {
 387  			// Add logging for tag filter debugging
 388  			if f.Tags != nil && f.Tags.Len() > 0 {
 389  				// var eventTags []string
 390  				// if ev.Tags != nil && ev.Tags.Len() > 0 {
 391  				// 	for _, t := range *ev.Tags {
 392  				// 		if t.Len() >= 2 {
 393  				// 			eventTags = append(
 394  				// 				eventTags,
 395  				// 				string(t.Key())+"="+string(t.Value()),
 396  				// 			)
 397  				// 		}
 398  				// 	}
 399  				// }
 400  				// log.T.F(
 401  				// 	"QueryEvents: processing event ID=%s kind=%d tags=%v",
 402  				// 	hex.Enc(ev.ID), ev.Kind, eventTags,
 403  				// )
 404  				// Check if this event matches ALL required tags in the filter
 405  				tagMatches := 0
 406  				for _, filterTag := range *f.Tags {
 407  					if filterTag.Len() >= 2 {
 408  						filterKey := filterTag.Key()
 409  						// Handle filter keys that start with # (remove the prefix for comparison)
 410  						var actualKey []byte
 411  						if len(filterKey) == 2 && filterKey[0] == '#' {
 412  							actualKey = filterKey[1:]
 413  						} else {
 414  							actualKey = filterKey
 415  						}
 416  						// Check if event has this tag key with any of the filter's values
 417  						eventHasTag := false
 418  						if ev.Tags != nil {
 419  							for _, eventTag := range *ev.Tags {
 420  								if eventTag.Len() >= 2 && bytes.Equal(
 421  									eventTag.Key(), actualKey,
 422  								) {
 423  									// Check if the event's tag value matches any of the filter's values
 424  									// Using TagValuesMatchUsingTagMethods handles binary/hex conversion
 425  									// for e/p tags automatically
 426  									for _, filterValue := range filterTag.T[1:] {
 427  										if TagValuesMatchUsingTagMethods(eventTag, filterValue) {
 428  											eventHasTag = true
 429  											break
 430  										}
 431  									}
 432  									if eventHasTag {
 433  										break
 434  									}
 435  								}
 436  							}
 437  						}
 438  						if eventHasTag {
 439  							tagMatches++
 440  						}
 441  						// log.T.F(
 442  						// 	"QueryEvents: tag filter %s (actual key: %s) matches: %v (total matches: %d/%d)",
 443  						// 	string(filterKey), string(actualKey), eventHasTag,
 444  						// 	tagMatches, f.Tags.Len(),
 445  						// )
 446  					}
 447  				}
 448  
 449  				// If not all tags match, skip this event
 450  				if tagMatches < f.Tags.Len() {
 451  					// log.T.F(
 452  					// 	"QueryEvents: event ID=%s SKIPPED - only matches %d/%d required tags",
 453  					// 	hex.Enc(ev.ID), tagMatches, f.Tags.Len(),
 454  					// )
 455  					continue
 456  				}
 457  				// log.T.F(
 458  				// 	"QueryEvents: event ID=%s PASSES all tag filters",
 459  				// 	hex.Enc(ev.ID),
 460  				// )
 461  			}
 462  
 463  			// Skip events with kind 5 (Deletion) unless explicitly requested in the filter
 464  			if ev.Kind == kind.Deletion.K {
 465  				// Check if kind 5 (deletion) is explicitly requested in the filter
 466  				kind5Requested := false
 467  				if f.Kinds != nil && f.Kinds.Len() > 0 {
 468  					for i := 0; i < f.Kinds.Len(); i++ {
 469  						if f.Kinds.K[i].K == kind.Deletion.K {
 470  							kind5Requested = true
 471  							break
 472  						}
 473  					}
 474  				}
 475  				if !kind5Requested {
 476  					continue
 477  				}
 478  			}
 479  			// Check if this event's ID is in the filter
 480  			isIdInFilter := false
 481  			if f.Ids != nil && f.Ids.Len() > 0 {
 482  				for i := 0; i < f.Ids.Len(); i++ {
 483  					if utils.FastEqual(ev.ID, (*f.Ids).T[i]) {
 484  						isIdInFilter = true
 485  						break
 486  					}
 487  				}
 488  			}
 489  			// Check if this specific event has been deleted
 490  			// Skip deletion checks when ACL is "none" (open relay mode)
 491  			aclActive := !mode.IsOpen()
 492  			eventIdHex := hex.Enc(ev.ID)
 493  			if aclActive && deletedEventIds[eventIdHex] {
 494  				// Skip this event if it has been specifically deleted
 495  				continue
 496  			}
 497  			if kind.IsReplaceable(ev.Kind) {
 498  				// For replaceable events, we only keep the latest version for
 499  				// each pubkey and kind, and only if it hasn't been deleted
 500  				key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
 501  				// For replaceable events, we need to be more careful with
 502  				// deletion Only skip this event if it has been deleted by
 503  				// kind/pubkey and is not in the filter AND there isn't a newer
 504  				// event with the same kind/pubkey
 505  				if aclActive && deletionsByKindPubkey[key] && !isIdInFilter {
 506  					// This replaceable event has been deleted, skip it
 507  					continue
 508  				} else if wantMultipleVersions {
 509  					// If wantMultipleVersions is true, collect all versions
 510  					replaceableEventVersions[key] = append(replaceableEventVersions[key], ev)
 511  				} else {
 512  					// Normal replaceable event handling - keep only the newest
 513  					existing, exists := replaceableEvents[key]
 514  					if !exists || ev.CreatedAt > existing.CreatedAt {
 515  						replaceableEvents[key] = ev
 516  					}
 517  				}
 518  			} else if kind.IsParameterizedReplaceable(ev.Kind) {
 519  				// For parameterized replaceable events, we need to consider the
 520  				// 'd' tag
 521  				key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
 522  
 523  				// Get the 'd' tag value
 524  				dTag := ev.Tags.GetFirst([]byte("d"))
 525  				var dValue string
 526  				if dTag != nil && dTag.Len() > 1 {
 527  					dValue = string(dTag.Value())
 528  				} else {
 529  					// If no 'd' tag, use empty string
 530  					dValue = ""
 531  				}
 532  
 533  				// Check if this event has been deleted via an a-tag
 534  				// Skip deletion check when ACL is "none" (open relay mode)
 535  				if aclActive {
 536  					if deletionMap, exists := deletionsByKindPubkeyDTag[key]; exists {
 537  						// If there is a deletion timestamp and this event is older than the deletion,
 538  						// and this event is not specifically requested by ID, skip it
 539  						if delTs, ok := deletionMap[dValue]; ok && ev.CreatedAt < delTs && !isIdInFilter {
 540  							continue
 541  						}
 542  					}
 543  				}
 544  
 545  				if wantMultipleVersions {
 546  					// If wantMultipleVersions is true, collect all versions
 547  					if _, exists := paramReplaceableEventVersions[key]; !exists {
 548  						paramReplaceableEventVersions[key] = make(map[string]event.S)
 549  					}
 550  					paramReplaceableEventVersions[key][dValue] = append(paramReplaceableEventVersions[key][dValue], ev)
 551  				} else {
 552  					// Initialize the inner map if it doesn't exist
 553  					if _, exists := paramReplaceableEvents[key]; !exists {
 554  						paramReplaceableEvents[key] = make(map[string]*event.E)
 555  					}
 556  
 557  					// Check if we already have an event with this 'd' tag value
 558  					existing, exists := paramReplaceableEvents[key][dValue]
 559  					// Only keep the newer event, regardless of processing order
 560  					if !exists {
 561  						// No existing event, add this one
 562  						paramReplaceableEvents[key][dValue] = ev
 563  					} else if ev.CreatedAt > existing.CreatedAt {
 564  						// This event is newer than the existing one, replace it
 565  						paramReplaceableEvents[key][dValue] = ev
 566  					}
 567  				}
 568  				// If this event is older than the existing one, ignore it
 569  			} else {
 570  				// Regular events
 571  				regularEvents = append(regularEvents, ev)
 572  			}
 573  		}
 574  		// Add all the latest replaceable events to the result
 575  		if wantMultipleVersions {
 576  			// Add all versions (sorted by timestamp, newest first)
 577  			for key, versions := range replaceableEventVersions {
 578  				// Sort versions by timestamp (newest first)
 579  				sort.Slice(versions, func(i, j int) bool {
 580  					return versions[i].CreatedAt > versions[j].CreatedAt
 581  				})
 582  				// Add versions up to the limit
 583  				limit := len(versions)
 584  				if f.Limit != nil && int(*f.Limit) < limit {
 585  					limit = int(*f.Limit)
 586  				}
 587  				for i := 0; i < limit && i < len(versions); i++ {
 588  					evs = append(evs, versions[i])
 589  				}
 590  				_ = key // Use key to avoid unused variable warning
 591  			}
 592  		} else {
 593  			// Add only the newest version of each replaceable event
 594  			for _, ev := range replaceableEvents {
 595  				evs = append(evs, ev)
 596  			}
 597  		}
 598  
 599  		// Add all the latest parameterized replaceable events to the result
 600  		if wantMultipleVersions {
 601  			// Add all versions (sorted by timestamp, newest first)
 602  			for key, dTagMap := range paramReplaceableEventVersions {
 603  				for dTag, versions := range dTagMap {
 604  					// Sort versions by timestamp (newest first)
 605  					sort.Slice(versions, func(i, j int) bool {
 606  						return versions[i].CreatedAt > versions[j].CreatedAt
 607  					})
 608  					// Add versions up to the limit
 609  					limit := len(versions)
 610  					if f.Limit != nil && int(*f.Limit) < limit {
 611  						limit = int(*f.Limit)
 612  					}
 613  					for i := 0; i < limit && i < len(versions); i++ {
 614  						evs = append(evs, versions[i])
 615  					}
 616  					_ = key  // Use key to avoid unused variable warning
 617  					_ = dTag // Use dTag to avoid unused variable warning
 618  				}
 619  			}
 620  		} else {
 621  			// Add only the newest version of each parameterized replaceable event
 622  			for _, innerMap := range paramReplaceableEvents {
 623  				for _, ev := range innerMap {
 624  					evs = append(evs, ev)
 625  				}
 626  			}
 627  		}
 628  		// Add all regular events to the result
 629  		evs = append(evs, regularEvents...)
 630  		// Sort all events by timestamp (newest first)
 631  		sort.Slice(
 632  			evs, func(i, j int) bool {
 633  				return evs[i].CreatedAt > evs[j].CreatedAt
 634  			},
 635  		)
 636  		// Apply limit after processing replaceable/addressable events
 637  		if f.Limit != nil && len(evs) > int(*f.Limit) {
 638  			evs = evs[:*f.Limit]
 639  		}
 640  		// TODO: DISABLED - inline deletion of expired events causes Badger race conditions
 641  		// under high concurrent load ("assignment to entry in nil map" panic).
 642  		// Expired events should be cleaned up by a separate, rate-limited background
 643  		// worker instead of being deleted inline during query processing.
 644  		// See: pkg/storage/gc.go TODOs for proper batch deletion implementation.
 645  		if len(expDeletes) > 0 {
 646  			log.D.F("QueryEvents: found %d expired events (deletion disabled)", len(expDeletes))
 647  		}
 648  	}
 649  
 650  	return
 651  }
 652  
 653  // QueryDeleteEventsByTargetId queries for delete events that target a specific event ID
 654  func (d *D) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (
 655  	evs event.S, err error,
 656  ) {
 657  	// Create a filter for deletion events with the target event ID in e-tags
 658  	f := &filter.F{
 659  		Kinds: kind.NewS(kind.Deletion),
 660  		Tags: tag.NewS(
 661  			tag.NewFromAny("#e", hex.Enc(targetEventId)),
 662  		),
 663  	}
 664  
 665  	// Query for the delete events
 666  	if evs, err = d.QueryEventsWithOptions(c, f, true, false); chk.E(err) {
 667  		return
 668  	}
 669  
 670  	return
 671  }
 672