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