request.go 11 KB

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