cursor.go raw

   1  //go:build js && wasm
   2  // +build js,wasm
   3  
   4  package idb
   5  
   6  import (
   7  	"github.com/aperturerobotics/go-indexeddb/idb/internal/jscache"
   8  	"github.com/hack-pad/safejs"
   9  )
  10  
  11  var (
  12  	jsObjectStore        safejs.Value
  13  	cursorDirectionCache jscache.Strings
  14  )
  15  
  16  func init() {
  17  	var err error
  18  	jsObjectStore, err = safejs.Global().Get("IDBObjectStore")
  19  	if err != nil {
  20  		panic(err)
  21  	}
  22  }
  23  
  24  // CursorDirection is the direction of traversal of the cursor
  25  type CursorDirection int
  26  
  27  const (
  28  	// CursorNext direction causes the cursor to be opened at the start of the source.
  29  	CursorNext CursorDirection = iota
  30  	// CursorNextUnique direction causes the cursor to be opened at the start of the source. For every key with duplicate values, only the first record is yielded.
  31  	CursorNextUnique
  32  	// CursorPrevious direction causes the cursor to be opened at the end of the source.
  33  	CursorPrevious
  34  	// CursorPreviousUnique direction causes the cursor to be opened at the end of the source. For every key with duplicate values, only the first record is yielded.
  35  	CursorPreviousUnique
  36  )
  37  
  38  func parseCursorDirection(s string) CursorDirection {
  39  	switch s {
  40  	case "nextunique":
  41  		return CursorNextUnique
  42  	case "prev":
  43  		return CursorPrevious
  44  	case "prevunique":
  45  		return CursorPreviousUnique
  46  	default:
  47  		return CursorNext
  48  	}
  49  }
  50  
  51  func (d CursorDirection) String() string {
  52  	switch d {
  53  	case CursorNextUnique:
  54  		return "nextunique"
  55  	case CursorPrevious:
  56  		return "prev"
  57  	case CursorPreviousUnique:
  58  		return "prevunique"
  59  	default:
  60  		return "next"
  61  	}
  62  }
  63  
  64  func (d CursorDirection) jsValue() safejs.Value {
  65  	return cursorDirectionCache.Value(d.String())
  66  }
  67  
  68  // Cursor represents a cursor for traversing or iterating over multiple records in a Database
  69  type Cursor struct {
  70  	txn      *Transaction
  71  	jsCursor safejs.Value
  72  	iterated bool // set to true when an iteration method is called, like Continue
  73  }
  74  
  75  func wrapCursor(txn *Transaction, jsCursor safejs.Value) *Cursor {
  76  	return &Cursor{
  77  		txn:      txn,
  78  		jsCursor: jsCursor,
  79  	}
  80  }
  81  
  82  // Source returns the ObjectStore or Index that the cursor is iterating
  83  func (c *Cursor) Source() (objectStore *ObjectStore, index *Index, err error) {
  84  	jsSource, err := c.jsCursor.Get("source")
  85  	if err != nil {
  86  		return
  87  	}
  88  	if isInstance, _ := jsSource.InstanceOf(jsObjectStore); isInstance {
  89  		objectStore = wrapObjectStore(c.txn, jsSource)
  90  	} else if isInstance, _ := jsSource.InstanceOf(jsIDBIndex); isInstance {
  91  		index = wrapIndex(c.txn, jsSource)
  92  	}
  93  	return
  94  }
  95  
  96  // Direction returns the direction of traversal of the cursor
  97  func (c *Cursor) Direction() (CursorDirection, error) {
  98  	direction, err := c.jsCursor.Get("direction")
  99  	if err != nil {
 100  		return 0, err
 101  	}
 102  	directionStr, err := direction.String()
 103  	return parseCursorDirection(directionStr), err
 104  }
 105  
 106  // Key returns the key for the record at the cursor's position. If the cursor is outside its range, this is set to undefined.
 107  func (c *Cursor) Key() (safejs.Value, error) {
 108  	return c.jsCursor.Get("key")
 109  }
 110  
 111  // PrimaryKey returns the cursor's current effective primary key. If the cursor is currently being iterated or has iterated outside its range, this is set to undefined.
 112  func (c *Cursor) PrimaryKey() (safejs.Value, error) {
 113  	return c.jsCursor.Get("primaryKey")
 114  }
 115  
 116  // Request returns the Request that was used to obtain the cursor.
 117  func (c *Cursor) Request() (*Request, error) {
 118  	reqValue, err := c.jsCursor.Get("request")
 119  	if err != nil {
 120  		return nil, err
 121  	}
 122  	return wrapRequest(c.txn, reqValue), nil
 123  }
 124  
 125  // Unwrap returns the underlying JavaScript cursor object.
 126  func (c *Cursor) Unwrap() safejs.Value {
 127  	if c == nil {
 128  		return safejs.Null()
 129  	}
 130  	return c.jsCursor
 131  }
 132  
 133  // Advance sets the number of times a cursor should move its position forward.
 134  func (c *Cursor) Advance(count uint) error {
 135  	c.iterated = true
 136  	_, err := c.jsCursor.Call("advance", count)
 137  	return tryAsDOMException(err)
 138  }
 139  
 140  // Continue advances the cursor to the next position along its direction.
 141  func (c *Cursor) Continue() error {
 142  	c.iterated = true
 143  	_, err := c.jsCursor.Call("continue")
 144  	return tryAsDOMException(err)
 145  }
 146  
 147  // ContinueKey advances the cursor to the next position along its direction.
 148  func (c *Cursor) ContinueKey(key safejs.Value) error {
 149  	c.iterated = true
 150  	_, err := c.jsCursor.Call("continue", key)
 151  	return tryAsDOMException(err)
 152  }
 153  
 154  // ContinuePrimaryKey sets the cursor to the given index key and primary key given as arguments. Returns an error if the source is not an index.
 155  func (c *Cursor) ContinuePrimaryKey(key, primaryKey safejs.Value) error {
 156  	c.iterated = true
 157  	_, err := c.jsCursor.Call("continuePrimaryKey", key, primaryKey)
 158  	return tryAsDOMException(err)
 159  }
 160  
 161  // Delete returns an AckRequest, and, in a separate thread, deletes the record at the cursor's position, without changing the cursor's position. This can be used to delete specific records.
 162  func (c *Cursor) Delete() (*AckRequest, error) {
 163  	reqValue, err := c.jsCursor.Call("delete")
 164  	if err != nil {
 165  		return nil, tryAsDOMException(err)
 166  	}
 167  	req := wrapRequest(c.txn, reqValue)
 168  	return newAckRequest(req), nil
 169  }
 170  
 171  // Update returns a Request, and, in a separate thread, updates the value at the current position of the cursor in the object store. This can be used to update specific records.
 172  func (c *Cursor) Update(value safejs.Value) (*Request, error) {
 173  	reqValue, err := c.jsCursor.Call("update", value)
 174  	if err != nil {
 175  		return nil, tryAsDOMException(err)
 176  	}
 177  	return wrapRequest(c.txn, reqValue), nil
 178  }
 179  
 180  // CursorWithValue represents a cursor for traversing or iterating over multiple records in a database. It is the same as the Cursor, except that it includes the value property.
 181  type CursorWithValue struct {
 182  	*Cursor
 183  }
 184  
 185  func newCursorWithValue(cursor *Cursor) *CursorWithValue {
 186  	return &CursorWithValue{cursor}
 187  }
 188  
 189  func wrapCursorWithValue(txn *Transaction, jsCursor safejs.Value) *CursorWithValue {
 190  	return newCursorWithValue(wrapCursor(txn, jsCursor))
 191  }
 192  
 193  // Value returns the value of the current cursor
 194  func (c *CursorWithValue) Value() (safejs.Value, error) {
 195  	return c.jsCursor.Get("value")
 196  }
 197  
 198  // Unwrap returns the underlying JavaScript cursor object.
 199  func (c *CursorWithValue) Unwrap() safejs.Value {
 200  	return c.jsCursor
 201  }
 202