You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
6.2 KiB
256 lines
6.2 KiB
//go:build js && wasm |
|
|
|
package wasmdb |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
|
|
"github.com/aperturerobotics/go-indexeddb/idb" |
|
"lol.mleku.dev/chk" |
|
|
|
"git.mleku.dev/mleku/nostr/encoders/event" |
|
"next.orly.dev/pkg/database/indexes" |
|
"next.orly.dev/pkg/database/indexes/types" |
|
"next.orly.dev/pkg/interfaces/store" |
|
) |
|
|
|
// FetchEventBySerial retrieves an event by its serial number |
|
func (w *W) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) { |
|
if ser == nil { |
|
return nil, errors.New("nil serial") |
|
} |
|
|
|
// First try small event store (sev prefix) |
|
ev, err = w.fetchSmallEvent(ser) |
|
if err == nil && ev != nil { |
|
return ev, nil |
|
} |
|
|
|
// Then try large event store (evt prefix) |
|
ev, err = w.fetchLargeEvent(ser) |
|
if err == nil && ev != nil { |
|
return ev, nil |
|
} |
|
|
|
return nil, errors.New("event not found") |
|
} |
|
|
|
// fetchSmallEvent fetches an event from the small event store |
|
func (w *W) fetchSmallEvent(ser *types.Uint40) (*event.E, error) { |
|
// Build the key prefix |
|
keyBuf := new(bytes.Buffer) |
|
if err := indexes.SmallEventEnc(ser).MarshalWrite(keyBuf); chk.E(err) { |
|
return nil, err |
|
} |
|
prefix := keyBuf.Bytes() |
|
|
|
// Open transaction |
|
tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.SmallEventPrefix)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
store, err := tx.ObjectStore(string(indexes.SmallEventPrefix)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Use cursor to find matching key |
|
cursorReq, err := store.OpenCursor(idb.CursorNext) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var foundEvent *event.E |
|
err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error { |
|
keyVal, keyErr := cursor.Key() |
|
if keyErr != nil { |
|
return keyErr |
|
} |
|
|
|
keyBytes := safeValueToBytes(keyVal) |
|
if len(keyBytes) >= len(prefix) && bytes.HasPrefix(keyBytes, prefix) { |
|
// Found matching key |
|
// Format: sev|serial(5)|size(2)|data(variable) |
|
if len(keyBytes) > 10 { // 3 + 5 + 2 = 10 minimum |
|
sizeOffset := 8 // 3 prefix + 5 serial |
|
if len(keyBytes) > sizeOffset+2 { |
|
size := int(keyBytes[sizeOffset])<<8 | int(keyBytes[sizeOffset+1]) |
|
dataStart := sizeOffset + 2 |
|
if len(keyBytes) >= dataStart+size { |
|
eventData := keyBytes[dataStart : dataStart+size] |
|
ev := new(event.E) |
|
if unmarshalErr := ev.UnmarshalBinary(bytes.NewReader(eventData)); unmarshalErr == nil { |
|
foundEvent = ev |
|
return errors.New("found") // Stop iteration |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return cursor.Continue() |
|
}) |
|
|
|
if foundEvent != nil { |
|
return foundEvent, nil |
|
} |
|
if err != nil && err.Error() != "found" { |
|
return nil, err |
|
} |
|
|
|
return nil, errors.New("small event not found") |
|
} |
|
|
|
// fetchLargeEvent fetches an event from the large event store |
|
func (w *W) fetchLargeEvent(ser *types.Uint40) (*event.E, error) { |
|
// Build the key |
|
keyBuf := new(bytes.Buffer) |
|
if err := indexes.EventEnc(ser).MarshalWrite(keyBuf); chk.E(err) { |
|
return nil, err |
|
} |
|
|
|
// Open transaction |
|
tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.EventPrefix)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
store, err := tx.ObjectStore(string(indexes.EventPrefix)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Get the value directly |
|
keyJS := bytesToSafeValue(keyBuf.Bytes()) |
|
req, err := store.Get(keyJS) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
val, err := req.Await(w.ctx) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if val.IsUndefined() || val.IsNull() { |
|
return nil, errors.New("large event not found") |
|
} |
|
|
|
eventData := safeValueToBytes(val) |
|
if len(eventData) == 0 { |
|
return nil, errors.New("empty event data") |
|
} |
|
|
|
ev := new(event.E) |
|
if err := ev.UnmarshalBinary(bytes.NewReader(eventData)); err != nil { |
|
return nil, err |
|
} |
|
|
|
return ev, nil |
|
} |
|
|
|
// FetchEventsBySerials retrieves multiple events by their serial numbers |
|
func (w *W) FetchEventsBySerials(serials []*types.Uint40) (events map[uint64]*event.E, err error) { |
|
events = make(map[uint64]*event.E) |
|
|
|
for _, ser := range serials { |
|
if ser == nil { |
|
continue |
|
} |
|
ev, fetchErr := w.FetchEventBySerial(ser) |
|
if fetchErr == nil && ev != nil { |
|
events[ser.Get()] = ev |
|
} |
|
} |
|
|
|
return events, nil |
|
} |
|
|
|
// GetFullIdPubkeyBySerial retrieves the ID, pubkey hash, and timestamp for a serial |
|
func (w *W) GetFullIdPubkeyBySerial(ser *types.Uint40) (fidpk *store.IdPkTs, err error) { |
|
if ser == nil { |
|
return nil, errors.New("nil serial") |
|
} |
|
|
|
// Build the prefix to search for |
|
keyBuf := new(bytes.Buffer) |
|
indexes.FullIdPubkeyEnc(ser, nil, nil, nil).MarshalWrite(keyBuf) |
|
prefix := keyBuf.Bytes()[:8] // 3 prefix + 5 serial |
|
|
|
// Search in the fpc object store |
|
tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.FullIdPubkeyPrefix)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
objStore, err := tx.ObjectStore(string(indexes.FullIdPubkeyPrefix)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Use cursor to find matching key |
|
cursorReq, err := objStore.OpenCursor(idb.CursorNext) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error { |
|
keyVal, keyErr := cursor.Key() |
|
if keyErr != nil { |
|
return keyErr |
|
} |
|
|
|
keyBytes := safeValueToBytes(keyVal) |
|
if len(keyBytes) >= len(prefix) && bytes.HasPrefix(keyBytes, prefix) { |
|
// Found matching key |
|
// Format: fpc|serial(5)|id(32)|pubkey_hash(8)|timestamp(8) |
|
if len(keyBytes) >= 56 { // 3 + 5 + 32 + 8 + 8 = 56 |
|
fidpk = &store.IdPkTs{ |
|
Id: make([]byte, 32), |
|
Pub: make([]byte, 8), |
|
Ts: 0, |
|
} |
|
copy(fidpk.Id, keyBytes[8:40]) |
|
copy(fidpk.Pub, keyBytes[40:48]) |
|
// Parse timestamp (big-endian uint64) |
|
var ts int64 |
|
for i := 0; i < 8; i++ { |
|
ts = (ts << 8) | int64(keyBytes[48+i]) |
|
} |
|
fidpk.Ts = ts |
|
fidpk.Ser = ser.Get() |
|
return errors.New("found") // Stop iteration |
|
} |
|
} |
|
|
|
return cursor.Continue() |
|
}) |
|
|
|
if fidpk != nil { |
|
return fidpk, nil |
|
} |
|
if err != nil && err.Error() != "found" { |
|
return nil, err |
|
} |
|
|
|
return nil, errors.New("full id pubkey not found") |
|
} |
|
|
|
// GetFullIdPubkeyBySerials retrieves ID/pubkey/timestamp for multiple serials |
|
func (w *W) GetFullIdPubkeyBySerials(sers []*types.Uint40) (fidpks []*store.IdPkTs, err error) { |
|
fidpks = make([]*store.IdPkTs, 0, len(sers)) |
|
|
|
for _, ser := range sers { |
|
if ser == nil { |
|
continue |
|
} |
|
fidpk, fetchErr := w.GetFullIdPubkeyBySerial(ser) |
|
if fetchErr == nil && fidpk != nil { |
|
fidpks = append(fidpks, fidpk) |
|
} |
|
} |
|
|
|
return fidpks, nil |
|
}
|
|
|