implement wasm/js specific database engine
This commit is contained in:
256
pkg/wasmdb/fetch-event.go
Normal file
256
pkg/wasmdb/fetch-event.go
Normal file
@@ -0,0 +1,256 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user