request.go raw
1 //go:build js && wasm
2 // +build js,wasm
3
4 package idb
5
6 import (
7 "context"
8 "errors"
9 "fmt"
10 "log"
11 "syscall/js"
12
13 "github.com/hack-pad/safejs"
14 )
15
16 var (
17 // ErrCursorStopIter stops iteration when returned from a CursorRequest.Iter() handler
18 ErrCursorStopIter = errors.New("stop cursor iteration")
19 )
20
21 var (
22 jsIDBRequest safejs.Value
23 jsIDBIndex safejs.Value
24 )
25
26 func init() {
27 var err error
28 jsIDBRequest, err = safejs.Global().Get("IDBRequest")
29 if err != nil {
30 panic(err)
31 }
32 jsIDBIndex, err = safejs.Global().Get("IDBIndex")
33 if err != nil {
34 panic(err)
35 }
36 }
37
38 // Request provides access to results of asynchronous requests to databases and database objects
39 // using event listeners. Each reading and writing operation on a database is done using a request.
40 type Request struct {
41 txn *Transaction
42 jsRequest safejs.Value
43 }
44
45 func wrapRequest(txn *Transaction, jsRequest safejs.Value) *Request {
46 if isInstance, err := jsRequest.InstanceOf(jsIDBRequest); !isInstance || err != nil {
47 panic("Invalid JS request type")
48 }
49 if txn == nil {
50 txn = (*Transaction)(nil)
51 }
52 return &Request{
53 txn: txn,
54 jsRequest: jsRequest,
55 }
56 }
57
58 // Source returns the source of the request, such as an Index or an ObjectStore. If no source exists (such as when calling Factory.Open), it returns nil for both.
59 func (r *Request) Source() (objectStore *ObjectStore, index *Index, err error) {
60 jsSource, err := r.jsRequest.Get("source")
61 if err != nil {
62 return
63 }
64 if isInstance, _ := jsSource.InstanceOf(jsObjectStore); isInstance {
65 objectStore = wrapObjectStore(r.txn, jsSource)
66 } else if isInstance, _ := jsSource.InstanceOf(jsIDBIndex); isInstance {
67 index = wrapIndex(r.txn, jsSource)
68 }
69 return
70 }
71
72 // Result returns the result of the request. If the request failed and the result is not available, an error is returned.
73 func (r *Request) Result() (safejs.Value, error) {
74 return r.jsRequest.Get("result")
75 }
76
77 // Err returns an error in the event of an unsuccessful request, indicating what went wrong.
78 func (r *Request) Err() (err error) {
79 jsErr, err := r.jsRequest.Get("error")
80 if err != nil {
81 return err
82 }
83 return domExceptionAsError(jsErr)
84 }
85
86 // AwaitCursor awaits the iterator cursor and returns the value.
87 //
88 // returns nil if there are no more results.
89 func (r *Request) AwaitCursor(ctx context.Context) (*Cursor, error) {
90 result, err := r.Await(ctx)
91 if err != nil {
92 return nil, err
93 }
94 if result.IsNull() {
95 return nil, nil
96 }
97 return wrapCursor(r.txn, result), nil
98 }
99
100 // Await waits for success or failure, then returns the results.
101 func (r *Request) Await(ctx context.Context) (safejs.Value, error) {
102 resultCh := make(chan safejs.Value, 1)
103 errCh := make(chan error, 1)
104
105 ctx, cancel := context.WithCancel(ctx)
106 defer cancel()
107
108 err := r.Listen(ctx, func() {
109 result, err := r.Result()
110 if err != nil {
111 errCh <- err
112 } else {
113 resultCh <- result
114 }
115 }, func() {
116 errCh <- r.Err()
117 })
118 if err != nil {
119 return safejs.Null(), err
120 }
121
122 select {
123 case result := <-resultCh:
124 return result, nil
125 case err := <-errCh:
126 return safejs.Null(), err
127 case <-ctx.Done():
128 return safejs.Null(), ctx.Err()
129 }
130 }
131
132 // ReadyState returns the state of the request. Every request starts in the pending state. The state changes to done when the request completes successfully or when an error occurs.
133 func (r *Request) ReadyState() (string, error) {
134 readyState, err := r.jsRequest.Get("readyState")
135 if err != nil {
136 return "", err
137 }
138 return readyState.String()
139 }
140
141 // Transaction returns the transaction for the request. This can return nil for certain requests, for example those returned from Factory.Open unless an upgrade is needed. (You're just connecting to a database, so there is no transaction to return).
142 func (r *Request) Transaction() (*Transaction, error) {
143 if r.txn == (*Transaction)(nil) {
144 return nil, errNotInTransaction
145 }
146 return r.txn, nil
147 }
148
149 // ListenSuccess invokes the callback when the request succeeds
150 func (r *Request) ListenSuccess(ctx context.Context, success func()) error {
151 return r.Listen(ctx, success, nil)
152 }
153
154 // ListenError invokes the callback when the request fails
155 func (r *Request) ListenError(ctx context.Context, failed func()) error {
156 return r.Listen(ctx, nil, failed)
157 }
158
159 // Listen invokes the success callback when the request succeeds and failed when it fails.
160 func (r *Request) Listen(ctx context.Context, success, failed func()) error {
161 if success != nil {
162 // by default, only listen for 1 value
163 var cancel context.CancelFunc
164 ctx, cancel = context.WithCancel(ctx)
165 originalSuccess := success
166 success = func() {
167 defer cancel()
168 originalSuccess()
169 }
170 }
171 return r.listen(ctx, success, failed)
172 }
173
174 // listen is like Listen, but doesn't cancel the context after success is called
175 func (r *Request) listen(ctx context.Context, success, failed func()) error {
176 ctx, cancel := context.WithCancel(ctx)
177 panicHandler := func(err error) {
178 log.Println("Failed resolving request results:", err)
179 txn, err := r.Transaction()
180 if err == nil {
181 _ = txn.Abort()
182 }
183 cancel()
184 ignorePanic(failed) // helps the listener to cancel the outer context
185 }
186
187 if failed != nil {
188 errFunc, err := safejs.FuncOf(func(safejs.Value, []safejs.Value) interface{} {
189 defer catchHandler(panicHandler)
190 failed()
191 cancel()
192 return nil
193 })
194 if err != nil {
195 panic(err)
196 }
197 _, err = r.jsRequest.Call(addEventListener, "error", errFunc)
198 if err != nil {
199 return tryAsDOMException(err)
200 }
201 go func() {
202 <-ctx.Done()
203 _, err := r.jsRequest.Call(removeEventListener, "error", errFunc)
204 if err != nil {
205 panic(err)
206 }
207 errFunc.Release()
208 }()
209 }
210 if success != nil {
211 successFunc, err := safejs.FuncOf(func(safejs.Value, []safejs.Value) interface{} {
212 defer catchHandler(panicHandler)
213 success()
214 // don't cancel ctx here, need to allow multiple values for cursors
215 return nil
216 })
217 if err != nil {
218 panic(err)
219 }
220 _, err = r.jsRequest.Call(addEventListener, "success", successFunc)
221 if err != nil {
222 return tryAsDOMException(err)
223 }
224 go func() {
225 <-ctx.Done()
226 _, err := r.jsRequest.Call(removeEventListener, "success", successFunc)
227 if err != nil {
228 panic(err)
229 }
230 successFunc.Release()
231 }()
232 }
233 return nil
234 }
235
236 func catchHandler(fn func(err error)) {
237 err := recoveryToError(recover())
238 if err != nil {
239 fn(err)
240 }
241 }
242
243 func recoveryToError(r interface{}) error {
244 if r == nil {
245 return nil
246 }
247 switch val := r.(type) {
248 case error:
249 return val
250 case js.Value:
251 return js.Error{Value: val}
252 default:
253 return fmt.Errorf("%+v", val)
254 }
255 }
256
257 func ignorePanic(fn func()) {
258 defer func() {
259 _ = recover()
260 }()
261 fn()
262 }
263
264 // UintRequest is a Request that retrieves a uint result
265 type UintRequest struct {
266 *Request
267 }
268
269 func newUintRequest(req *Request) *UintRequest {
270 return &UintRequest{req}
271 }
272
273 // Result returns the result of the request. If the request failed and the result is not available, an error is returned.
274 func (u *UintRequest) Result() (uint, error) {
275 result, err := u.Request.Result()
276 if err != nil {
277 return 0, err
278 }
279 value, err := result.Int()
280 return uint(value), err
281 }
282
283 // Await waits for success or failure, then returns the results.
284 func (u *UintRequest) Await(ctx context.Context) (uint, error) {
285 result, err := u.Request.Await(ctx)
286 if err != nil {
287 return 0, err
288 }
289 value, err := result.Int()
290 return uint(value), err
291 }
292
293 // ArrayRequest is a Request that retrieves an array of js.Values
294 type ArrayRequest struct {
295 *Request
296 }
297
298 func newArrayRequest(req *Request) *ArrayRequest {
299 return &ArrayRequest{req}
300 }
301
302 // Result returns the result of the request. If the request failed and the result is not available, an error is returned.
303 func (a *ArrayRequest) Result() ([]safejs.Value, error) {
304 result, err := a.Request.Result()
305 if err != nil {
306 return nil, err
307 }
308 var values []safejs.Value
309 err = iterArray(result, func(i int, value safejs.Value) (bool, error) {
310 values = append(values, value)
311 return true, nil
312 })
313 return values, err
314 }
315
316 // Await waits for success or failure, then returns the results.
317 func (a *ArrayRequest) Await(ctx context.Context) ([]safejs.Value, error) {
318 result, err := a.Request.Await(ctx)
319 if err != nil {
320 return nil, err
321 }
322 var values []safejs.Value
323 err = iterArray(result, func(i int, value safejs.Value) (bool, error) {
324 values = append(values, value)
325 return true, nil
326 })
327 return values, err
328 }
329
330 // AckRequest is a Request that doesn't retrieve a value, only used to detect errors.
331 type AckRequest struct {
332 *Request
333 }
334
335 func newAckRequest(req *Request) *AckRequest {
336 return &AckRequest{req}
337 }
338
339 // Result is a no-op. This kind of request does not retrieve any data in the result.
340 func (a *AckRequest) Result() {} // no-op
341
342 // Await waits for success or failure, then returns the results.
343 func (a *AckRequest) Await(ctx context.Context) error {
344 _, err := a.Request.Await(ctx)
345 return err
346 }
347
348 func cursorIter(ctx context.Context, req *Request, iter func(*Cursor) error) error {
349 for {
350 cursor, err := req.AwaitCursor(ctx)
351 if err != nil {
352 return err
353 }
354 if cursor == nil {
355 return nil
356 }
357 err = iter(cursor)
358 if err != nil {
359 if err == ErrCursorStopIter {
360 return nil
361 }
362 return err
363 }
364 if !cursor.iterated {
365 err := cursor.Continue()
366 if err != nil {
367 return err
368 }
369 }
370 }
371 }
372
373 // CursorRequest is a Request that retrieves a Cursor
374 type CursorRequest struct {
375 *Request
376 }
377
378 func newCursorRequest(req *Request) *CursorRequest {
379 return &CursorRequest{req}
380 }
381
382 // Iter invokes the callback when the request succeeds for each cursor iteration
383 func (c *CursorRequest) Iter(ctx context.Context, iter func(*Cursor) error) error {
384 return cursorIter(ctx, c.Request, iter)
385 }
386
387 // Result returns the result of the request. If the request failed and the result is not available, an error is returned.
388 func (c *CursorRequest) Result() (*Cursor, error) {
389 result, err := c.Request.Result()
390 if err != nil {
391 return nil, err
392 }
393 return wrapCursor(c.txn, result), nil
394 }
395
396 // Await waits for success or failure, then returns the results.
397 func (c *CursorRequest) Await(ctx context.Context) (*Cursor, error) {
398 return c.Request.AwaitCursor(ctx)
399 }
400
401 // Unwrap returns the underlying JavaScript request object.
402 func (c *CursorRequest) Unwrap() safejs.Value {
403 return c.Request.jsRequest
404 }
405
406 // CursorWithValueRequest is a Request that retrieves a CursorWithValue
407 type CursorWithValueRequest struct {
408 *Request
409 }
410
411 func newCursorWithValueRequest(req *Request) *CursorWithValueRequest {
412 return &CursorWithValueRequest{req}
413 }
414
415 // Iter invokes the callback when the request succeeds for each cursor iteration
416 func (c *CursorWithValueRequest) Iter(ctx context.Context, iter func(*CursorWithValue) error) error {
417 return cursorIter(ctx, c.Request, func(cursor *Cursor) error {
418 return iter(newCursorWithValue(cursor))
419 })
420 }
421
422 // Result returns the result of the request. If the request failed and the result is not available, an error is returned.
423 func (c *CursorWithValueRequest) Result() (*CursorWithValue, error) {
424 result, err := c.Request.Result()
425 if err != nil {
426 return nil, err
427 }
428 return wrapCursorWithValue(c.txn, result), nil
429 }
430
431 // Await waits for success or failure, then returns the results.
432 func (c *CursorWithValueRequest) Await(ctx context.Context) (*CursorWithValue, error) {
433 result, err := c.Request.Await(ctx)
434 if err != nil {
435 return nil, err
436 }
437 return wrapCursorWithValue(c.txn, result), nil
438 }
439