fetch-event.go raw

   1  //go:build js && wasm
   2  
   3  package wasmdb
   4  
   5  import (
   6  	"bytes"
   7  	"errors"
   8  
   9  	"github.com/aperturerobotics/go-indexeddb/idb"
  10  	"next.orly.dev/pkg/lol/chk"
  11  
  12  	"next.orly.dev/pkg/nostr/encoders/event"
  13  	"next.orly.dev/pkg/database/indexes"
  14  	"next.orly.dev/pkg/database/indexes/types"
  15  	"next.orly.dev/pkg/interfaces/store"
  16  )
  17  
  18  // FetchEventBySerial retrieves an event by its serial number
  19  func (w *W) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) {
  20  	if ser == nil {
  21  		return nil, errors.New("nil serial")
  22  	}
  23  
  24  	// First try small event store (sev prefix)
  25  	ev, err = w.fetchSmallEvent(ser)
  26  	if err == nil && ev != nil {
  27  		return ev, nil
  28  	}
  29  
  30  	// Then try large event store (evt prefix)
  31  	ev, err = w.fetchLargeEvent(ser)
  32  	if err == nil && ev != nil {
  33  		return ev, nil
  34  	}
  35  
  36  	return nil, errors.New("event not found")
  37  }
  38  
  39  // fetchSmallEvent fetches an event from the small event store
  40  func (w *W) fetchSmallEvent(ser *types.Uint40) (*event.E, error) {
  41  	// Build the key prefix
  42  	keyBuf := new(bytes.Buffer)
  43  	if err := indexes.SmallEventEnc(ser).MarshalWrite(keyBuf); chk.E(err) {
  44  		return nil, err
  45  	}
  46  	prefix := keyBuf.Bytes()
  47  
  48  	// Open transaction
  49  	tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.SmallEventPrefix))
  50  	if err != nil {
  51  		return nil, err
  52  	}
  53  
  54  	store, err := tx.ObjectStore(string(indexes.SmallEventPrefix))
  55  	if err != nil {
  56  		return nil, err
  57  	}
  58  
  59  	// Use cursor to find matching key
  60  	cursorReq, err := store.OpenCursor(idb.CursorNext)
  61  	if err != nil {
  62  		return nil, err
  63  	}
  64  
  65  	var foundEvent *event.E
  66  	err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error {
  67  		keyVal, keyErr := cursor.Key()
  68  		if keyErr != nil {
  69  			return keyErr
  70  		}
  71  
  72  		keyBytes := safeValueToBytes(keyVal)
  73  		if len(keyBytes) >= len(prefix) && bytes.HasPrefix(keyBytes, prefix) {
  74  			// Found matching key
  75  			// Format: sev|serial(5)|size(2)|data(variable)
  76  			if len(keyBytes) > 10 { // 3 + 5 + 2 = 10 minimum
  77  				sizeOffset := 8 // 3 prefix + 5 serial
  78  				if len(keyBytes) > sizeOffset+2 {
  79  					size := int(keyBytes[sizeOffset])<<8 | int(keyBytes[sizeOffset+1])
  80  					dataStart := sizeOffset + 2
  81  					if len(keyBytes) >= dataStart+size {
  82  						eventData := keyBytes[dataStart : dataStart+size]
  83  						ev := new(event.E)
  84  						if unmarshalErr := ev.UnmarshalBinary(bytes.NewReader(eventData)); unmarshalErr == nil {
  85  							foundEvent = ev
  86  							return errors.New("found") // Stop iteration
  87  						}
  88  					}
  89  				}
  90  			}
  91  		}
  92  
  93  		return cursor.Continue()
  94  	})
  95  
  96  	if foundEvent != nil {
  97  		return foundEvent, nil
  98  	}
  99  	if err != nil && err.Error() != "found" {
 100  		return nil, err
 101  	}
 102  
 103  	return nil, errors.New("small event not found")
 104  }
 105  
 106  // fetchLargeEvent fetches an event from the large event store
 107  func (w *W) fetchLargeEvent(ser *types.Uint40) (*event.E, error) {
 108  	// Build the key
 109  	keyBuf := new(bytes.Buffer)
 110  	if err := indexes.EventEnc(ser).MarshalWrite(keyBuf); chk.E(err) {
 111  		return nil, err
 112  	}
 113  
 114  	// Open transaction
 115  	tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.EventPrefix))
 116  	if err != nil {
 117  		return nil, err
 118  	}
 119  
 120  	store, err := tx.ObjectStore(string(indexes.EventPrefix))
 121  	if err != nil {
 122  		return nil, err
 123  	}
 124  
 125  	// Get the value directly
 126  	keyJS := bytesToSafeValue(keyBuf.Bytes())
 127  	req, err := store.Get(keyJS)
 128  	if err != nil {
 129  		return nil, err
 130  	}
 131  
 132  	val, err := req.Await(w.ctx)
 133  	if err != nil {
 134  		return nil, err
 135  	}
 136  
 137  	if val.IsUndefined() || val.IsNull() {
 138  		return nil, errors.New("large event not found")
 139  	}
 140  
 141  	eventData := safeValueToBytes(val)
 142  	if len(eventData) == 0 {
 143  		return nil, errors.New("empty event data")
 144  	}
 145  
 146  	ev := new(event.E)
 147  	if err := ev.UnmarshalBinary(bytes.NewReader(eventData)); err != nil {
 148  		return nil, err
 149  	}
 150  
 151  	return ev, nil
 152  }
 153  
 154  // FetchEventsBySerials retrieves multiple events by their serial numbers
 155  func (w *W) FetchEventsBySerials(serials []*types.Uint40) (events map[uint64]*event.E, err error) {
 156  	events = make(map[uint64]*event.E)
 157  
 158  	for _, ser := range serials {
 159  		if ser == nil {
 160  			continue
 161  		}
 162  		ev, fetchErr := w.FetchEventBySerial(ser)
 163  		if fetchErr == nil && ev != nil {
 164  			events[ser.Get()] = ev
 165  		}
 166  	}
 167  
 168  	return events, nil
 169  }
 170  
 171  // GetFullIdPubkeyBySerial retrieves the ID, pubkey hash, and timestamp for a serial
 172  func (w *W) GetFullIdPubkeyBySerial(ser *types.Uint40) (fidpk *store.IdPkTs, err error) {
 173  	if ser == nil {
 174  		return nil, errors.New("nil serial")
 175  	}
 176  
 177  	// Build the prefix to search for
 178  	keyBuf := new(bytes.Buffer)
 179  	indexes.FullIdPubkeyEnc(ser, nil, nil, nil).MarshalWrite(keyBuf)
 180  	prefix := keyBuf.Bytes()[:8] // 3 prefix + 5 serial
 181  
 182  	// Search in the fpc object store
 183  	tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.FullIdPubkeyPrefix))
 184  	if err != nil {
 185  		return nil, err
 186  	}
 187  
 188  	objStore, err := tx.ObjectStore(string(indexes.FullIdPubkeyPrefix))
 189  	if err != nil {
 190  		return nil, err
 191  	}
 192  
 193  	// Use cursor to find matching key
 194  	cursorReq, err := objStore.OpenCursor(idb.CursorNext)
 195  	if err != nil {
 196  		return nil, err
 197  	}
 198  
 199  	err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error {
 200  		keyVal, keyErr := cursor.Key()
 201  		if keyErr != nil {
 202  			return keyErr
 203  		}
 204  
 205  		keyBytes := safeValueToBytes(keyVal)
 206  		if len(keyBytes) >= len(prefix) && bytes.HasPrefix(keyBytes, prefix) {
 207  			// Found matching key
 208  			// Format: fpc|serial(5)|id(32)|pubkey_hash(8)|timestamp(8)
 209  			if len(keyBytes) >= 56 { // 3 + 5 + 32 + 8 + 8 = 56
 210  				fidpk = &store.IdPkTs{
 211  					Id:  make([]byte, 32),
 212  					Pub: make([]byte, 8),
 213  					Ts:  0,
 214  				}
 215  				copy(fidpk.Id, keyBytes[8:40])
 216  				copy(fidpk.Pub, keyBytes[40:48])
 217  				// Parse timestamp (big-endian uint64)
 218  				var ts int64
 219  				for i := 0; i < 8; i++ {
 220  					ts = (ts << 8) | int64(keyBytes[48+i])
 221  				}
 222  				fidpk.Ts = ts
 223  				fidpk.Ser = ser.Get()
 224  				return errors.New("found") // Stop iteration
 225  			}
 226  		}
 227  
 228  		return cursor.Continue()
 229  	})
 230  
 231  	if fidpk != nil {
 232  		return fidpk, nil
 233  	}
 234  	if err != nil && err.Error() != "found" {
 235  		return nil, err
 236  	}
 237  
 238  	return nil, errors.New("full id pubkey not found")
 239  }
 240  
 241  // GetFullIdPubkeyBySerials retrieves ID/pubkey/timestamp for multiple serials
 242  func (w *W) GetFullIdPubkeyBySerials(sers []*types.Uint40) (fidpks []*store.IdPkTs, err error) {
 243  	fidpks = make([]*store.IdPkTs, 0, len(sers))
 244  
 245  	for _, ser := range sers {
 246  		if ser == nil {
 247  			continue
 248  		}
 249  		fidpk, fetchErr := w.GetFullIdPubkeyBySerial(ser)
 250  		if fetchErr == nil && fidpk != nil {
 251  			fidpks = append(fidpks, fidpk)
 252  		}
 253  	}
 254  
 255  	return fidpks, nil
 256  }
 257