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