save-event.go raw

   1  //go:build js && wasm
   2  
   3  package wasmdb
   4  
   5  import (
   6  	"bytes"
   7  	"context"
   8  	"errors"
   9  	"fmt"
  10  	"strings"
  11  
  12  	"github.com/aperturerobotics/go-indexeddb/idb"
  13  	"github.com/hack-pad/safejs"
  14  	"next.orly.dev/pkg/lol/chk"
  15  
  16  	"next.orly.dev/pkg/nostr/encoders/event"
  17  	"next.orly.dev/pkg/nostr/encoders/hex"
  18  	"next.orly.dev/pkg/nostr/encoders/kind"
  19  	"next.orly.dev/pkg/nostr/encoders/tag"
  20  	"next.orly.dev/pkg/database"
  21  	"next.orly.dev/pkg/database/indexes"
  22  	"next.orly.dev/pkg/database/indexes/types"
  23  )
  24  
  25  var (
  26  	// ErrOlderThanExisting is returned when a candidate event is older than an existing replaceable/addressable event.
  27  	ErrOlderThanExisting = errors.New("older than existing event")
  28  	// ErrMissingDTag is returned when a parameterized replaceable event lacks the required 'd' tag.
  29  	ErrMissingDTag = errors.New("event is missing a d tag identifier")
  30  )
  31  
  32  // SaveEvent saves an event to the database, generating all necessary indexes.
  33  func (w *W) SaveEvent(c context.Context, ev *event.E) (replaced bool, err error) {
  34  	if ev == nil {
  35  		err = errors.New("nil event")
  36  		return
  37  	}
  38  
  39  	// Reject ephemeral events (kinds 20000-29999) - they should never be stored
  40  	if ev.Kind >= 20000 && ev.Kind <= 29999 {
  41  		err = errors.New("blocked: ephemeral events should not be stored")
  42  		return
  43  	}
  44  
  45  	// Validate kind 3 (follow list) events have at least one p tag
  46  	if ev.Kind == 3 {
  47  		hasPTag := false
  48  		if ev.Tags != nil {
  49  			for _, t := range *ev.Tags {
  50  				if t != nil && t.Len() >= 2 {
  51  					key := t.Key()
  52  					if len(key) == 1 && key[0] == 'p' {
  53  						hasPTag = true
  54  						break
  55  					}
  56  				}
  57  			}
  58  		}
  59  		if !hasPTag {
  60  			w.Logger.Warnf("SaveEvent: rejecting kind 3 event without p tags from pubkey %x", ev.Pubkey)
  61  			err = errors.New("blocked: kind 3 follow list events must have at least one p tag")
  62  			return
  63  		}
  64  	}
  65  
  66  	// Check if the event already exists
  67  	var ser *types.Uint40
  68  	if ser, err = w.GetSerialById(ev.ID); err == nil && ser != nil {
  69  		err = errors.New("blocked: event already exists: " + hex.Enc(ev.ID[:]))
  70  		return
  71  	}
  72  
  73  	// If the error is "id not found", we can proceed
  74  	if err != nil && strings.Contains(err.Error(), "id not found") {
  75  		err = nil
  76  	} else if err != nil {
  77  		return
  78  	}
  79  
  80  	// Check for replacement - only validate, don't delete old events
  81  	if kind.IsReplaceable(ev.Kind) || kind.IsParameterizedReplaceable(ev.Kind) {
  82  		var werr error
  83  		if replaced, _, werr = w.WouldReplaceEvent(ev); werr != nil {
  84  			if errors.Is(werr, ErrOlderThanExisting) {
  85  				if kind.IsReplaceable(ev.Kind) {
  86  					err = errors.New("blocked: event is older than existing replaceable event")
  87  				} else {
  88  					err = errors.New("blocked: event is older than existing addressable event")
  89  				}
  90  				return
  91  			}
  92  			if errors.Is(werr, ErrMissingDTag) {
  93  				err = ErrMissingDTag
  94  				return
  95  			}
  96  			return
  97  		}
  98  	}
  99  
 100  	// Get the next sequence number for the event
 101  	serial, err := w.nextEventSerial()
 102  	if err != nil {
 103  		return
 104  	}
 105  
 106  	// Generate all indexes for the event
 107  	idxs, err := database.GetIndexesForEvent(ev, serial)
 108  	if err != nil {
 109  		return
 110  	}
 111  
 112  	// Serialize event to binary
 113  	eventDataBuf := new(bytes.Buffer)
 114  	ev.MarshalBinary(eventDataBuf)
 115  	eventData := eventDataBuf.Bytes()
 116  
 117  	// Determine storage strategy
 118  	smallEventThreshold := 1024 // Could be made configurable
 119  	isSmallEvent := len(eventData) <= smallEventThreshold
 120  	isReplaceableEvent := kind.IsReplaceable(ev.Kind)
 121  	isAddressableEvent := kind.IsParameterizedReplaceable(ev.Kind)
 122  
 123  	// Create serial type
 124  	ser = new(types.Uint40)
 125  	if err = ser.Set(serial); chk.E(err) {
 126  		return
 127  	}
 128  
 129  	// Start a transaction to save the event and all its indexes
 130  	// We need to include all object stores we'll write to
 131  	storesToWrite := []string{
 132  		string(indexes.IdPrefix),
 133  		string(indexes.FullIdPubkeyPrefix),
 134  		string(indexes.CreatedAtPrefix),
 135  		string(indexes.PubkeyPrefix),
 136  		string(indexes.KindPrefix),
 137  		string(indexes.KindPubkeyPrefix),
 138  		string(indexes.TagPrefix),
 139  		string(indexes.TagKindPrefix),
 140  		string(indexes.TagPubkeyPrefix),
 141  		string(indexes.TagKindPubkeyPrefix),
 142  		string(indexes.WordPrefix),
 143  	}
 144  
 145  	// Add event storage store
 146  	if isSmallEvent {
 147  		storesToWrite = append(storesToWrite, string(indexes.SmallEventPrefix))
 148  	} else {
 149  		storesToWrite = append(storesToWrite, string(indexes.EventPrefix))
 150  	}
 151  
 152  	// Add specialized stores if needed
 153  	if isAddressableEvent && isSmallEvent {
 154  		storesToWrite = append(storesToWrite, string(indexes.AddressableEventPrefix))
 155  	} else if isReplaceableEvent && isSmallEvent {
 156  		storesToWrite = append(storesToWrite, string(indexes.ReplaceableEventPrefix))
 157  	}
 158  
 159  	// Start transaction
 160  	tx, err := w.db.Transaction(idb.TransactionReadWrite, storesToWrite[0], storesToWrite[1:]...)
 161  	if err != nil {
 162  		return false, fmt.Errorf("failed to start transaction: %w", err)
 163  	}
 164  
 165  	// Save each index to its respective object store
 166  	for _, key := range idxs {
 167  		if len(key) < 3 {
 168  			continue
 169  		}
 170  		// Extract store name from 3-byte prefix
 171  		storeName := string(key[:3])
 172  
 173  		store, storeErr := tx.ObjectStore(storeName)
 174  		if storeErr != nil {
 175  			w.Logger.Warnf("SaveEvent: failed to get object store %s: %v", storeName, storeErr)
 176  			continue
 177  		}
 178  
 179  		// Use the full key as the IndexedDB key, empty value
 180  		keyJS := bytesToSafeValue(key)
 181  		_, putErr := store.PutKey(keyJS, safejs.Null())
 182  		if putErr != nil {
 183  			w.Logger.Warnf("SaveEvent: failed to put index %s: %v", storeName, putErr)
 184  		}
 185  	}
 186  
 187  	// Store the event data
 188  	if isSmallEvent {
 189  		// Small event: store inline with sev prefix
 190  		// Format: sev|serial|size_uint16|event_data
 191  		keyBuf := new(bytes.Buffer)
 192  		if err = indexes.SmallEventEnc(ser).MarshalWrite(keyBuf); chk.E(err) {
 193  			return
 194  		}
 195  		// Append size as uint16 big-endian
 196  		sizeBytes := []byte{byte(len(eventData) >> 8), byte(len(eventData))}
 197  		keyBuf.Write(sizeBytes)
 198  		keyBuf.Write(eventData)
 199  
 200  		store, storeErr := tx.ObjectStore(string(indexes.SmallEventPrefix))
 201  		if storeErr == nil {
 202  			keyJS := bytesToSafeValue(keyBuf.Bytes())
 203  			store.PutKey(keyJS, safejs.Null())
 204  		}
 205  	} else {
 206  		// Large event: store separately with evt prefix
 207  		keyBuf := new(bytes.Buffer)
 208  		if err = indexes.EventEnc(ser).MarshalWrite(keyBuf); chk.E(err) {
 209  			return
 210  		}
 211  
 212  		store, storeErr := tx.ObjectStore(string(indexes.EventPrefix))
 213  		if storeErr == nil {
 214  			keyJS := bytesToSafeValue(keyBuf.Bytes())
 215  			valueJS := bytesToSafeValue(eventData)
 216  			store.PutKey(keyJS, valueJS)
 217  		}
 218  	}
 219  
 220  	// Store specialized keys for replaceable/addressable events
 221  	if isAddressableEvent && isSmallEvent {
 222  		dTag := ev.Tags.GetFirst([]byte("d"))
 223  		if dTag != nil {
 224  			pubHash := new(types.PubHash)
 225  			pubHash.FromPubkey(ev.Pubkey)
 226  			kindVal := new(types.Uint16)
 227  			kindVal.Set(ev.Kind)
 228  			dTagHash := new(types.Ident)
 229  			dTagHash.FromIdent(dTag.Value())
 230  
 231  			keyBuf := new(bytes.Buffer)
 232  			if err = indexes.AddressableEventEnc(pubHash, kindVal, dTagHash).MarshalWrite(keyBuf); chk.E(err) {
 233  				return
 234  			}
 235  			sizeBytes := []byte{byte(len(eventData) >> 8), byte(len(eventData))}
 236  			keyBuf.Write(sizeBytes)
 237  			keyBuf.Write(eventData)
 238  
 239  			store, storeErr := tx.ObjectStore(string(indexes.AddressableEventPrefix))
 240  			if storeErr == nil {
 241  				keyJS := bytesToSafeValue(keyBuf.Bytes())
 242  				store.PutKey(keyJS, safejs.Null())
 243  			}
 244  		}
 245  	} else if isReplaceableEvent && isSmallEvent {
 246  		pubHash := new(types.PubHash)
 247  		pubHash.FromPubkey(ev.Pubkey)
 248  		kindVal := new(types.Uint16)
 249  		kindVal.Set(ev.Kind)
 250  
 251  		keyBuf := new(bytes.Buffer)
 252  		if err = indexes.ReplaceableEventEnc(pubHash, kindVal).MarshalWrite(keyBuf); chk.E(err) {
 253  			return
 254  		}
 255  		sizeBytes := []byte{byte(len(eventData) >> 8), byte(len(eventData))}
 256  		keyBuf.Write(sizeBytes)
 257  		keyBuf.Write(eventData)
 258  
 259  		store, storeErr := tx.ObjectStore(string(indexes.ReplaceableEventPrefix))
 260  		if storeErr == nil {
 261  			keyJS := bytesToSafeValue(keyBuf.Bytes())
 262  			store.PutKey(keyJS, safejs.Null())
 263  		}
 264  	}
 265  
 266  	// Commit transaction
 267  	if err = tx.Await(c); err != nil {
 268  		return false, fmt.Errorf("failed to commit transaction: %w", err)
 269  	}
 270  
 271  	w.Logger.Debugf("SaveEvent: saved event %x (kind %d, %d bytes, %d indexes)",
 272  		ev.ID[:8], ev.Kind, len(eventData), len(idxs))
 273  
 274  	return
 275  }
 276  
 277  // WouldReplaceEvent checks if the provided event would replace existing events
 278  func (w *W) WouldReplaceEvent(ev *event.E) (bool, types.Uint40s, error) {
 279  	// Only relevant for replaceable or parameterized replaceable kinds
 280  	if !(kind.IsReplaceable(ev.Kind) || kind.IsParameterizedReplaceable(ev.Kind)) {
 281  		return false, nil, nil
 282  	}
 283  
 284  	// Build filter for existing events
 285  	var f interface{}
 286  	if kind.IsReplaceable(ev.Kind) {
 287  		// For now, simplified check - would need full filter implementation
 288  		return false, nil, nil
 289  	} else {
 290  		// Parameterized replaceable requires 'd' tag
 291  		dTag := ev.Tags.GetFirst([]byte("d"))
 292  		if dTag == nil {
 293  			return false, nil, ErrMissingDTag
 294  		}
 295  		// Simplified - full implementation would query existing events
 296  		_ = f
 297  	}
 298  
 299  	// Simplified implementation - assume no conflicts for now
 300  	// Full implementation would query the database and compare timestamps
 301  	return false, nil, nil
 302  }
 303  
 304  // GetSerialById looks up the serial number for an event ID
 305  func (w *W) GetSerialById(id []byte) (ser *types.Uint40, err error) {
 306  	if len(id) != 32 {
 307  		return nil, errors.New("invalid event ID length")
 308  	}
 309  
 310  	// Create ID hash
 311  	idHash := new(types.IdHash)
 312  	if err = idHash.FromId(id); chk.E(err) {
 313  		return nil, err
 314  	}
 315  
 316  	// Build the prefix to search for
 317  	keyBuf := new(bytes.Buffer)
 318  	indexes.IdEnc(idHash, nil).MarshalWrite(keyBuf)
 319  	prefix := keyBuf.Bytes()[:11] // 3 prefix + 8 id hash
 320  
 321  	// Search in the eid object store
 322  	tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.IdPrefix))
 323  	if err != nil {
 324  		return nil, err
 325  	}
 326  
 327  	store, err := tx.ObjectStore(string(indexes.IdPrefix))
 328  	if err != nil {
 329  		return nil, err
 330  	}
 331  
 332  	// Use cursor to find matching key
 333  	cursorReq, err := store.OpenCursor(idb.CursorNext)
 334  	if err != nil {
 335  		return nil, err
 336  	}
 337  
 338  	err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error {
 339  		keyVal, keyErr := cursor.Key()
 340  		if keyErr != nil {
 341  			return keyErr
 342  		}
 343  
 344  		keyBytes := safeValueToBytes(keyVal)
 345  		if len(keyBytes) >= len(prefix) && bytes.HasPrefix(keyBytes, prefix) {
 346  			// Found matching key, extract serial from last 5 bytes
 347  			if len(keyBytes) >= 16 { // 3 + 8 + 5
 348  				ser = new(types.Uint40)
 349  				ser.UnmarshalRead(bytes.NewReader(keyBytes[11:16]))
 350  				return errors.New("found") // Stop iteration
 351  			}
 352  		}
 353  
 354  		return cursor.Continue()
 355  	})
 356  
 357  	if ser != nil {
 358  		return ser, nil
 359  	}
 360  	if err != nil && err.Error() != "found" {
 361  		return nil, err
 362  	}
 363  
 364  	return nil, errors.New("id not found in database")
 365  }
 366  
 367  // GetSerialsByIds looks up serial numbers for multiple event IDs
 368  func (w *W) GetSerialsByIds(ids *tag.T) (serials map[string]*types.Uint40, err error) {
 369  	serials = make(map[string]*types.Uint40)
 370  
 371  	if ids == nil {
 372  		return
 373  	}
 374  
 375  	for i := 1; i < ids.Len(); i++ {
 376  		idBytes := ids.T[i]
 377  		if len(idBytes) == 64 {
 378  			// Hex encoded ID
 379  			var decoded []byte
 380  			decoded, err = hex.Dec(string(idBytes))
 381  			if err != nil {
 382  				continue
 383  			}
 384  			idBytes = decoded
 385  		}
 386  
 387  		if len(idBytes) == 32 {
 388  			var ser *types.Uint40
 389  			ser, err = w.GetSerialById(idBytes)
 390  			if err == nil && ser != nil {
 391  				serials[hex.Enc(idBytes)] = ser
 392  			}
 393  		}
 394  	}
 395  
 396  	err = nil
 397  	return
 398  }
 399  
 400  // GetSerialsByIdsWithFilter looks up serial numbers with a filter function
 401  func (w *W) GetSerialsByIdsWithFilter(ids *tag.T, fn func(ev *event.E, ser *types.Uint40) bool) (serials map[string]*types.Uint40, err error) {
 402  	allSerials, err := w.GetSerialsByIds(ids)
 403  	if err != nil {
 404  		return nil, err
 405  	}
 406  
 407  	if fn == nil {
 408  		return allSerials, nil
 409  	}
 410  
 411  	serials = make(map[string]*types.Uint40)
 412  	for idHex, ser := range allSerials {
 413  		ev, fetchErr := w.FetchEventBySerial(ser)
 414  		if fetchErr != nil {
 415  			continue
 416  		}
 417  		if fn(ev, ser) {
 418  			serials[idHex] = ser
 419  		}
 420  	}
 421  
 422  	return serials, nil
 423  }
 424