Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'
This commit is contained in:
749
repos/effect/packages/platform-browser/CHANGELOG.md
Normal file
749
repos/effect/packages/platform-browser/CHANGELOG.md
Normal file
@@ -0,0 +1,749 @@
|
||||
# @effect/platform-browser
|
||||
|
||||
## 4.0.0-beta.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]:
|
||||
- effect@4.0.0-beta.99
|
||||
|
||||
## 4.0.0-beta.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]:
|
||||
- effect@4.0.0-beta.98
|
||||
|
||||
## 4.0.0-beta.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- effect@4.0.0-beta.97
|
||||
|
||||
## 4.0.0-beta.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]:
|
||||
- effect@4.0.0-beta.96
|
||||
|
||||
## 4.0.0-beta.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]:
|
||||
- effect@4.0.0-beta.95
|
||||
|
||||
## 4.0.0-beta.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]:
|
||||
- effect@4.0.0-beta.94
|
||||
|
||||
## 4.0.0-beta.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]:
|
||||
- effect@4.0.0-beta.93
|
||||
|
||||
## 4.0.0-beta.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]:
|
||||
- effect@4.0.0-beta.92
|
||||
|
||||
## 4.0.0-beta.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]:
|
||||
- effect@4.0.0-beta.91
|
||||
|
||||
## 4.0.0-beta.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]:
|
||||
- effect@4.0.0-beta.90
|
||||
|
||||
## 4.0.0-beta.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]:
|
||||
- effect@4.0.0-beta.89
|
||||
|
||||
## 4.0.0-beta.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]:
|
||||
- effect@4.0.0-beta.88
|
||||
|
||||
## 4.0.0-beta.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]:
|
||||
- effect@4.0.0-beta.87
|
||||
|
||||
## 4.0.0-beta.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]:
|
||||
- effect@4.0.0-beta.86
|
||||
|
||||
## 4.0.0-beta.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]:
|
||||
- effect@4.0.0-beta.85
|
||||
|
||||
## 4.0.0-beta.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]:
|
||||
- effect@4.0.0-beta.84
|
||||
|
||||
## 4.0.0-beta.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]:
|
||||
- effect@4.0.0-beta.83
|
||||
|
||||
## 4.0.0-beta.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]:
|
||||
- effect@4.0.0-beta.82
|
||||
|
||||
## 4.0.0-beta.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]:
|
||||
- effect@4.0.0-beta.81
|
||||
|
||||
## 4.0.0-beta.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]:
|
||||
- effect@4.0.0-beta.80
|
||||
|
||||
## 4.0.0-beta.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]:
|
||||
- effect@4.0.0-beta.79
|
||||
|
||||
## 4.0.0-beta.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]:
|
||||
- effect@4.0.0-beta.78
|
||||
|
||||
## 4.0.0-beta.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]:
|
||||
- effect@4.0.0-beta.77
|
||||
|
||||
## 4.0.0-beta.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]:
|
||||
- effect@4.0.0-beta.76
|
||||
|
||||
## 4.0.0-beta.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]:
|
||||
- effect@4.0.0-beta.75
|
||||
|
||||
## 4.0.0-beta.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]:
|
||||
- effect@4.0.0-beta.74
|
||||
|
||||
## 4.0.0-beta.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]:
|
||||
- effect@4.0.0-beta.73
|
||||
|
||||
## 4.0.0-beta.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]:
|
||||
- effect@4.0.0-beta.72
|
||||
|
||||
## 4.0.0-beta.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]:
|
||||
- effect@4.0.0-beta.71
|
||||
|
||||
## 4.0.0-beta.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]:
|
||||
- effect@4.0.0-beta.70
|
||||
|
||||
## 4.0.0-beta.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2241](https://github.com/Effect-TS/effect-smol/pull/2241) [`c5e54d8`](https://github.com/Effect-TS/effect-smol/commit/c5e54d8e4d4ea0f64d9023793b54dfa83d85eac4) Thanks @tim-smart! - Fix IndexedDB bulk writes so `insertAll` and `upsertAll` resume when used inside `withTransaction`.
|
||||
|
||||
- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]:
|
||||
- effect@4.0.0-beta.69
|
||||
|
||||
## 4.0.0-beta.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure.
|
||||
|
||||
- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]:
|
||||
- effect@4.0.0-beta.68
|
||||
|
||||
## 4.0.0-beta.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2196](https://github.com/Effect-TS/effect-smol/pull/2196) [`b7abd0e`](https://github.com/Effect-TS/effect-smol/commit/b7abd0e5c236500a00de3a871c151cc50c3ec40b) Thanks @juemrami! - Adds an IndexedDB backed implementation of `KeyValueStore` as `BrowserKeyValueStore.layerIndexedDb`. This backend allows for non-blocking `KeyValueStore` operations, unlike the existing `Storage` api backed implementations.
|
||||
|
||||
- [#2183](https://github.com/Effect-TS/effect-smol/pull/2183) [`e32343a`](https://github.com/Effect-TS/effect-smol/commit/e32343adf3e449cb9452908655c83757843d1907) Thanks @tim-smart! - use Cause.NoSuchElementError for idb .first queries
|
||||
|
||||
- [#2182](https://github.com/Effect-TS/effect-smol/pull/2182) [`ae40463`](https://github.com/Effect-TS/effect-smol/commit/ae404636ab75fc47d0167c22a20564777c12cf59) Thanks @tim-smart! - cache base idb query builders
|
||||
|
||||
- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]:
|
||||
- effect@4.0.0-beta.67
|
||||
|
||||
## 4.0.0-beta.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]:
|
||||
- effect@4.0.0-beta.66
|
||||
|
||||
## 4.0.0-beta.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]:
|
||||
- effect@4.0.0-beta.65
|
||||
|
||||
## 4.0.0-beta.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]:
|
||||
- effect@4.0.0-beta.64
|
||||
|
||||
## 4.0.0-beta.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]:
|
||||
- effect@4.0.0-beta.63
|
||||
|
||||
## 4.0.0-beta.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]:
|
||||
- effect@4.0.0-beta.62
|
||||
|
||||
## 4.0.0-beta.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]:
|
||||
- effect@4.0.0-beta.61
|
||||
|
||||
## 4.0.0-beta.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2110](https://github.com/Effect-TS/effect-smol/pull/2110) [`f862e40`](https://github.com/Effect-TS/effect-smol/commit/f862e40573b6d1c04942799be5ff6f7dbea22ae9) Thanks @tim-smart! - cleanup IndexedDb prototypes
|
||||
|
||||
- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]:
|
||||
- effect@4.0.0-beta.60
|
||||
|
||||
## 4.0.0-beta.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]:
|
||||
- effect@4.0.0-beta.59
|
||||
|
||||
## 4.0.0-beta.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]:
|
||||
- effect@4.0.0-beta.58
|
||||
|
||||
## 4.0.0-beta.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2086](https://github.com/Effect-TS/effect-smol/pull/2086) [`979b56b`](https://github.com/Effect-TS/effect-smol/commit/979b56b5c45facc488cc920a83339b87fc51334d) Thanks @tim-smart! - fix idb entries transaction
|
||||
|
||||
- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]:
|
||||
- effect@4.0.0-beta.57
|
||||
|
||||
## 4.0.0-beta.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- effect@4.0.0-beta.56
|
||||
|
||||
## 4.0.0-beta.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]:
|
||||
- effect@4.0.0-beta.55
|
||||
|
||||
## 4.0.0-beta.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]:
|
||||
- effect@4.0.0-beta.54
|
||||
|
||||
## 4.0.0-beta.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]:
|
||||
- effect@4.0.0-beta.53
|
||||
|
||||
## 4.0.0-beta.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]:
|
||||
- effect@4.0.0-beta.52
|
||||
|
||||
## 4.0.0-beta.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]:
|
||||
- effect@4.0.0-beta.51
|
||||
|
||||
## 4.0.0-beta.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]:
|
||||
- effect@4.0.0-beta.50
|
||||
|
||||
## 4.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2030](https://github.com/Effect-TS/effect-smol/pull/2030) [`253efe6`](https://github.com/Effect-TS/effect-smol/commit/253efe6f52ecef187d286e6eaba270e0f4d939ed) Thanks @tim-smart! - Add BrowserPersistence.layerIndexedDb for composing Persistence.layer with the IndexedDB backing layer, and export BrowserPersistence from the package barrel.
|
||||
|
||||
- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]:
|
||||
- effect@4.0.0-beta.49
|
||||
|
||||
## 4.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]:
|
||||
- effect@4.0.0-beta.48
|
||||
|
||||
## 4.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]:
|
||||
- effect@4.0.0-beta.47
|
||||
|
||||
## 4.0.0-beta.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]:
|
||||
- effect@4.0.0-beta.46
|
||||
|
||||
## 4.0.0-beta.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]:
|
||||
- effect@4.0.0-beta.45
|
||||
|
||||
## 4.0.0-beta.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1976](https://github.com/Effect-TS/effect-smol/pull/1976) [`7a35a83`](https://github.com/Effect-TS/effect-smol/commit/7a35a83382f9956b1cb92b6a47399adabd79cbf9) Thanks @tim-smart! - improve idb support for compound indexes
|
||||
|
||||
- [#1993](https://github.com/Effect-TS/effect-smol/pull/1993) [`acc7fff`](https://github.com/Effect-TS/effect-smol/commit/acc7fffcaa82b34225f54d324090c2853f9b3547) Thanks @tim-smart! - improve idb transaction api
|
||||
|
||||
- [#1964](https://github.com/Effect-TS/effect-smol/pull/1964) [`1ee4543`](https://github.com/Effect-TS/effect-smol/commit/1ee4543450895f58d2f3ba986dadea6962de4818) Thanks @tim-smart! - allow customizing idb durability
|
||||
|
||||
- [#1937](https://github.com/Effect-TS/effect-smol/pull/1937) [`96cb778`](https://github.com/Effect-TS/effect-smol/commit/96cb77829d6677de788c4227cbee06fc4c707c9c) Thanks @tim-smart! - add .reactive to indexeddb .first queries
|
||||
|
||||
- [#1953](https://github.com/Effect-TS/effect-smol/pull/1953) [`fbbdaec`](https://github.com/Effect-TS/effect-smol/commit/fbbdaec5ed020f7f94a030ede02c121011313037) Thanks @tim-smart! - add defaults to indexeddb reactivity keys
|
||||
|
||||
- [#1977](https://github.com/Effect-TS/effect-smol/pull/1977) [`4f58e30`](https://github.com/Effect-TS/effect-smol/commit/4f58e309d16ded3cac3fb95185a61c06972e2e26) Thanks @tim-smart! - add idb stream and offset
|
||||
|
||||
- [#1989](https://github.com/Effect-TS/effect-smol/pull/1989) [`3cc091d`](https://github.com/Effect-TS/effect-smol/commit/3cc091de7ecfa657b519b0ced316754bcbf53099) Thanks @tim-smart! - use encoded types for idb queries
|
||||
|
||||
- [#1985](https://github.com/Effect-TS/effect-smol/pull/1985) [`f244e71`](https://github.com/Effect-TS/effect-smol/commit/f244e7141770bab8a00f34e48e6923d97ebbe405) Thanks @tim-smart! - add .reverse() to idb select
|
||||
|
||||
- [#1992](https://github.com/Effect-TS/effect-smol/pull/1992) [`8c74d03`](https://github.com/Effect-TS/effect-smol/commit/8c74d0353fff9099075ba28bb50166a23f4062dc) Thanks @tim-smart! - Add rebuild api to idb databases
|
||||
|
||||
- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename the `ServiceMap` module to `Context` across exports, docs, and tests.
|
||||
|
||||
- [#1924](https://github.com/Effect-TS/effect-smol/pull/1924) [`716fe24`](https://github.com/Effect-TS/effect-smol/commit/716fe24886292aa6af2ad1f3fd7aa1b2f0a10c7f) Thanks @tim-smart! - allow Model.Class for indexeddb schemas
|
||||
|
||||
- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]:
|
||||
- effect@4.0.0-beta.44
|
||||
|
||||
## 4.0.0-beta.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1240](https://github.com/Effect-TS/effect-smol/pull/1240) [`583ea00`](https://github.com/Effect-TS/effect-smol/commit/583ea002fdccc58fd2110a36c6d103e63152dcb3) Thanks @SandroMaglione! - add IndexedDb modules
|
||||
|
||||
- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]:
|
||||
- effect@4.0.0-beta.43
|
||||
|
||||
## 4.0.0-beta.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]:
|
||||
- effect@4.0.0-beta.42
|
||||
|
||||
## 4.0.0-beta.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]:
|
||||
- effect@4.0.0-beta.41
|
||||
|
||||
## 4.0.0-beta.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]:
|
||||
- effect@4.0.0-beta.40
|
||||
|
||||
## 4.0.0-beta.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]:
|
||||
- effect@4.0.0-beta.39
|
||||
|
||||
## 4.0.0-beta.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]:
|
||||
- effect@4.0.0-beta.38
|
||||
|
||||
## 4.0.0-beta.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]:
|
||||
- effect@4.0.0-beta.37
|
||||
|
||||
## 4.0.0-beta.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]:
|
||||
- effect@4.0.0-beta.36
|
||||
|
||||
## 4.0.0-beta.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]:
|
||||
- effect@4.0.0-beta.35
|
||||
|
||||
## 4.0.0-beta.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]:
|
||||
- effect@4.0.0-beta.34
|
||||
|
||||
## 4.0.0-beta.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]:
|
||||
- effect@4.0.0-beta.33
|
||||
|
||||
## 4.0.0-beta.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]:
|
||||
- effect@4.0.0-beta.32
|
||||
|
||||
## 4.0.0-beta.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1710](https://github.com/Effect-TS/effect-smol/pull/1710) [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423) Thanks @gcanti! - Schema: `toCodecJson` now returns `Codec<T, Json, RD, RE>` instead of `Codec<T, unknown, RD, RE>`.
|
||||
|
||||
Http: the `json` property on `HttpIncomingMessage`, `HttpClientResponse`, `HttpServerRequest`, and `HttpServerResponse` now returns `Effect<Schema.Json, E>` instead of `Effect<unknown, E>`.
|
||||
|
||||
- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]:
|
||||
- effect@4.0.0-beta.31
|
||||
|
||||
## 4.0.0-beta.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]:
|
||||
- effect@4.0.0-beta.30
|
||||
|
||||
## 4.0.0-beta.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]:
|
||||
- effect@4.0.0-beta.29
|
||||
|
||||
## 4.0.0-beta.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]:
|
||||
- effect@4.0.0-beta.28
|
||||
|
||||
## 4.0.0-beta.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]:
|
||||
- effect@4.0.0-beta.27
|
||||
|
||||
## 4.0.0-beta.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]:
|
||||
- effect@4.0.0-beta.26
|
||||
|
||||
## 4.0.0-beta.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]:
|
||||
- effect@4.0.0-beta.25
|
||||
|
||||
## 4.0.0-beta.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]:
|
||||
- effect@4.0.0-beta.24
|
||||
|
||||
## 4.0.0-beta.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]:
|
||||
- effect@4.0.0-beta.23
|
||||
|
||||
## 4.0.0-beta.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]:
|
||||
- effect@4.0.0-beta.22
|
||||
|
||||
## 4.0.0-beta.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]:
|
||||
- effect@4.0.0-beta.21
|
||||
|
||||
## 4.0.0-beta.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]:
|
||||
- effect@4.0.0-beta.20
|
||||
|
||||
## 4.0.0-beta.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- effect@4.0.0-beta.19
|
||||
|
||||
## 4.0.0-beta.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]:
|
||||
- effect@4.0.0-beta.18
|
||||
|
||||
## 4.0.0-beta.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]:
|
||||
- effect@4.0.0-beta.17
|
||||
|
||||
## 4.0.0-beta.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]:
|
||||
- effect@4.0.0-beta.16
|
||||
|
||||
## 4.0.0-beta.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]:
|
||||
- effect@4.0.0-beta.15
|
||||
|
||||
## 4.0.0-beta.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]:
|
||||
- effect@4.0.0-beta.14
|
||||
|
||||
## 4.0.0-beta.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]:
|
||||
- effect@4.0.0-beta.13
|
||||
|
||||
## 4.0.0-beta.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]:
|
||||
- effect@4.0.0-beta.12
|
||||
|
||||
## 4.0.0-beta.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]:
|
||||
- effect@4.0.0-beta.11
|
||||
|
||||
## 4.0.0-beta.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]:
|
||||
- effect@4.0.0-beta.10
|
||||
|
||||
## 4.0.0-beta.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]:
|
||||
- effect@4.0.0-beta.9
|
||||
|
||||
## 4.0.0-beta.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]:
|
||||
- effect@4.0.0-beta.8
|
||||
|
||||
## 4.0.0-beta.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]:
|
||||
- effect@4.0.0-beta.7
|
||||
|
||||
## 4.0.0-beta.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]:
|
||||
- effect@4.0.0-beta.6
|
||||
|
||||
## 4.0.0-beta.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]:
|
||||
- effect@4.0.0-beta.5
|
||||
|
||||
## 4.0.0-beta.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]:
|
||||
- effect@4.0.0-beta.4
|
||||
|
||||
## 4.0.0-beta.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]:
|
||||
- effect@4.0.0-beta.3
|
||||
|
||||
## 4.0.0-beta.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]:
|
||||
- effect@4.0.0-beta.2
|
||||
|
||||
## 4.0.0-beta.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]:
|
||||
- effect@4.0.0-beta.1
|
||||
|
||||
## 4.0.0-beta.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]:
|
||||
- effect@4.0.0-beta.0
|
||||
21
repos/effect/packages/platform-browser/LICENSE
Normal file
21
repos/effect/packages/platform-browser/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Effectful Technologies Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1
repos/effect/packages/platform-browser/README.md
Normal file
1
repos/effect/packages/platform-browser/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# `@effect/platform-browser`
|
||||
20
repos/effect/packages/platform-browser/docgen.json
Normal file
20
repos/effect/packages/platform-browser/docgen.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "../../node_modules/@effect/docgen/schema.json",
|
||||
"srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/platform-browser/src/",
|
||||
"exclude": ["src/internal/**/*.ts"],
|
||||
"examplesCompilerOptions": {
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"module": "ES2022",
|
||||
"target": "ES2022",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"types": ["node"],
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"plugins": [
|
||||
{ "name": "@effect/language-service", "includeSuggestionsInTsc": false }
|
||||
]
|
||||
}
|
||||
}
|
||||
73
repos/effect/packages/platform-browser/package.json
Normal file
73
repos/effect/packages/platform-browser/package.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "@effect/platform-browser",
|
||||
"type": "module",
|
||||
"version": "4.0.0-beta.99",
|
||||
"license": "MIT",
|
||||
"description": "Platform specific implementations for the browser",
|
||||
"homepage": "https://effect.website",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Effect-TS/effect.git",
|
||||
"directory": "packages/platform-browser"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Effect-TS/effect/issues"
|
||||
},
|
||||
"tags": [
|
||||
"browser",
|
||||
"typescript",
|
||||
"algebraic-data-types",
|
||||
"functional-programming"
|
||||
],
|
||||
"keywords": [
|
||||
"browser",
|
||||
"typescript",
|
||||
"algebraic-data-types",
|
||||
"functional-programming"
|
||||
],
|
||||
"sideEffects": [],
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*.ts",
|
||||
"./internal/*": null,
|
||||
"./*/index": null
|
||||
},
|
||||
"files": [
|
||||
"src/**/*.ts",
|
||||
"dist/**/*.js",
|
||||
"dist/**/*.js.map",
|
||||
"dist/**/*.d.ts",
|
||||
"dist/**/*.d.ts.map"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"provenance": true,
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": "./dist/index.js",
|
||||
"./*": "./dist/*.js",
|
||||
"./internal/*": null,
|
||||
"./*/index": null
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"codegen": "effect-utils codegen",
|
||||
"build": "tsc -b tsconfig.json && pnpm babel",
|
||||
"babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps",
|
||||
"check": "tsc -b tsconfig.json",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest --coverage"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"effect": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"effect": "workspace:^",
|
||||
"fake-indexeddb": "^6.2.5",
|
||||
"mock-xmlhttprequest": "^8.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"multipasta": "^0.2.8"
|
||||
}
|
||||
}
|
||||
97
repos/effect/packages/platform-browser/src/BrowserCrypto.ts
Normal file
97
repos/effect/packages/platform-browser/src/BrowserCrypto.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Browser-backed implementation of Effect's Crypto service.
|
||||
*
|
||||
* This module provides a `Crypto.Crypto` layer backed by the Web Crypto API.
|
||||
* The {@link WebCrypto} context reference defaults to `globalThis.crypto`, so
|
||||
* browser programs can use the standard implementation while tests or embedded
|
||||
* runtimes can provide their own `Crypto` object.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
import * as Context from "effect/Context"
|
||||
import * as EffectCrypto from "effect/Crypto"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as PlatformError from "effect/PlatformError"
|
||||
|
||||
/**
|
||||
* Provides Browser Web Crypto APIs used by the Crypto service implementation.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use to override the browser `Crypto` object used by the platform crypto
|
||||
* layer.
|
||||
*
|
||||
* @category references
|
||||
* @since 1.0.0
|
||||
*/
|
||||
export const WebCrypto = Context.Reference<Crypto>("@effect/platform-browser/Crypto/WebCrypto", {
|
||||
defaultValue: () => globalThis.crypto
|
||||
})
|
||||
|
||||
/**
|
||||
* Layer that directly interfaces with the Web Crypto API.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use to provide cryptographic randomness, UUID generation, and digest
|
||||
* operations in browser runtimes backed by `globalThis.crypto`.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* Random bytes are produced with `crypto.getRandomValues`. Digests are computed
|
||||
* with `crypto.subtle.digest` and returned as `Uint8Array` values.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* The layer dies if the Web Crypto object is unavailable. Digest operations
|
||||
* fail with `PlatformError` when `crypto.subtle.digest` is unavailable or the
|
||||
* browser rejects the digest request.
|
||||
*
|
||||
* @category layers
|
||||
* @since 1.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<EffectCrypto.Crypto> = Layer.effect(
|
||||
EffectCrypto.Crypto,
|
||||
Effect.gen(function*() {
|
||||
const crypto = yield* WebCrypto
|
||||
if (!crypto) {
|
||||
return yield* Effect.die(new Error("Web Crypto API is not available"))
|
||||
}
|
||||
const randomBytes = (size: number): Uint8Array => {
|
||||
const bytes = new Uint8Array(size)
|
||||
crypto.getRandomValues(bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) => {
|
||||
if (typeof crypto.subtle.digest !== "function") {
|
||||
return Effect.fail(PlatformError.systemError({
|
||||
module: "Crypto",
|
||||
method: "digest",
|
||||
_tag: "Unknown",
|
||||
description: "crypto.subtle.digest is not available"
|
||||
}))
|
||||
}
|
||||
return Effect.map(
|
||||
Effect.tryPromise({
|
||||
try: () => crypto.subtle.digest(algorithm, new Uint8Array(data)),
|
||||
catch: (cause) =>
|
||||
PlatformError.systemError({
|
||||
module: "Crypto",
|
||||
method: "digest",
|
||||
_tag: "Unknown",
|
||||
description: "Could not compute digest",
|
||||
cause
|
||||
})
|
||||
}),
|
||||
(buffer) => new Uint8Array(buffer)
|
||||
)
|
||||
}
|
||||
|
||||
return EffectCrypto.make({
|
||||
randomBytes,
|
||||
digest
|
||||
})
|
||||
})
|
||||
)
|
||||
430
repos/effect/packages/platform-browser/src/BrowserHttpClient.ts
Normal file
430
repos/effect/packages/platform-browser/src/BrowserHttpClient.ts
Normal file
@@ -0,0 +1,430 @@
|
||||
/**
|
||||
* Browser implementations of the Effect `HttpClient`.
|
||||
*
|
||||
* This module exposes HTTP client layers for code that runs in a browser. It
|
||||
* re-exports the fetch-based `Fetch`, `RequestInit`, and `layerFetch` APIs for
|
||||
* the common case where requests use the platform `fetch` implementation. It
|
||||
* also provides an `XMLHttpRequest`-backed client, including controls for the
|
||||
* XHR response type, an overridable `XMLHttpRequest` constructor service, and
|
||||
* the `layerXMLHttpRequest` layer.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Cause from "effect/Cause"
|
||||
import * as Context from "effect/Context"
|
||||
import * as Effect from "effect/Effect"
|
||||
import type { LazyArg } from "effect/Function"
|
||||
import * as Inspectable from "effect/Inspectable"
|
||||
import type * as Layer from "effect/Layer"
|
||||
import * as Option from "effect/Option"
|
||||
import { type Pipeable, pipeArguments } from "effect/Pipeable"
|
||||
import * as Queue from "effect/Queue"
|
||||
import type * as Schema from "effect/Schema"
|
||||
import * as Stream from "effect/Stream"
|
||||
import * as Cookies from "effect/unstable/http/Cookies"
|
||||
import * as Headers from "effect/unstable/http/Headers"
|
||||
import * as HttpClient from "effect/unstable/http/HttpClient"
|
||||
import * as HttpClientError from "effect/unstable/http/HttpClientError"
|
||||
import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
|
||||
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
|
||||
import * as HttpIncomingMessage from "effect/unstable/http/HttpIncomingMessage"
|
||||
import * as UrlParams from "effect/unstable/http/UrlParams"
|
||||
import * as HeaderParser from "multipasta/HeadersParser"
|
||||
|
||||
// =============================================================================
|
||||
// Fetch
|
||||
// =============================================================================
|
||||
|
||||
export {
|
||||
/**
|
||||
* Context reference for the `fetch` implementation used by the fetch-based HTTP client.
|
||||
*
|
||||
* @category fetch
|
||||
* @since 4.0.0
|
||||
*/
|
||||
Fetch,
|
||||
/**
|
||||
* Layer that provides an `HttpClient` implementation backed by the configured `Fetch` function.
|
||||
*
|
||||
* @category fetch
|
||||
* @since 4.0.0
|
||||
*/
|
||||
layer as layerFetch,
|
||||
/**
|
||||
* Service that contains default fetch options for the browser fetch client.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use to provide default credentials, cache, redirect, integrity, or other
|
||||
* fetch options for browser HTTP requests.
|
||||
*
|
||||
* @category fetch
|
||||
* @since 4.0.0
|
||||
*/
|
||||
RequestInit
|
||||
} from "effect/unstable/http/FetchHttpClient"
|
||||
|
||||
// =============================================================================
|
||||
// XML Http Request
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Allowed response body modes for the browser XHR HTTP client.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type XHRResponseType = "arraybuffer" | "text"
|
||||
|
||||
/**
|
||||
* Context reference for the `XMLHttpRequest.responseType` used by the browser XHR HTTP client, defaulting to `"text"`.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need XHR-backed HTTP requests to receive response bodies as text
|
||||
* or raw `ArrayBuffer` values.
|
||||
*
|
||||
* @see {@link XHRResponseType} for the allowed response body modes
|
||||
* @see {@link withXHRArrayBuffer} for scoping XHR response handling to `ArrayBuffer`
|
||||
*
|
||||
* @category references
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const CurrentXHRResponseType: Context.Reference<XHRResponseType> = Context.Reference(
|
||||
"@effect/platform-browser/BrowserHttpClient/CurrentXHRResponseType",
|
||||
{ defaultValue: (): XHRResponseType => "text" }
|
||||
)
|
||||
|
||||
/**
|
||||
* Runs an effect with `CurrentXHRResponseType` set to `"arraybuffer"` so the XHR HTTP client receives response bodies as `ArrayBuffer` values.
|
||||
*
|
||||
* @category references
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const withXHRArrayBuffer = <A, E, R>(
|
||||
self: Effect.Effect<A, E, R>
|
||||
): Effect.Effect<A, E, R> =>
|
||||
Effect.provideService(
|
||||
self,
|
||||
CurrentXHRResponseType,
|
||||
"arraybuffer"
|
||||
)
|
||||
|
||||
/**
|
||||
* Service tag for the `XMLHttpRequest` constructor used by the browser XHR HTTP client.
|
||||
*
|
||||
* @category services
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class XMLHttpRequest extends Context.Service<
|
||||
XMLHttpRequest,
|
||||
LazyArg<globalThis.XMLHttpRequest>
|
||||
>()("@effect/platform-browser/BrowserHttpClient/XMLHttpRequest") {}
|
||||
|
||||
const makeXhrRequest = () => new globalThis.XMLHttpRequest()
|
||||
|
||||
const makeXmlHttpRequest = HttpClient.make(
|
||||
(request, url, signal, fiber) =>
|
||||
Effect.suspend(() => {
|
||||
const xhr = Context.getOrElse(
|
||||
fiber.context,
|
||||
XMLHttpRequest,
|
||||
() => makeXhrRequest
|
||||
)()
|
||||
signal.addEventListener("abort", () => {
|
||||
xhr.abort()
|
||||
xhr.onreadystatechange = null
|
||||
}, { once: true })
|
||||
xhr.open(request.method, url.toString(), true)
|
||||
xhr.responseType = fiber.getRef(CurrentXHRResponseType)
|
||||
Object.entries(request.headers).forEach(([k, v]) => {
|
||||
xhr.setRequestHeader(k, v)
|
||||
})
|
||||
return Effect.andThen(
|
||||
sendBody(xhr, request),
|
||||
Effect.callback<ClientResponseImpl, HttpClientError.HttpClientError>((resume) => {
|
||||
let sent = false
|
||||
const onChange = () => {
|
||||
if (!sent && xhr.readyState >= 2) {
|
||||
sent = true
|
||||
resume(Effect.succeed(new ClientResponseImpl(request, xhr)))
|
||||
}
|
||||
}
|
||||
xhr.onreadystatechange = onChange
|
||||
xhr.onerror = (_event) => {
|
||||
resume(Effect.fail(
|
||||
new HttpClientError.HttpClientError({
|
||||
reason: new HttpClientError.TransportError({
|
||||
request,
|
||||
cause: xhr.statusText
|
||||
})
|
||||
})
|
||||
))
|
||||
}
|
||||
onChange()
|
||||
return Effect.void
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
const sendBody = (
|
||||
xhr: globalThis.XMLHttpRequest,
|
||||
request: HttpClientRequest.HttpClientRequest
|
||||
): Effect.Effect<void, HttpClientError.HttpClientError> => {
|
||||
const body = request.body
|
||||
switch (body._tag) {
|
||||
case "Empty":
|
||||
return Effect.sync(() => xhr.send())
|
||||
case "Raw":
|
||||
return Effect.sync(() => xhr.send(body.body as any))
|
||||
case "Uint8Array":
|
||||
return Effect.sync(() => xhr.send(body.body as any))
|
||||
case "FormData":
|
||||
return Effect.sync(() => xhr.send(body.formData))
|
||||
case "Stream":
|
||||
return Effect.matchEffect(
|
||||
Stream.runFold(body.stream, () => new Uint8Array(0), (acc, chunk) => {
|
||||
const next = new Uint8Array(acc.length + chunk.length)
|
||||
next.set(acc, 0)
|
||||
next.set(chunk, acc.length)
|
||||
return next
|
||||
}),
|
||||
{
|
||||
onFailure: (cause) =>
|
||||
Effect.fail(
|
||||
new HttpClientError.HttpClientError({
|
||||
reason: new HttpClientError.EncodeError({
|
||||
request,
|
||||
cause
|
||||
})
|
||||
})
|
||||
),
|
||||
onSuccess: (body) => Effect.sync(() => xhr.send(body))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
abstract class IncomingMessageImpl<E> extends Inspectable.Class implements HttpIncomingMessage.HttpIncomingMessage<E> {
|
||||
readonly [HttpIncomingMessage.TypeId]: typeof HttpIncomingMessage.TypeId
|
||||
readonly source: globalThis.XMLHttpRequest
|
||||
readonly onError: (error: unknown) => E
|
||||
|
||||
constructor(source: globalThis.XMLHttpRequest, onError: (error: unknown) => E) {
|
||||
super()
|
||||
this[HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId
|
||||
this.source = source
|
||||
this.onError = onError
|
||||
this._rawHeaderString = source.getAllResponseHeaders()
|
||||
}
|
||||
|
||||
private _rawHeaderString: string
|
||||
private _rawHeaders: Record<string, string | Array<string>> | undefined
|
||||
private _headers: Headers.Headers | undefined
|
||||
get headers() {
|
||||
if (this._headers) {
|
||||
return this._headers
|
||||
}
|
||||
if (this._rawHeaderString === "") {
|
||||
return this._headers = Headers.empty
|
||||
}
|
||||
const parser = HeaderParser.make()
|
||||
const result = parser(encoder.encode(this._rawHeaderString + "\r\n"), 0)
|
||||
this._rawHeaders = result._tag === "Headers" ? result.headers : undefined
|
||||
const parsed = result._tag === "Headers" ? Headers.fromInput(result.headers) : Headers.empty
|
||||
return this._headers = parsed
|
||||
}
|
||||
|
||||
cachedCookies: Cookies.Cookies | undefined
|
||||
get cookies() {
|
||||
if (this.cachedCookies) {
|
||||
return this.cachedCookies
|
||||
}
|
||||
if (this._rawHeaders === undefined) {
|
||||
return Cookies.empty
|
||||
} else if (this._rawHeaders["set-cookie"] === undefined) {
|
||||
return this.cachedCookies = Cookies.empty
|
||||
}
|
||||
return this.cachedCookies = Cookies.fromSetCookie(this._rawHeaders["set-cookie"])
|
||||
}
|
||||
|
||||
get remoteAddress() {
|
||||
return Option.none()
|
||||
}
|
||||
|
||||
_textEffect: Effect.Effect<string, E> | undefined
|
||||
get text(): Effect.Effect<string, E> {
|
||||
if (this._textEffect) {
|
||||
return this._textEffect
|
||||
}
|
||||
return this._textEffect = Effect.callback<string, E>((resume) => {
|
||||
if (this.source.readyState === 4) {
|
||||
resume(Effect.succeed(this.source.responseText))
|
||||
return
|
||||
}
|
||||
|
||||
const onReadyStateChange = () => {
|
||||
if (this.source.readyState === 4) {
|
||||
resume(Effect.succeed(this.source.responseText))
|
||||
}
|
||||
}
|
||||
const onError = () => {
|
||||
resume(Effect.fail(this.onError(this.source.statusText)))
|
||||
}
|
||||
this.source.addEventListener("readystatechange", onReadyStateChange)
|
||||
this.source.addEventListener("error", onError)
|
||||
return Effect.sync(() => {
|
||||
this.source.removeEventListener("readystatechange", onReadyStateChange)
|
||||
this.source.removeEventListener("error", onError)
|
||||
})
|
||||
}).pipe(
|
||||
Effect.cached,
|
||||
Effect.runSync
|
||||
)
|
||||
}
|
||||
|
||||
get json(): Effect.Effect<Schema.Json, E> {
|
||||
return Effect.flatMap(this.text, (text) =>
|
||||
Effect.try({
|
||||
try: () => text === "" ? null : JSON.parse(text),
|
||||
catch: this.onError
|
||||
}))
|
||||
}
|
||||
|
||||
get urlParamsBody(): Effect.Effect<UrlParams.UrlParams, E> {
|
||||
return Effect.flatMap(this.text, (text) =>
|
||||
Effect.try({
|
||||
try: () => UrlParams.fromInput(new URLSearchParams(text)),
|
||||
catch: this.onError
|
||||
}))
|
||||
}
|
||||
|
||||
get stream(): Stream.Stream<Uint8Array, E> {
|
||||
return Stream.callback<Uint8Array, E>((queue) => {
|
||||
let offset = 0
|
||||
const onReadyStateChange = () => {
|
||||
if (this.source.readyState === 3) {
|
||||
const encoded = encoder.encode(this.source.responseText.slice(offset))
|
||||
Queue.offerUnsafe(queue, encoded)
|
||||
offset = this.source.responseText.length
|
||||
} else if (this.source.readyState === 4) {
|
||||
const encoded = encoder.encode(this.source.responseText.slice(offset))
|
||||
if (offset < this.source.responseText.length) {
|
||||
Queue.offerUnsafe(queue, encoded)
|
||||
}
|
||||
Queue.endUnsafe(queue)
|
||||
}
|
||||
}
|
||||
const onError = () => {
|
||||
Queue.failCauseUnsafe(queue, Cause.fail(this.onError(this.source.statusText)))
|
||||
}
|
||||
this.source.addEventListener("readystatechange", onReadyStateChange)
|
||||
this.source.addEventListener("error", onError)
|
||||
onReadyStateChange()
|
||||
return Effect.sync(() => {
|
||||
this.source.removeEventListener("readystatechange", onReadyStateChange)
|
||||
this.source.removeEventListener("error", onError)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_arrayBufferEffect: Effect.Effect<ArrayBuffer, E> | undefined
|
||||
get arrayBuffer(): Effect.Effect<ArrayBuffer, E> {
|
||||
if (this._arrayBufferEffect) {
|
||||
return this._arrayBufferEffect
|
||||
}
|
||||
return this._arrayBufferEffect = Effect.callback<ArrayBuffer, E>((resume) => {
|
||||
if (this.source.readyState === 4) {
|
||||
resume(Effect.succeed(this.source.response))
|
||||
return
|
||||
}
|
||||
|
||||
const onReadyStateChange = () => {
|
||||
if (this.source.readyState === 4) {
|
||||
resume(Effect.succeed(this.source.response))
|
||||
}
|
||||
}
|
||||
const onError = () => {
|
||||
resume(Effect.fail(this.onError(this.source.statusText)))
|
||||
}
|
||||
this.source.addEventListener("readystatechange", onReadyStateChange)
|
||||
this.source.addEventListener("error", onError)
|
||||
return Effect.sync(() => {
|
||||
this.source.removeEventListener("readystatechange", onReadyStateChange)
|
||||
this.source.removeEventListener("error", onError)
|
||||
})
|
||||
}).pipe(
|
||||
Effect.map((response) => {
|
||||
if (typeof response === "string") {
|
||||
const arr = encoder.encode(response)
|
||||
return arr.byteLength !== arr.buffer.byteLength
|
||||
? arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength)
|
||||
: arr.buffer
|
||||
}
|
||||
return response
|
||||
}),
|
||||
Effect.cached,
|
||||
Effect.runSync
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ClientResponseImpl extends IncomingMessageImpl<HttpClientError.HttpClientError>
|
||||
implements HttpClientResponse.HttpClientResponse, Pipeable
|
||||
{
|
||||
readonly [HttpClientResponse.TypeId]: typeof HttpClientResponse.TypeId
|
||||
readonly request: HttpClientRequest.HttpClientRequest
|
||||
|
||||
constructor(
|
||||
request: HttpClientRequest.HttpClientRequest,
|
||||
source: globalThis.XMLHttpRequest
|
||||
) {
|
||||
super(source, (cause) =>
|
||||
new HttpClientError.HttpClientError({
|
||||
reason: new HttpClientError.DecodeError({
|
||||
request,
|
||||
response: this,
|
||||
cause
|
||||
})
|
||||
}))
|
||||
this.request = request
|
||||
this[HttpClientResponse.TypeId] = HttpClientResponse.TypeId
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.source.status
|
||||
}
|
||||
|
||||
get formData(): Effect.Effect<FormData, HttpClientError.HttpClientError> {
|
||||
return Effect.die("Not implemented")
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
return `ClientResponse(${this.status})`
|
||||
}
|
||||
|
||||
toJSON(): unknown {
|
||||
return HttpIncomingMessage.inspect(this, {
|
||||
_id: "@effect/platform/HttpClientResponse",
|
||||
request: this.request.toJSON(),
|
||||
status: this.status
|
||||
})
|
||||
}
|
||||
|
||||
pipe() {
|
||||
return pipeArguments(this, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layer that provides an `HttpClient` implementation backed by the browser `XMLHttpRequest` API.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerXMLHttpRequest: Layer.Layer<HttpClient.HttpClient> = HttpClient.layerMergedContext(
|
||||
Effect.succeed(makeXmlHttpRequest)
|
||||
)
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* Browser-backed `KeyValueStore` layers for client-side Effect programs.
|
||||
*
|
||||
* This module provides browser implementations of the unstable persistence
|
||||
* `KeyValueStore` service. Use {@link layerLocalStorage} for small
|
||||
* origin-scoped values that should survive reloads and browser restarts, use
|
||||
* {@link layerSessionStorage} for tab / page-session state, and use
|
||||
* {@link layerIndexedDb} when the store should be asynchronous and backed by
|
||||
* IndexedDB. The IndexedDB layer requires the browser `IndexedDb` service and
|
||||
* accepts an optional database name.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore"
|
||||
import { IndexedDb } from "./IndexedDb.ts"
|
||||
|
||||
/**
|
||||
* Creates a `KeyValueStore` layer that uses the browser's `localStorage` API and stores values between browser sessions.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerLocalStorage: Layer.Layer<KeyValueStore.KeyValueStore> = KeyValueStore.layerStorage(() =>
|
||||
globalThis.localStorage
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a `KeyValueStore` layer that uses the browser's `sessionStorage` API and stores values only for the current session.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerSessionStorage: Layer.Layer<KeyValueStore.KeyValueStore> = KeyValueStore.layerStorage(() =>
|
||||
globalThis.sessionStorage
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a `KeyValueStore` layer backed by IndexedDB.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need persistent asynchronous IndexedDB storage for a browser
|
||||
* `KeyValueStore` instead of the synchronous Web Storage APIs.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* The database name defaults to `"effect_key_value_store"`. The layer requires
|
||||
* the `IndexedDb` service and stores string and `Uint8Array` values in the same
|
||||
* backing object store.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* IndexedDB may be unavailable or blocked by browser settings, private browsing,
|
||||
* quota limits, or restricted contexts. The string and `Uint8Array` accessors do
|
||||
* not coerce values stored with the other representation.
|
||||
*
|
||||
* @see {@link layerLocalStorage} for synchronous persistent Web Storage
|
||||
* @see {@link layerSessionStorage} for synchronous tab-session Web Storage
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerIndexedDb = (options?: {
|
||||
readonly database?: string | undefined
|
||||
}): Layer.Layer<KeyValueStore.KeyValueStore, never, IndexedDb> =>
|
||||
Layer.effect(KeyValueStore.KeyValueStore)(
|
||||
Effect.gen(function*() {
|
||||
const db = yield* Effect.acquireRelease(
|
||||
openDatabase(options?.database ?? "effect_key_value_store"),
|
||||
(db) => Effect.sync(() => db.close())
|
||||
).pipe(Effect.orDie)
|
||||
|
||||
return KeyValueStore.make({
|
||||
clear: Effect.suspend(() => {
|
||||
const store = getKvsEntriesStore(db, "readwrite")
|
||||
return idbRequest({ method: "clear", message: "Failed to clear backing store" }, () => store.clear())
|
||||
}),
|
||||
get: (key: string) =>
|
||||
Effect.map(
|
||||
Effect.suspend(() => {
|
||||
const store = getKvsEntriesStore(db, "readonly")
|
||||
return idbRequest<{ key: string; value: string } | undefined>({
|
||||
method: "get",
|
||||
message: "Failed to get value from backing store",
|
||||
key
|
||||
}, () => store.get(key))
|
||||
}),
|
||||
(found) => typeof found?.value === "string" ? found.value : undefined
|
||||
),
|
||||
getUint8Array: (key: string) =>
|
||||
Effect.map(
|
||||
Effect.suspend(() => {
|
||||
const store = getKvsEntriesStore(db, "readonly")
|
||||
return idbRequest<{ key: string; value: Uint8Array } | undefined>({
|
||||
method: "getUint8Array",
|
||||
message: "Failed to get value from backing store",
|
||||
key
|
||||
}, () => store.get(key))
|
||||
}),
|
||||
(found) => found?.value && found.value instanceof Uint8Array ? found.value : undefined
|
||||
),
|
||||
set: (key: string, value: string | Uint8Array) =>
|
||||
Effect.asVoid(Effect.suspend(() => {
|
||||
const store = getKvsEntriesStore(db, "readwrite")
|
||||
return idbRequest(
|
||||
{ method: "set", message: "Failed to set value in backing store", key },
|
||||
() => store.put({ key, value })
|
||||
)
|
||||
})),
|
||||
size: Effect.suspend(() => {
|
||||
const store = getKvsEntriesStore(db, "readonly")
|
||||
return idbRequest<number>(
|
||||
{ method: "size", message: "Failed to get backing store size" },
|
||||
() => store.count()
|
||||
)
|
||||
}),
|
||||
remove: (key: string) =>
|
||||
Effect.asVoid(Effect.suspend(() => {
|
||||
const store = getKvsEntriesStore(db, "readwrite")
|
||||
return idbRequest(
|
||||
{ method: "remove", message: "Failed to remove value from backing store", key },
|
||||
() => store.delete(key)
|
||||
)
|
||||
}))
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const databaseVersion = 1
|
||||
const entriesStoreName = "entries"
|
||||
const openDatabase = Effect.fnUntraced(function*(database: string) {
|
||||
const idb = (yield* IndexedDb).indexedDB
|
||||
const openRequest = yield* Effect.try({
|
||||
try: () => idb.open(database, databaseVersion),
|
||||
catch: (cause) =>
|
||||
new KeyValueStore.KeyValueStoreError({
|
||||
method: "open",
|
||||
message: "Failed to open backing store database",
|
||||
cause
|
||||
})
|
||||
})
|
||||
openRequest.onupgradeneeded = () => {
|
||||
const db = openRequest.result
|
||||
if (!db.objectStoreNames.contains(entriesStoreName)) {
|
||||
db.createObjectStore(entriesStoreName, { keyPath: "key" })
|
||||
}
|
||||
}
|
||||
return yield* idbRequest({ method: "open", message: "Failed to open backing store database" }, () => openRequest)
|
||||
})
|
||||
|
||||
const idbRequest = <A>(
|
||||
failArgs: { method: string; message: string; key?: string },
|
||||
evaluate: () => IDBRequest<A>
|
||||
): Effect.Effect<A, KeyValueStore.KeyValueStoreError> =>
|
||||
Effect.callback<A, KeyValueStore.KeyValueStoreError>((resume) => {
|
||||
const request = evaluate()
|
||||
if (request.readyState === "done") {
|
||||
return resume(Effect.succeed(request.result))
|
||||
}
|
||||
request.onsuccess = () => {
|
||||
resume(Effect.succeed(request.result))
|
||||
}
|
||||
request.onerror = () =>
|
||||
resume(Effect.fail(
|
||||
new KeyValueStore.KeyValueStoreError({
|
||||
...failArgs,
|
||||
cause: request.error
|
||||
})
|
||||
))
|
||||
})
|
||||
|
||||
const getKvsEntriesStore = (db: IDBDatabase, mode: IDBTransactionMode) => {
|
||||
const transaction = db.transaction(entriesStoreName, mode)
|
||||
return transaction.objectStore(entriesStoreName)
|
||||
}
|
||||
334
repos/effect/packages/platform-browser/src/BrowserPersistence.ts
Normal file
334
repos/effect/packages/platform-browser/src/BrowserPersistence.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* IndexedDB-backed persistence layers for browser Effect programs.
|
||||
*
|
||||
* This module provides a low-level `BackingPersistence` layer and a higher-level
|
||||
* `Persistence` layer that store object values in IndexedDB. Values are stored
|
||||
* by persistence store id and key, and reads check TTL expiration before
|
||||
* returning stored data. The database name can be customized and defaults to
|
||||
* `"effect_persistence"`.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type * as Arr from "effect/Array"
|
||||
import * as Clock from "effect/Clock"
|
||||
import type * as Duration from "effect/Duration"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Persistence from "effect/unstable/persistence/Persistence"
|
||||
|
||||
/**
|
||||
* Creates a `BackingPersistence` layer backed by IndexedDB, optionally using the provided database name.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when composing persistence manually and the lower-level
|
||||
* `BackingPersistence` service should be backed by browser IndexedDB.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* The database name defaults to `"effect_persistence"`. Entries are stored by
|
||||
* persistence store id and key in a shared object store, and TTL expiration is
|
||||
* checked when values are read.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* Opening the database is defected during layer acquisition if IndexedDB is
|
||||
* unavailable or cannot be opened. Store operations report `PersistenceError`
|
||||
* for IndexedDB request, transaction, quota, and structured-clone failures.
|
||||
*
|
||||
* @see {@link layerIndexedDb} for providing the higher-level `Persistence` service
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerBackingIndexedDb = (options?: {
|
||||
readonly database?: string | undefined
|
||||
}): Layer.Layer<Persistence.BackingPersistence> =>
|
||||
Layer.effect(Persistence.BackingPersistence)(Effect.gen(function*() {
|
||||
const db = yield* Effect.acquireRelease(
|
||||
openDatabase(options?.database ?? defaultDatabase),
|
||||
(db) => Effect.sync(() => db.close())
|
||||
).pipe(Effect.orDie)
|
||||
|
||||
return Persistence.BackingPersistence.of({
|
||||
make: Effect.fnUntraced(function*(storeId) {
|
||||
const clock = yield* Clock.Clock
|
||||
return {
|
||||
get: (key) => get(db, clock, storeId, key),
|
||||
getMany: (keys) => getMany(db, clock, storeId, keys),
|
||||
set: (key, value, ttl) => set(db, clock, storeId, key, value, ttl),
|
||||
setMany: (entries) => setMany(db, clock, storeId, entries),
|
||||
remove: (key) => remove(db, storeId, key),
|
||||
clear: clear(db, storeId)
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
const defaultDatabase = "effect_persistence"
|
||||
const databaseVersion = 1
|
||||
const entriesStoreName = "entries"
|
||||
const storeIdIndexName = "storeId"
|
||||
|
||||
/**
|
||||
* Creates a `Persistence` layer backed by IndexedDB, optionally using the provided database name.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerIndexedDb = (options?: {
|
||||
readonly database?: string | undefined
|
||||
}): Layer.Layer<Persistence.Persistence> =>
|
||||
Persistence.layer.pipe(
|
||||
Layer.provide(layerBackingIndexedDb(options))
|
||||
)
|
||||
|
||||
const openDatabase = (database: string): Effect.Effect<IDBDatabase, Persistence.PersistenceError> =>
|
||||
Effect.gen(function*() {
|
||||
const openRequest = yield* Effect.try({
|
||||
try: () => globalThis.indexedDB.open(database, databaseVersion),
|
||||
catch: (cause) =>
|
||||
new Persistence.PersistenceError({
|
||||
message: "Failed to open backing store database",
|
||||
cause
|
||||
})
|
||||
})
|
||||
|
||||
openRequest.onupgradeneeded = () => {
|
||||
const db = openRequest.result
|
||||
const entries = db.objectStoreNames.contains(entriesStoreName)
|
||||
? openRequest.transaction?.objectStore(entriesStoreName)
|
||||
: db.createObjectStore(entriesStoreName, { keyPath: ["storeId", "id"] })
|
||||
if (entries && !entries.indexNames.contains(storeIdIndexName)) {
|
||||
entries.createIndex(storeIdIndexName, storeIdIndexName, { unique: false })
|
||||
}
|
||||
}
|
||||
|
||||
return yield* idbRequest("Failed to open backing store database", () => openRequest)
|
||||
})
|
||||
|
||||
interface EntryRow {
|
||||
readonly storeId: string
|
||||
readonly id: string
|
||||
readonly value: object
|
||||
readonly expires: number | null
|
||||
}
|
||||
|
||||
const isExpired = (row: EntryRow, now: number): boolean => row.expires !== null && row.expires <= now
|
||||
|
||||
const get = (
|
||||
db: IDBDatabase,
|
||||
clock: Clock.Clock,
|
||||
storeId: string,
|
||||
key: string
|
||||
): Effect.Effect<object | undefined, Persistence.PersistenceError> =>
|
||||
withEntriesTransaction<object | undefined>(
|
||||
db,
|
||||
"readwrite",
|
||||
`Failed to get key ${key} from backing store`,
|
||||
(
|
||||
entries,
|
||||
setResult,
|
||||
fail
|
||||
) => {
|
||||
const now = clock.currentTimeMillisUnsafe()
|
||||
const id: [string, string] = [storeId, key]
|
||||
const request = entries.get(id)
|
||||
request.onerror = () => fail(request.error)
|
||||
request.onsuccess = () => {
|
||||
const row = request.result as EntryRow | undefined
|
||||
if (!row || !isExpired(row, now)) {
|
||||
setResult(row?.value)
|
||||
return
|
||||
}
|
||||
|
||||
const deleteRequest = entries.delete(id)
|
||||
deleteRequest.onerror = () => fail(deleteRequest.error)
|
||||
deleteRequest.onsuccess = () => setResult(undefined)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const getMany = (
|
||||
db: IDBDatabase,
|
||||
clock: Clock.Clock,
|
||||
storeId: string,
|
||||
keys: Arr.NonEmptyArray<string>
|
||||
): Effect.Effect<Arr.NonEmptyArray<object | undefined>, Persistence.PersistenceError> =>
|
||||
withEntriesTransaction(
|
||||
db,
|
||||
"readwrite",
|
||||
"Failed to getMany from backing store",
|
||||
(entries, setResult, fail) => {
|
||||
const now = clock.currentTimeMillisUnsafe()
|
||||
const results = new Array<object | undefined>(keys.length)
|
||||
setResult(results as any)
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
const keyPath = [storeId, key]
|
||||
const request = entries.get(keyPath)
|
||||
request.onerror = () => fail(request.error)
|
||||
request.onsuccess = () => {
|
||||
const row = request.result as EntryRow | undefined
|
||||
if (!row) return
|
||||
else if (!isExpired(row, now)) {
|
||||
results[i] = row.value
|
||||
return
|
||||
}
|
||||
const deleteRequest = entries.delete(keyPath)
|
||||
deleteRequest.onerror = () => fail(deleteRequest.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const set = (
|
||||
db: IDBDatabase,
|
||||
clock: Clock.Clock,
|
||||
storeId: string,
|
||||
key: string,
|
||||
value: object,
|
||||
ttl: Duration.Duration | undefined
|
||||
): Effect.Effect<void, Persistence.PersistenceError> =>
|
||||
withEntriesTransaction(
|
||||
db,
|
||||
"readwrite",
|
||||
`Failed to set key ${key} in backing store`,
|
||||
(entries, setResult, fail) => {
|
||||
const request = entries.put(
|
||||
{
|
||||
storeId,
|
||||
id: key,
|
||||
value,
|
||||
expires: Persistence.unsafeTtlToExpires(clock, ttl)
|
||||
} satisfies EntryRow
|
||||
)
|
||||
request.onerror = () => fail(request.error)
|
||||
request.onsuccess = () => setResult(undefined)
|
||||
}
|
||||
)
|
||||
|
||||
const setMany = (
|
||||
db: IDBDatabase,
|
||||
clock: Clock.Clock,
|
||||
storeId: string,
|
||||
entries: Arr.NonEmptyArray<readonly [key: string, value: object, ttl: Duration.Duration | undefined]>
|
||||
): Effect.Effect<void, Persistence.PersistenceError> =>
|
||||
withEntriesTransaction(
|
||||
db,
|
||||
"readwrite",
|
||||
"Failed to setMany in backing store",
|
||||
(store, setResult, fail) => {
|
||||
for (const [key, value, ttl] of entries) {
|
||||
const request = store.put(
|
||||
{
|
||||
storeId,
|
||||
id: key,
|
||||
value,
|
||||
expires: Persistence.unsafeTtlToExpires(clock, ttl)
|
||||
} satisfies EntryRow
|
||||
)
|
||||
request.onerror = () => fail(request.error)
|
||||
request.onsuccess = () => setResult(undefined)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const remove = (
|
||||
db: IDBDatabase,
|
||||
storeId: string,
|
||||
key: string
|
||||
): Effect.Effect<void, Persistence.PersistenceError> =>
|
||||
withEntriesTransaction(
|
||||
db,
|
||||
"readwrite",
|
||||
`Failed to remove key ${key} from backing store`,
|
||||
(entries, setResult, fail) => {
|
||||
const request = entries.delete([storeId, key])
|
||||
request.onerror = () => fail(request.error)
|
||||
request.onsuccess = () => setResult(undefined)
|
||||
}
|
||||
)
|
||||
|
||||
const clear = (db: IDBDatabase, storeId: string): Effect.Effect<void, Persistence.PersistenceError> =>
|
||||
withEntriesTransaction(db, "readwrite", "Failed to clear backing store", (entries, setResult, fail) => {
|
||||
const index = entries.index(storeIdIndexName)
|
||||
const cursorRequest = index.openCursor(storeId)
|
||||
cursorRequest.onerror = () => fail(cursorRequest.error)
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result
|
||||
if (!cursor) {
|
||||
setResult(undefined)
|
||||
return
|
||||
}
|
||||
const deleteRequest = cursor.delete()
|
||||
deleteRequest.onerror = () => fail(deleteRequest.error)
|
||||
deleteRequest.onsuccess = () => cursor.continue()
|
||||
}
|
||||
})
|
||||
|
||||
const withEntriesTransaction = <A>(
|
||||
db: IDBDatabase,
|
||||
mode: IDBTransactionMode,
|
||||
message: string,
|
||||
run: (
|
||||
entries: IDBObjectStore,
|
||||
onResult: (result: A) => void,
|
||||
fail: (cause: unknown) => void
|
||||
) => void
|
||||
): Effect.Effect<A, Persistence.PersistenceError> =>
|
||||
Effect.callback<A, Persistence.PersistenceError>((resume) => {
|
||||
const tx = db.transaction(entriesStoreName, mode)
|
||||
const entries = tx.objectStore(entriesStoreName)
|
||||
|
||||
let result: A | undefined
|
||||
let setResult = false
|
||||
let done = false
|
||||
|
||||
const fail = (cause: unknown) => {
|
||||
done = true
|
||||
resume(Effect.fail(new Persistence.PersistenceError({ message, cause })))
|
||||
}
|
||||
|
||||
tx.oncomplete = () => {
|
||||
done = true
|
||||
if (setResult) resume(Effect.succeed(result!))
|
||||
}
|
||||
tx.onerror = () => {
|
||||
done = true
|
||||
fail(tx.error)
|
||||
}
|
||||
tx.onabort = () => {
|
||||
done = true
|
||||
fail(tx.error)
|
||||
}
|
||||
|
||||
run(entries, (next) => {
|
||||
if (done) return resume(Effect.succeed(next))
|
||||
setResult = true
|
||||
result = next
|
||||
}, fail)
|
||||
|
||||
return Effect.sync(() => {
|
||||
tx.abort()
|
||||
})
|
||||
})
|
||||
|
||||
const idbRequest = <A>(
|
||||
message: string,
|
||||
evaluate: () => IDBRequest<A>
|
||||
): Effect.Effect<A, Persistence.PersistenceError> =>
|
||||
Effect.callback<A, Persistence.PersistenceError>((resume) => {
|
||||
const request = evaluate()
|
||||
const fail = (cause: unknown) => {
|
||||
resume(Effect.fail(new Persistence.PersistenceError({ message, cause })))
|
||||
}
|
||||
if (request.readyState === "done") {
|
||||
resume(Effect.succeed(request.result))
|
||||
}
|
||||
request.onsuccess = () => {
|
||||
resume(Effect.succeed(request.result))
|
||||
}
|
||||
request.onerror = () => fail(request.error)
|
||||
})
|
||||
53
repos/effect/packages/platform-browser/src/BrowserRuntime.ts
Normal file
53
repos/effect/packages/platform-browser/src/BrowserRuntime.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Browser helper for running a root Effect program.
|
||||
*
|
||||
* This module exports `runMain`, a browser version of the main Effect runner.
|
||||
* It forwards runner options to the core runtime and adds a `beforeunload`
|
||||
* listener that interrupts the main fiber when the page is unloading.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type * as Effect from "effect/Effect"
|
||||
import { makeRunMain, type Teardown } from "effect/Runtime"
|
||||
|
||||
/**
|
||||
* Runs an effect as the browser main program and interrupts its fiber when the page receives a `beforeunload` event.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use to launch a browser page, single-page application, demo, or browser test
|
||||
* harness as a root Effect program.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* Supports both direct and curried call forms. Options are forwarded to
|
||||
* `makeRunMain`, including `disableErrorReporting` and custom `teardown`
|
||||
* behavior.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* The `beforeunload` interruption is best-effort. Browser teardown may prevent
|
||||
* asynchronous finalizers, network work, timers, or prompts from completing.
|
||||
*
|
||||
* @category Runtime
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const runMain: {
|
||||
(
|
||||
options?: {
|
||||
readonly disableErrorReporting?: boolean | undefined
|
||||
readonly teardown?: Teardown | undefined
|
||||
}
|
||||
): <E, A>(effect: Effect.Effect<A, E>) => void
|
||||
<E, A>(
|
||||
effect: Effect.Effect<A, E>,
|
||||
options?: {
|
||||
readonly disableErrorReporting?: boolean | undefined
|
||||
readonly teardown?: Teardown | undefined
|
||||
}
|
||||
): void
|
||||
} = makeRunMain(({ fiber }) => {
|
||||
globalThis.addEventListener("beforeunload", () => {
|
||||
fiber.interruptUnsafe(fiber.id)
|
||||
})
|
||||
})
|
||||
52
repos/effect/packages/platform-browser/src/BrowserSocket.ts
Normal file
52
repos/effect/packages/platform-browser/src/BrowserSocket.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Browser WebSocket layers for Effect sockets.
|
||||
*
|
||||
* `layerWebSocket` creates a `Socket.Socket` connected to a WebSocket URL using
|
||||
* the browser `WebSocket` constructor. `layerWebSocketConstructor` provides
|
||||
* only the browser-backed constructor service for lower-level socket code.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
|
||||
/**
|
||||
* Creates a `Socket` layer connected to the given URL using the browser `WebSocket` constructor.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need browser code to satisfy the platform socket service from a
|
||||
* URL without wiring the browser constructor service separately.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* Delegates socket construction to `Socket.makeWebSocket` and provides the
|
||||
* browser-backed `WebSocketConstructor` service.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* Browser WebSocket rules still control URL schemes, mixed-content blocking,
|
||||
* cookies, authentication, origin checks, subprotocols, and extensions. Close
|
||||
* events are errors unless `closeCodeIsError` classifies the close code as
|
||||
* clean.
|
||||
*
|
||||
* @see {@link layerWebSocketConstructor} for providing only the browser constructor service
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerWebSocket = (url: string, options?: {
|
||||
readonly closeCodeIsError?: (code: number) => boolean
|
||||
}): Layer.Layer<Socket.Socket> =>
|
||||
Layer.effect(Socket.Socket, Socket.makeWebSocket(url, options)).pipe(
|
||||
Layer.provide(layerWebSocketConstructor)
|
||||
)
|
||||
|
||||
/**
|
||||
* Layer that provides a `WebSocketConstructor` service backed by `globalThis.WebSocket`.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerWebSocketConstructor: Layer.Layer<Socket.WebSocketConstructor> =
|
||||
Socket.layerWebSocketConstructorGlobal
|
||||
56
repos/effect/packages/platform-browser/src/BrowserStream.ts
Normal file
56
repos/effect/packages/platform-browser/src/BrowserStream.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Browser DOM event streams.
|
||||
*
|
||||
* This module provides typed constructors that turn `window.addEventListener`
|
||||
* and `document.addEventListener` events into Effect `Stream` values. Both
|
||||
* helpers accept the usual listener options and an optional stream buffer size.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
|
||||
import * as Stream from "effect/Stream"
|
||||
|
||||
/**
|
||||
* Creates a `Stream` from `window.addEventListener`.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* By default, the underlying buffer is unbounded in size. You can customize the
|
||||
* buffer size by passing an object as the second argument with the `bufferSize`
|
||||
* field.
|
||||
*
|
||||
* @category streams
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromEventListenerWindow = <K extends keyof WindowEventMap>(
|
||||
type: K,
|
||||
options?: boolean | {
|
||||
readonly capture?: boolean
|
||||
readonly passive?: boolean
|
||||
readonly once?: boolean
|
||||
readonly bufferSize?: number | undefined
|
||||
} | undefined
|
||||
): Stream.Stream<WindowEventMap[K], never, never> => Stream.fromEventListener<WindowEventMap[K]>(window, type, options)
|
||||
|
||||
/**
|
||||
* Creates a `Stream` from `document.addEventListener`.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* By default, the underlying buffer is unbounded in size. You can customize the
|
||||
* buffer size by passing an object as the second argument with the `bufferSize`
|
||||
* field.
|
||||
*
|
||||
* @category streams
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromEventListenerDocument = <K extends keyof DocumentEventMap>(
|
||||
type: K,
|
||||
options?: boolean | {
|
||||
readonly capture?: boolean
|
||||
readonly passive?: boolean
|
||||
readonly once?: boolean
|
||||
readonly bufferSize?: number | undefined
|
||||
} | undefined
|
||||
): Stream.Stream<DocumentEventMap[K], never, never> =>
|
||||
Stream.fromEventListener<DocumentEventMap[K]>(document, type, options)
|
||||
98
repos/effect/packages/platform-browser/src/BrowserWorker.ts
Normal file
98
repos/effect/packages/platform-browser/src/BrowserWorker.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Parent-side browser platform for Effect workers.
|
||||
*
|
||||
* `layerPlatform` provides the `WorkerPlatform` used to communicate with a
|
||||
* browser `Worker`, `SharedWorker`, or `MessagePort` through Effect's worker
|
||||
* protocol. `layer` combines that platform with a `Spawner` built from a
|
||||
* callback that creates or returns the worker endpoint for each worker id.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Deferred from "effect/Deferred"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Scope from "effect/Scope"
|
||||
import * as Worker from "effect/unstable/workers/Worker"
|
||||
import { WorkerError, WorkerReceiveError } from "effect/unstable/workers/WorkerError"
|
||||
|
||||
/**
|
||||
* Creates browser worker layers by combining the default `WorkerPlatform` with a spawner for `Worker`, `SharedWorker`, or `MessagePort` instances.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need both the browser `WorkerPlatform` and a `Spawner` from one
|
||||
* layer.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* The `spawn` callback receives the numeric worker id and may return a
|
||||
* `Worker`, `SharedWorker`, or `MessagePort`.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* Scope finalization sends the worker close protocol over the port. Dedicated
|
||||
* workers created by `spawn` are not terminated by this layer.
|
||||
*
|
||||
* @see {@link layerPlatform} for providing only the browser worker platform
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer = (
|
||||
spawn: (id: number) => Worker | SharedWorker | MessagePort
|
||||
): Layer.Layer<Worker.WorkerPlatform | Worker.Spawner> =>
|
||||
Layer.merge(
|
||||
layerPlatform,
|
||||
Worker.layerSpawner(spawn)
|
||||
)
|
||||
|
||||
/**
|
||||
* Layer that provides the browser `WorkerPlatform` for `Worker`, `SharedWorker`, and `MessagePort` communication.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerPlatform: Layer.Layer<Worker.WorkerPlatform> = Layer.succeed(Worker.WorkerPlatform)(
|
||||
Worker.makePlatform<globalThis.SharedWorker | globalThis.Worker | MessagePort>()({
|
||||
setup({ scope, worker }) {
|
||||
const port = "port" in worker ? worker.port : worker
|
||||
return Effect.as(
|
||||
Scope.addFinalizer(
|
||||
scope,
|
||||
Effect.sync(() => {
|
||||
port.postMessage([1])
|
||||
})
|
||||
),
|
||||
port
|
||||
)
|
||||
},
|
||||
listen({ deferred, emit, port, scope }) {
|
||||
function onMessage(event: MessageEvent) {
|
||||
emit(event.data)
|
||||
}
|
||||
function onError(event: ErrorEvent) {
|
||||
Deferred.doneUnsafe(
|
||||
deferred,
|
||||
new WorkerError({
|
||||
reason: new WorkerReceiveError({
|
||||
message: "An error event was emitter",
|
||||
cause: event.error ?? event.message
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
port.addEventListener("message", onMessage as any)
|
||||
port.addEventListener("error", onError as any)
|
||||
if ("start" in port) {
|
||||
port.start()
|
||||
}
|
||||
return Scope.addFinalizer(
|
||||
scope,
|
||||
Effect.sync(() => {
|
||||
port.removeEventListener("message", onMessage as any)
|
||||
port.removeEventListener("error", onError as any)
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Runner-side browser platform for Effect worker handlers.
|
||||
*
|
||||
* `make` builds a `WorkerRunnerPlatform` over a `MessagePort` or `Window`.
|
||||
* `layer` provides the platform from the global worker `self`, and
|
||||
* `layerMessagePort` provides it from an explicit endpoint. The platform
|
||||
* receives parent or client messages, runs Effect handlers, and posts responses
|
||||
* back through the browser messaging channel.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Cause from "effect/Cause"
|
||||
import * as Deferred from "effect/Deferred"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Exit from "effect/Exit"
|
||||
import * as Fiber from "effect/Fiber"
|
||||
import { identity } from "effect/Function"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Queue from "effect/Queue"
|
||||
import * as Scope from "effect/Scope"
|
||||
import { WorkerError, WorkerReceiveError } from "effect/unstable/workers/WorkerError"
|
||||
import * as WorkerRunner from "effect/unstable/workers/WorkerRunner"
|
||||
|
||||
const cachedPorts = new Set<MessagePort>()
|
||||
function globalHandleConnect(event: MessageEvent) {
|
||||
cachedPorts.add((event as MessageEvent).ports[0])
|
||||
}
|
||||
if (typeof self !== "undefined" && "onconnect" in self) {
|
||||
self.onconnect = globalHandleConnect
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `WorkerRunnerPlatform` service that runs worker handlers over a `MessagePort` or `Window`.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make = (self: MessagePort | Window): WorkerRunner.WorkerRunnerPlatform["Service"] => ({
|
||||
start: Effect.fnUntraced(function*<O = unknown, I = unknown>() {
|
||||
const disconnects = yield* Queue.make<number>()
|
||||
let currentPortId = 0
|
||||
|
||||
const ports = new Map<number, readonly [MessagePort, Scope.Closeable]>()
|
||||
const sendUnsafe = (portId: number, message: O, transfer?: ReadonlyArray<unknown>) =>
|
||||
(ports.get(portId)?.[0] ?? self).postMessage([1, message], {
|
||||
transfer: transfer as any
|
||||
})
|
||||
const send = (portId: number, message: O, transfer?: ReadonlyArray<unknown>) =>
|
||||
Effect.sync(() => sendUnsafe(portId, message, transfer))
|
||||
|
||||
const run = <A, E, R>(
|
||||
handler: (portId: number, message: I) => Effect.Effect<A, E, R> | void
|
||||
) =>
|
||||
Effect.scopedWith(Effect.fnUntraced(function*(scope) {
|
||||
const closeLatch = Deferred.makeUnsafe<void, WorkerError>()
|
||||
const trackFiber = Fiber.runIn(scope)
|
||||
const services = yield* Effect.context<R>()
|
||||
const runFork = Effect.runForkWith(services)
|
||||
const onExit = (exit: Exit.Exit<any, E>) => {
|
||||
if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) {
|
||||
runFork(Effect.logError("unhandled error in worker", exit.cause))
|
||||
}
|
||||
}
|
||||
|
||||
function onMessage(portId: number) {
|
||||
return function(event: MessageEvent) {
|
||||
const message = event.data as WorkerRunner.PlatformMessage<I>
|
||||
if (message[0] === 0) {
|
||||
const result = handler(portId, message[1])
|
||||
if (Effect.isEffect(result)) {
|
||||
const fiber = runFork(result)
|
||||
fiber.addObserver(onExit)
|
||||
trackFiber(fiber)
|
||||
}
|
||||
} else {
|
||||
const port = ports.get(portId)
|
||||
if (!port) {
|
||||
return
|
||||
} else if (ports.size === 1) {
|
||||
// let the last port close with the outer scope
|
||||
return Deferred.doneUnsafe(closeLatch, Exit.void)
|
||||
}
|
||||
ports.delete(portId)
|
||||
Effect.runFork(Scope.close(port[1], Exit.void))
|
||||
}
|
||||
}
|
||||
}
|
||||
function onMessageError(error: MessageEvent) {
|
||||
Deferred.doneUnsafe(
|
||||
closeLatch,
|
||||
new WorkerError({
|
||||
reason: new WorkerReceiveError({
|
||||
message: "An messageerror event was emitted",
|
||||
cause: error.data
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
function onError(error: any) {
|
||||
Deferred.doneUnsafe(
|
||||
closeLatch,
|
||||
new WorkerError({
|
||||
reason: new WorkerReceiveError({
|
||||
message: "An error event was emitted",
|
||||
cause: error.data
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
function handlePort(port: MessagePort) {
|
||||
const portScope = Scope.forkUnsafe(scope)
|
||||
const portId = currentPortId++
|
||||
ports.set(portId, [port, portScope])
|
||||
const onMsg = onMessage(portId)
|
||||
port.addEventListener("message", onMsg)
|
||||
port.addEventListener("messageerror", onMessageError)
|
||||
if ("start" in port) {
|
||||
port.start()
|
||||
}
|
||||
port.postMessage([0])
|
||||
Effect.runSync(Scope.addFinalizer(
|
||||
portScope,
|
||||
Effect.sync(() => {
|
||||
port.removeEventListener("message", onMsg)
|
||||
port.removeEventListener("messageerror", onError)
|
||||
port.close()
|
||||
})
|
||||
))
|
||||
}
|
||||
self.addEventListener("error", onError)
|
||||
let prevOnConnect: unknown | undefined
|
||||
if ("onconnect" in self) {
|
||||
prevOnConnect = self.onconnect
|
||||
self.onconnect = function(event: MessageEvent) {
|
||||
const port = (event as MessageEvent).ports[0]
|
||||
handlePort(port)
|
||||
}
|
||||
for (const port of cachedPorts) {
|
||||
handlePort(port)
|
||||
}
|
||||
cachedPorts.clear()
|
||||
} else {
|
||||
handlePort(self as any)
|
||||
}
|
||||
yield* Scope.addFinalizer(
|
||||
scope,
|
||||
Effect.sync(() => {
|
||||
self.removeEventListener("error", onError)
|
||||
if ("onconnect" in self) {
|
||||
self.close()
|
||||
self.onconnect = prevOnConnect
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
yield* Deferred.await(closeLatch)
|
||||
}))
|
||||
|
||||
return identity<WorkerRunner.WorkerRunner<O, I>>({ run, send, sendUnsafe, disconnects })
|
||||
}) as any
|
||||
})
|
||||
|
||||
/**
|
||||
* Layer that provides a browser `WorkerRunnerPlatform` using the global `self` worker context.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need a browser worker entry point to use the ambient `self`
|
||||
* object as the worker transport.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* Delegates to `make(self)` and provides the runner-side platform used by
|
||||
* protocols such as `RpcServer.layerProtocolWorkerRunner`.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* This layer depends on the browser worker global `self`. Use
|
||||
* `layerMessagePort` when the transport is an explicit `MessagePort` or
|
||||
* `Window`.
|
||||
*
|
||||
* @see {@link make} for constructing a runner platform from an explicit endpoint
|
||||
* @see {@link layerMessagePort} for providing a platform from an explicit endpoint
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<WorkerRunner.WorkerRunnerPlatform> = Layer.sync(WorkerRunner.WorkerRunnerPlatform)(() =>
|
||||
make(self)
|
||||
)
|
||||
|
||||
/**
|
||||
* Layer that provides a `WorkerRunnerPlatform` using the supplied `MessagePort` or `Window`.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerMessagePort = (port: MessagePort | Window): Layer.Layer<WorkerRunner.WorkerRunnerPlatform> =>
|
||||
Layer.succeed(WorkerRunner.WorkerRunnerPlatform)(make(port))
|
||||
143
repos/effect/packages/platform-browser/src/Clipboard.ts
Normal file
143
repos/effect/packages/platform-browser/src/Clipboard.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Browser clipboard integration for Effect programs.
|
||||
*
|
||||
* This module defines the `Clipboard` service, the `ClipboardError` raised by
|
||||
* failed browser operations, a `make` constructor for custom implementations,
|
||||
* and a browser-backed `layer` that uses `navigator.clipboard`. The service
|
||||
* supports reading and writing text, reading and writing `ClipboardItem`
|
||||
* payloads, writing one `Blob`, and clearing the clipboard.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Context from "effect/Context"
|
||||
import * as Data from "effect/Data"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
|
||||
const TypeId = "~@effect/platform-browser/Clipboard"
|
||||
const ErrorTypeId = "~@effect/platform-browser/Clipboard/ClipboardError"
|
||||
|
||||
/**
|
||||
* Defines the service interface for reading from, writing to, and clearing the browser clipboard.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when an application needs clipboard operations through an Effect service
|
||||
* so browser failures stay in the error channel.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* `read` and `write` work with `ClipboardItem` arrays. `readString` and
|
||||
* `writeString` use text, `writeBlob` writes one `Blob`, and `clear` writes an
|
||||
* empty string.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* Clipboard access generally requires a secure context and may require user
|
||||
* activation, permissions, or a focused document. `ClipboardItem` and non-text
|
||||
* MIME type support varies by browser. Failed browser operations are surfaced
|
||||
* as `ClipboardError`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface Clipboard {
|
||||
readonly [TypeId]: typeof TypeId
|
||||
readonly read: Effect.Effect<ClipboardItems, ClipboardError>
|
||||
readonly readString: Effect.Effect<string, ClipboardError>
|
||||
readonly write: (items: ClipboardItems) => Effect.Effect<void, ClipboardError>
|
||||
readonly writeString: (text: string) => Effect.Effect<void, ClipboardError>
|
||||
readonly writeBlob: (blob: Blob) => Effect.Effect<void, ClipboardError>
|
||||
readonly clear: Effect.Effect<void, ClipboardError>
|
||||
}
|
||||
|
||||
/**
|
||||
* Tagged error raised when a browser clipboard operation fails.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class ClipboardError extends Data.TaggedError("ClipboardError")<{
|
||||
readonly message: string
|
||||
readonly cause: unknown
|
||||
}> {
|
||||
readonly [ErrorTypeId] = ErrorTypeId
|
||||
}
|
||||
|
||||
/**
|
||||
* Service tag for browser clipboard capabilities.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need to require or provide clipboard capabilities through
|
||||
* Effect's context.
|
||||
*
|
||||
* @see {@link make} for building a custom clipboard service
|
||||
* @see {@link layer} for providing the browser-backed clipboard service
|
||||
*
|
||||
* @category services
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const Clipboard: Context.Service<Clipboard, Clipboard> = Context.Service<Clipboard>(TypeId)
|
||||
|
||||
/**
|
||||
* Builds a `Clipboard` service from primitive read and write operations, deriving `clear` and `writeBlob` helpers.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make = (
|
||||
impl: Omit<Clipboard, "clear" | "writeBlob" | typeof TypeId>
|
||||
): Clipboard =>
|
||||
Clipboard.of({
|
||||
...impl,
|
||||
[TypeId]: TypeId,
|
||||
clear: impl.writeString(""),
|
||||
writeBlob: (blob: Blob) => impl.write([new ClipboardItem({ [blob.type]: blob })])
|
||||
})
|
||||
|
||||
/**
|
||||
* Layer that directly interfaces with the browser Clipboard API.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<Clipboard> = Layer.succeed(
|
||||
Clipboard,
|
||||
make({
|
||||
read: Effect.tryPromise({
|
||||
try: () => navigator.clipboard.read(),
|
||||
catch: (cause) =>
|
||||
new ClipboardError({
|
||||
cause,
|
||||
"message": "Unable to read from clipboard"
|
||||
})
|
||||
}),
|
||||
write: (s: Array<ClipboardItem>) =>
|
||||
Effect.tryPromise({
|
||||
try: () => navigator.clipboard.write(s),
|
||||
catch: (cause) =>
|
||||
new ClipboardError({
|
||||
cause,
|
||||
"message": "Unable to write to clipboard"
|
||||
})
|
||||
}),
|
||||
readString: Effect.tryPromise({
|
||||
try: () => navigator.clipboard.readText(),
|
||||
catch: (cause) =>
|
||||
new ClipboardError({
|
||||
cause,
|
||||
"message": "Unable to read a string from clipboard"
|
||||
})
|
||||
}),
|
||||
writeString: (text: string) =>
|
||||
Effect.tryPromise({
|
||||
try: () => navigator.clipboard.writeText(text),
|
||||
catch: (cause) =>
|
||||
new ClipboardError({
|
||||
cause,
|
||||
"message": "Unable to write a string to clipboard"
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
233
repos/effect/packages/platform-browser/src/Geolocation.ts
Normal file
233
repos/effect/packages/platform-browser/src/Geolocation.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Browser geolocation integration for Effect programs.
|
||||
*
|
||||
* This module defines a `Geolocation` service backed by
|
||||
* `navigator.geolocation`. The service can read one current position or stream
|
||||
* watched position updates with a sliding buffer. Browser callback failures are
|
||||
* represented as `GeolocationError` values with `PositionUnavailable`,
|
||||
* `PermissionDenied`, or `Timeout` reasons. The module also provides the
|
||||
* browser-backed layer and a `watchPosition` accessor.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Cause from "effect/Cause"
|
||||
import * as Context from "effect/Context"
|
||||
import * as Data from "effect/Data"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Queue from "effect/Queue"
|
||||
import * as Stream from "effect/Stream"
|
||||
|
||||
const TypeId = "~@effect/platform-browser/Geolocation"
|
||||
const ErrorTypeId = "~@effect/platform-browser/Geolocation/GeolocationError"
|
||||
|
||||
/**
|
||||
* Defines the service interface for browser geolocation, providing effects for the current position and streams of watched positions.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when browser code needs a typed Effect service for one-shot location
|
||||
* reads or streamed location updates.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* `getCurrentPosition` returns one position effect. `watchPosition` returns a
|
||||
* stream and accepts the browser `PositionOptions` plus an optional sliding
|
||||
* `bufferSize`.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* Browser permission prompts, denied permissions, timeouts, unavailable
|
||||
* position data, secure-context restrictions, and policy restrictions are
|
||||
* surfaced as `GeolocationError`.
|
||||
*
|
||||
* @see {@link GeolocationError} for represented browser geolocation failures
|
||||
* @see {@link layer} for the browser-backed service implementation
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface Geolocation {
|
||||
readonly [TypeId]: typeof TypeId
|
||||
readonly getCurrentPosition: (
|
||||
options?: PositionOptions | undefined
|
||||
) => Effect.Effect<GeolocationPosition, GeolocationError>
|
||||
readonly watchPosition: (
|
||||
options?:
|
||||
| PositionOptions & {
|
||||
readonly bufferSize?: number | undefined
|
||||
}
|
||||
| undefined
|
||||
) => Stream.Stream<GeolocationPosition, GeolocationError>
|
||||
}
|
||||
|
||||
/**
|
||||
* Service tag for browser geolocation capabilities.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need to access or provide geolocation capabilities through
|
||||
* Effect's context.
|
||||
*
|
||||
* @see {@link layer} for providing the browser-backed geolocation service
|
||||
*
|
||||
* @category services
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const Geolocation: Context.Service<Geolocation, Geolocation> = Context.Service<Geolocation>(TypeId)
|
||||
|
||||
/**
|
||||
* Tagged error wrapping a browser geolocation failure reason.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class GeolocationError extends Data.TaggedError("GeolocationError")<{
|
||||
readonly reason: GeolocationErrorReason
|
||||
}> {
|
||||
constructor(props: {
|
||||
readonly reason: GeolocationErrorReason
|
||||
}) {
|
||||
super({
|
||||
...props,
|
||||
cause: props.reason.cause
|
||||
} as any)
|
||||
}
|
||||
|
||||
readonly [ErrorTypeId] = ErrorTypeId
|
||||
|
||||
override get message(): string {
|
||||
return this.reason.message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error reason for the browser geolocation `POSITION_UNAVAILABLE` failure.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class PositionUnavailable extends Data.TaggedError("PositionUnavailable")<{
|
||||
readonly cause: unknown
|
||||
}> {
|
||||
override get message(): string {
|
||||
return this._tag
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error reason for the browser geolocation `PERMISSION_DENIED` failure.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class PermissionDenied extends Data.TaggedError("PermissionDenied")<{
|
||||
readonly cause: unknown
|
||||
}> {
|
||||
override get message(): string {
|
||||
return this._tag
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error reason for the browser geolocation `TIMEOUT` failure.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class Timeout extends Data.TaggedError("Timeout")<{
|
||||
readonly cause: unknown
|
||||
}> {
|
||||
override get message(): string {
|
||||
return this._tag
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Union of browser geolocation error reasons represented by the service.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type GeolocationErrorReason = PositionUnavailable | PermissionDenied | Timeout
|
||||
|
||||
const makeQueue = (
|
||||
options:
|
||||
| PositionOptions & {
|
||||
readonly bufferSize?: number | undefined
|
||||
}
|
||||
| undefined
|
||||
) =>
|
||||
Queue.sliding<GeolocationPosition, GeolocationError>(options?.bufferSize ?? 16).pipe(
|
||||
Effect.tap((queue) =>
|
||||
Effect.acquireRelease(
|
||||
Effect.sync(() =>
|
||||
navigator.geolocation.watchPosition(
|
||||
(position) => Queue.offerUnsafe(queue, position),
|
||||
(cause) => {
|
||||
if (cause.code === cause.PERMISSION_DENIED) {
|
||||
const error = new GeolocationError({
|
||||
reason: new PermissionDenied({ cause })
|
||||
})
|
||||
Queue.failCauseUnsafe(queue, Cause.fail(error))
|
||||
} else if (cause.code === cause.TIMEOUT) {
|
||||
const error = new GeolocationError({
|
||||
reason: new Timeout({ cause })
|
||||
})
|
||||
Queue.failCauseUnsafe(queue, Cause.fail(error))
|
||||
} else if (cause.code === cause.POSITION_UNAVAILABLE) {
|
||||
const error = new GeolocationError({
|
||||
reason: new PositionUnavailable({ cause })
|
||||
})
|
||||
Queue.failCauseUnsafe(queue, Cause.fail(error))
|
||||
}
|
||||
},
|
||||
options
|
||||
)
|
||||
),
|
||||
(handleId) => Effect.sync(() => navigator.geolocation.clearWatch(handleId))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Layer that provides `Geolocation` using `navigator.geolocation`, with watched positions buffered in a sliding queue.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<Geolocation> = Layer.succeed(
|
||||
Geolocation,
|
||||
Geolocation.of({
|
||||
[TypeId]: TypeId,
|
||||
getCurrentPosition: (options) =>
|
||||
makeQueue(options).pipe(
|
||||
Effect.flatMap(Queue.take),
|
||||
Effect.scoped
|
||||
),
|
||||
watchPosition: (options) =>
|
||||
makeQueue(options).pipe(
|
||||
Effect.map(Stream.fromQueue),
|
||||
Stream.unwrap
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Reads geolocation positions from the `Geolocation` service as a stream, with
|
||||
* an optional sliding buffer size.
|
||||
*
|
||||
* @category accessors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const watchPosition = (
|
||||
options?:
|
||||
| PositionOptions & {
|
||||
readonly bufferSize?: number | undefined
|
||||
}
|
||||
| undefined
|
||||
): Stream.Stream<GeolocationPosition, GeolocationError, Geolocation> =>
|
||||
Stream.unwrap(Effect.map(
|
||||
Effect.service(Geolocation),
|
||||
(geolocation) => geolocation.watchPosition(options)
|
||||
))
|
||||
113
repos/effect/packages/platform-browser/src/IndexedDb.ts
Normal file
113
repos/effect/packages/platform-browser/src/IndexedDb.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Browser IndexedDB primitives and key schemas for Effect applications.
|
||||
*
|
||||
* This module is the low-level bridge used by the platform-browser IndexedDB
|
||||
* integration. It provides an `IndexedDb` service around the browser
|
||||
* `indexedDB` factory and `IDBKeyRange` constructor, a `layerWindow` layer for
|
||||
* wiring those primitives from `window`, and schemas for the key shapes accepted
|
||||
* by IndexedDB object stores and indexes.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Context from "effect/Context"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Schema from "effect/Schema"
|
||||
|
||||
const TypeId = "~@effect/platform-browser/IndexedDb"
|
||||
|
||||
/**
|
||||
* Service interface that provides the browser `indexedDB` factory and `IDBKeyRange` constructor.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface IndexedDb {
|
||||
readonly [TypeId]: typeof TypeId
|
||||
readonly indexedDB: globalThis.IDBFactory
|
||||
readonly IDBKeyRange: typeof globalThis.IDBKeyRange
|
||||
}
|
||||
|
||||
/**
|
||||
* Service tag for browser IndexedDB primitives.
|
||||
*
|
||||
* @category services
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const IndexedDb: Context.Service<IndexedDb, IndexedDb> = Context.Service<IndexedDb, IndexedDb>(TypeId)
|
||||
|
||||
/** @internal */
|
||||
const IDBFlatKey = Schema.Union([
|
||||
Schema.String,
|
||||
Schema.Number.check(Schema.makeFilter((input) => !Number.isNaN(input))),
|
||||
Schema.DateValid,
|
||||
Schema.declare(
|
||||
(input): input is BufferSource =>
|
||||
input instanceof ArrayBuffer ||
|
||||
(ArrayBuffer.isView(input) && input.buffer instanceof ArrayBuffer)
|
||||
)
|
||||
])
|
||||
|
||||
/**
|
||||
* Schema for IndexedDB keys: strings, non-NaN numbers, valid dates, buffer sources, or arrays of those flat key values.
|
||||
*
|
||||
* @category schemas
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const IDBValidKey = Schema.Union([IDBFlatKey, Schema.Array(IDBFlatKey)])
|
||||
|
||||
/**
|
||||
* Schema for auto-incremented IndexedDB keys, accepting integers from 1 through `2 ** 53`.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need to define numeric key-path fields for `IndexedDbTable`
|
||||
* definitions that use IndexedDB auto-increment keys.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* The schema accepts integer values from `1` through `2 ** 53`, matching the
|
||||
* range used for generated IndexedDB auto-increment keys.
|
||||
*
|
||||
* @see {@link IDBValidKey} for the broader IndexedDB key schema
|
||||
*
|
||||
* @category schemas
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const AutoIncrement = Schema.Int.check(
|
||||
Schema.isBetween({ minimum: 1, maximum: 2 ** 53 })
|
||||
).annotate({
|
||||
identifier: "AutoIncrement",
|
||||
title: "autoIncrement",
|
||||
description: "Defines a valid autoIncrement key path for the IndexedDb table"
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates an `IndexedDb` service from an `IDBFactory` and `IDBKeyRange` constructor.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make = (impl: Omit<IndexedDb, typeof TypeId>): IndexedDb => IndexedDb.of({ [TypeId]: TypeId, ...impl })
|
||||
|
||||
/**
|
||||
* Layer that provides `IndexedDb` from `window.indexedDB` and `window.IDBKeyRange`, failing with a config error when they are unavailable.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerWindow: Layer.Layer<IndexedDb> = Layer.effect(
|
||||
IndexedDb,
|
||||
Effect.suspend(() => {
|
||||
if (window.indexedDB && window.IDBKeyRange) {
|
||||
return Effect.succeed(
|
||||
make({
|
||||
indexedDB: window.indexedDB,
|
||||
IDBKeyRange: window.IDBKeyRange
|
||||
})
|
||||
)
|
||||
} else {
|
||||
return Effect.die(new Error("window.indexedDB is not available"))
|
||||
}
|
||||
})
|
||||
)
|
||||
648
repos/effect/packages/platform-browser/src/IndexedDbDatabase.ts
Normal file
648
repos/effect/packages/platform-browser/src/IndexedDbDatabase.ts
Normal file
@@ -0,0 +1,648 @@
|
||||
/**
|
||||
* Builds and opens typed IndexedDB databases from versioned schema migrations.
|
||||
*
|
||||
* This module turns an `IndexedDbVersion` migration chain into an
|
||||
* `IndexedDbDatabase` layer. The layer opens the browser database, runs any
|
||||
* pending upgrade migrations, provides a query builder for the current schema,
|
||||
* and exposes a `rebuild` effect that deletes and reopens the database.
|
||||
* Migration transactions can create or delete object stores and indexes, and
|
||||
* database failures are represented as `IndexedDbDatabaseError` values.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Context from "effect/Context"
|
||||
import * as Data from "effect/Data"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Effectable from "effect/Effectable"
|
||||
import * as Fiber from "effect/Fiber"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as MutableRef from "effect/MutableRef"
|
||||
import * as Semaphore from "effect/Semaphore"
|
||||
import * as Reactivity from "effect/unstable/reactivity/Reactivity"
|
||||
import * as IndexedDb from "./IndexedDb.ts"
|
||||
import * as IndexedDbQueryBuilder from "./IndexedDbQueryBuilder.ts"
|
||||
import type * as IndexedDbTable from "./IndexedDbTable.ts"
|
||||
import type * as IndexedDbVersion from "./IndexedDbVersion.ts"
|
||||
|
||||
const TypeId = "~@effect/platform-browser/IndexedDbDatabase"
|
||||
const ErrorTypeId = "~@effect/platform-browser/IndexedDbDatabase/IndexedDbDatabaseError"
|
||||
|
||||
const SchemaProto = {
|
||||
[TypeId]: {
|
||||
_A: (_: never) => _
|
||||
},
|
||||
...Effectable.Prototype<IndexedDbSchema<any, any, any>>({
|
||||
label: "IndexedDbSchema",
|
||||
evaluate() {
|
||||
return this.getQueryBuilder
|
||||
}
|
||||
}),
|
||||
get getQueryBuilder() {
|
||||
const self = this as unknown as IndexedDbSchema<any, any, any>
|
||||
return IndexedDbDatabase.useSync(({ database, IDBKeyRange, reactivity }) =>
|
||||
IndexedDbQueryBuilder.make({
|
||||
database,
|
||||
IDBKeyRange,
|
||||
tables: self.version.tables,
|
||||
reactivity
|
||||
})
|
||||
)
|
||||
},
|
||||
add<Version extends IndexedDbVersion.AnyWithProps>(
|
||||
this: IndexedDbSchema<any, any, any>,
|
||||
version: Version,
|
||||
migrate: (
|
||||
fromQuery: Transaction<any>,
|
||||
toQuery: Transaction<Version>
|
||||
) => Effect.Effect<void, Error>
|
||||
) {
|
||||
return makeMigration({
|
||||
fromVersion: this.version,
|
||||
version,
|
||||
migrate,
|
||||
previous: this
|
||||
})
|
||||
},
|
||||
layer(this: IndexedDbSchema<any, any, any>, databaseName: string) {
|
||||
return layer(databaseName, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String union describing the failure categories for IndexedDB database opening, migration, and schema operations.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type ErrorReason =
|
||||
| "TransactionError"
|
||||
| "MissingTable"
|
||||
| "OpenError"
|
||||
| "UpgradeError"
|
||||
| "Aborted"
|
||||
| "Blocked"
|
||||
| "MissingIndex"
|
||||
|
||||
/**
|
||||
* Tagged error for IndexedDB database operations, carrying a database error reason and the original cause.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class IndexedDbDatabaseError extends Data.TaggedError(
|
||||
"IndexedDbDatabaseError"
|
||||
)<{
|
||||
reason: ErrorReason
|
||||
cause: unknown
|
||||
}> {
|
||||
/**
|
||||
* Marks this value as an IndexedDB database error for runtime guards.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
readonly [ErrorTypeId]: typeof ErrorTypeId = ErrorTypeId
|
||||
override readonly message = this.reason
|
||||
}
|
||||
|
||||
/**
|
||||
* Service tag for an open IndexedDB database, its `IDBKeyRange` constructor, reactivity service, and rebuild effect.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need access to the live database service after an
|
||||
* `IndexedDbSchema` layer has been provided, especially for `rebuild` or
|
||||
* lower-level database primitives.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* `database` is a mutable reference to the current `IDBDatabase`. `IDBKeyRange`
|
||||
* and `reactivity` are shared with query builders created from the schema.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* `rebuild` closes and deletes the browser database, then reopens it and reruns
|
||||
* migrations. Records not recreated by migrations are removed.
|
||||
*
|
||||
* @see {@link IndexedDb.IndexedDb} for the lower-level browser IndexedDB primitives
|
||||
* @see {@link make} for creating a schema that provides this service as a layer
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class IndexedDbDatabase extends Context.Service<
|
||||
IndexedDbDatabase,
|
||||
{
|
||||
readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
|
||||
readonly IDBKeyRange: typeof globalThis.IDBKeyRange
|
||||
readonly reactivity: Reactivity.Reactivity["Service"]
|
||||
readonly rebuild: Effect.Effect<void, IndexedDbDatabaseError>
|
||||
}
|
||||
>()(TypeId) {}
|
||||
|
||||
/**
|
||||
* Describes an IndexedDB schema version and its migrations, and acts as an effect that yields a query builder for the target version.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface IndexedDbSchema<
|
||||
in out FromVersion extends IndexedDbVersion.AnyWithProps,
|
||||
in out ToVersion extends IndexedDbVersion.AnyWithProps,
|
||||
out Error = never
|
||||
> extends
|
||||
Effect.Effect<
|
||||
IndexedDbQueryBuilder.IndexedDbQueryBuilder<ToVersion>,
|
||||
never,
|
||||
IndexedDbDatabase
|
||||
>
|
||||
{
|
||||
new(_: never): {}
|
||||
|
||||
readonly previous: [FromVersion] extends [never] ? undefined
|
||||
: IndexedDbSchema<never, FromVersion, Error>
|
||||
readonly fromVersion: FromVersion
|
||||
readonly version: ToVersion
|
||||
|
||||
readonly migrate: [FromVersion] extends [never] ? (query: Transaction<ToVersion>) => Effect.Effect<void, Error>
|
||||
: (
|
||||
fromQuery: Transaction<FromVersion>,
|
||||
toQuery: Transaction<ToVersion>
|
||||
) => Effect.Effect<void, Error>
|
||||
|
||||
readonly add: <Version extends IndexedDbVersion.AnyWithProps, MigrationError>(
|
||||
version: Version,
|
||||
migrate: (
|
||||
fromQuery: Transaction<ToVersion>,
|
||||
toQuery: Transaction<Version>
|
||||
) => Effect.Effect<void, MigrationError>
|
||||
) => IndexedDbSchema<ToVersion, Version, MigrationError | Error>
|
||||
|
||||
readonly getQueryBuilder: Effect.Effect<
|
||||
IndexedDbQueryBuilder.IndexedDbQueryBuilder<ToVersion>,
|
||||
never,
|
||||
IndexedDbDatabase
|
||||
>
|
||||
|
||||
readonly layer: (
|
||||
databaseName: string
|
||||
) => Layer.Layer<
|
||||
IndexedDbDatabase,
|
||||
IndexedDbDatabaseError,
|
||||
IndexedDb.IndexedDb
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder available during a database migration, extended with object-store and index management helpers for the active `IDBTransaction`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface Transaction<
|
||||
Source extends IndexedDbVersion.AnyWithProps = never
|
||||
> extends Omit<IndexedDbQueryBuilder.IndexedDbQueryBuilder<Source>, "transaction"> {
|
||||
readonly transaction: globalThis.IDBTransaction
|
||||
|
||||
readonly createObjectStore: <
|
||||
A extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
|
||||
>(
|
||||
table: A
|
||||
) => Effect.Effect<globalThis.IDBObjectStore, IndexedDbDatabaseError>
|
||||
|
||||
readonly deleteObjectStore: <
|
||||
A extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
|
||||
>(
|
||||
table: A
|
||||
) => Effect.Effect<void, IndexedDbDatabaseError>
|
||||
|
||||
readonly createIndex: <
|
||||
Name extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
|
||||
>(
|
||||
table: Name,
|
||||
indexName: IndexFromTableName<Source, Name>,
|
||||
options?: IDBIndexParameters
|
||||
) => Effect.Effect<globalThis.IDBIndex, IndexedDbDatabaseError>
|
||||
|
||||
readonly deleteIndex: <
|
||||
Name extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
|
||||
>(
|
||||
table: Name,
|
||||
indexName: IndexFromTableName<Source, Name>
|
||||
) => Effect.Effect<void, IndexedDbDatabaseError>
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the string-literal index names defined by an `IndexedDbTable`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type IndexFromTable<Table extends IndexedDbTable.AnyWithProps> = IsStringLiteral<
|
||||
Extract<keyof IndexedDbTable.Indexes<Table>, string>
|
||||
> extends true ? Extract<keyof IndexedDbTable.Indexes<Table>, string>
|
||||
: never
|
||||
|
||||
/**
|
||||
* Extracts the valid index names for a table name within an IndexedDB version.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type IndexFromTableName<
|
||||
Version extends IndexedDbVersion.AnyWithProps,
|
||||
Table extends string
|
||||
> = IndexFromTable<
|
||||
IndexedDbTable.WithName<IndexedDbVersion.Tables<Version>, Table>
|
||||
>
|
||||
|
||||
/**
|
||||
* Type-erased IndexedDB schema shape used when traversing schema migration chains.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface Any {
|
||||
readonly previous?: Any | undefined
|
||||
readonly layer: (
|
||||
databaseName: string
|
||||
) => Layer.Layer<
|
||||
IndexedDbDatabase,
|
||||
IndexedDbDatabaseError,
|
||||
IndexedDb.IndexedDb
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-erased `IndexedDbSchema` covering any source version, target version, and migration error type.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type AnySchema = IndexedDbSchema<
|
||||
IndexedDbVersion.AnyWithProps,
|
||||
IndexedDbVersion.AnyWithProps,
|
||||
any
|
||||
>
|
||||
|
||||
/**
|
||||
* Creates the initial `IndexedDbSchema` from a version and an initialization migration run during database upgrade.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make = <
|
||||
InitialVersion extends IndexedDbVersion.AnyWithProps,
|
||||
Error
|
||||
>(
|
||||
initialVersion: InitialVersion,
|
||||
init: (toQuery: Transaction<InitialVersion>) => Effect.Effect<void, Error>
|
||||
): IndexedDbSchema<never, InitialVersion, Error> => {
|
||||
// oxlint-disable-next-line typescript/no-extraneous-class
|
||||
function Initial() {}
|
||||
Object.setPrototypeOf(Initial, SchemaProto)
|
||||
;(Initial as any).version = initialVersion
|
||||
;(Initial as any).migrate = init
|
||||
return Initial as any
|
||||
}
|
||||
|
||||
const makeMigration = <
|
||||
FromVersion extends IndexedDbVersion.AnyWithProps,
|
||||
ToVersion extends IndexedDbVersion.AnyWithProps,
|
||||
Error
|
||||
>(options: {
|
||||
readonly previous:
|
||||
| IndexedDbSchema<FromVersion, ToVersion, Error>
|
||||
| IndexedDbSchema<never, FromVersion, Error>
|
||||
readonly fromVersion: FromVersion
|
||||
readonly version: ToVersion
|
||||
readonly migrate: (
|
||||
fromQuery: Transaction<FromVersion>,
|
||||
toQuery: Transaction<ToVersion>
|
||||
) => Effect.Effect<void, Error>
|
||||
}): IndexedDbSchema<FromVersion, ToVersion, Error> => {
|
||||
// oxlint-disable-next-line typescript/no-extraneous-class
|
||||
function Migration() {}
|
||||
Object.setPrototypeOf(Migration, SchemaProto)
|
||||
;(Migration as any).previous = options.previous
|
||||
;(Migration as any).fromVersion = options.fromVersion
|
||||
;(Migration as any).version = options.version
|
||||
;(Migration as any).migrate = options.migrate
|
||||
|
||||
return Migration as any
|
||||
}
|
||||
|
||||
const layer = <DatabaseName extends string>(
|
||||
databaseName: DatabaseName,
|
||||
migration: Any
|
||||
) =>
|
||||
Layer.effect(
|
||||
IndexedDbDatabase,
|
||||
Effect.gen(function*() {
|
||||
const { IDBKeyRange, indexedDB } = yield* IndexedDb.IndexedDb
|
||||
const reactivity = yield* Reactivity.Reactivity
|
||||
const context = yield* Effect.context()
|
||||
const runForkWith = Effect.runForkWith(context)
|
||||
|
||||
let oldVersion = 0
|
||||
const migrations: Array<Any> = []
|
||||
let current = migration
|
||||
while (current) {
|
||||
migrations.unshift(current)
|
||||
current = (current as unknown as AnySchema).previous as any
|
||||
}
|
||||
|
||||
const version = migrations.length
|
||||
const database = MutableRef.make<globalThis.IDBDatabase>(null as any)
|
||||
|
||||
const open = Effect.callback<
|
||||
void,
|
||||
IndexedDbDatabaseError
|
||||
>((resume) => {
|
||||
const request = indexedDB.open(databaseName, version)
|
||||
|
||||
request.onblocked = (event) => {
|
||||
resume(
|
||||
Effect.fail(
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "Blocked",
|
||||
cause: event
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
request.onerror = (event) => {
|
||||
const idbRequest = event.target as IDBRequest<IDBDatabase>
|
||||
|
||||
resume(
|
||||
Effect.fail(
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "OpenError",
|
||||
cause: idbRequest.error
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let fiber: Fiber.Fiber<void, IndexedDbDatabaseError> | undefined
|
||||
request.onupgradeneeded = (event) => {
|
||||
const idbRequest = event.target as IDBRequest<IDBDatabase>
|
||||
const db = idbRequest.result
|
||||
const transaction = idbRequest.transaction
|
||||
oldVersion = event.oldVersion
|
||||
|
||||
MutableRef.set(database, db)
|
||||
|
||||
if (transaction === null) {
|
||||
return resume(
|
||||
Effect.fail(
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "TransactionError",
|
||||
cause: null
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
transaction.onabort = (event) => {
|
||||
resume(
|
||||
Effect.fail(
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "Aborted",
|
||||
cause: event
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
transaction.onerror = (event) => {
|
||||
resume(
|
||||
Effect.fail(
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "TransactionError",
|
||||
cause: event
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const effect = Effect.forEach(
|
||||
migrations.slice(oldVersion),
|
||||
(untypedMigration) => {
|
||||
if (untypedMigration.previous === undefined) {
|
||||
const migration = untypedMigration as any as AnySchema
|
||||
const api = makeTransactionProto({
|
||||
database,
|
||||
IDBKeyRange,
|
||||
tables: migration.version.tables,
|
||||
transaction,
|
||||
reactivity
|
||||
})
|
||||
return (migration as any).migrate(api) as Effect.Effect<
|
||||
void,
|
||||
IndexedDbDatabaseError
|
||||
>
|
||||
} else if (untypedMigration.previous) {
|
||||
const migration = untypedMigration as any as AnySchema
|
||||
const fromApi = makeTransactionProto({
|
||||
database,
|
||||
IDBKeyRange,
|
||||
tables: migration.fromVersion.tables,
|
||||
transaction,
|
||||
reactivity
|
||||
})
|
||||
const toApi = makeTransactionProto({
|
||||
database,
|
||||
IDBKeyRange,
|
||||
tables: migration.version.tables,
|
||||
transaction,
|
||||
reactivity
|
||||
})
|
||||
return migration.migrate(fromApi, toApi) as Effect.Effect<
|
||||
void,
|
||||
IndexedDbDatabaseError
|
||||
>
|
||||
}
|
||||
|
||||
return Effect.die(new Error("Invalid migration"))
|
||||
},
|
||||
{ discard: true }
|
||||
).pipe(
|
||||
Effect.mapError(
|
||||
(cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "UpgradeError",
|
||||
cause
|
||||
})
|
||||
),
|
||||
Effect.provideService(IndexedDbQueryBuilder.IndexedDbTransaction, transaction)
|
||||
)
|
||||
fiber = runForkWith(effect)
|
||||
fiber.currentDispatcher.flush()
|
||||
}
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
const idbRequest = event.target as IDBRequest<IDBDatabase>
|
||||
const db = idbRequest.result
|
||||
MutableRef.set(database, db)
|
||||
if (fiber) {
|
||||
// ensure migration errors are propagated
|
||||
resume(Effect.asVoid(Fiber.join(fiber)))
|
||||
} else {
|
||||
resume(Effect.void)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
yield* Effect.addFinalizer(() => {
|
||||
database.current?.close()
|
||||
return Effect.void
|
||||
})
|
||||
yield* open
|
||||
|
||||
const rebuildLock = Semaphore.makeUnsafe(1).withPermit
|
||||
const rebuild = Effect.callback<void, IndexedDbDatabaseError>((resume) => {
|
||||
database.current?.close()
|
||||
const request = indexedDB.deleteDatabase(databaseName)
|
||||
request.onerror = (_) => {
|
||||
resume(
|
||||
Effect.fail(
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "OpenError",
|
||||
cause: request.error
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
request.onsuccess = () => {
|
||||
resume(Effect.void)
|
||||
}
|
||||
}).pipe(
|
||||
Effect.flatMap(() => open),
|
||||
rebuildLock
|
||||
)
|
||||
|
||||
return IndexedDbDatabase.of({ database, IDBKeyRange, rebuild, reactivity })
|
||||
})
|
||||
).pipe(
|
||||
Layer.provide(Reactivity.layer)
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Internal
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type IsStringLiteral<T> = T extends string ? string extends T ? false
|
||||
: true
|
||||
: false
|
||||
|
||||
const makeTransactionProto = <Source extends IndexedDbVersion.AnyWithProps>({
|
||||
IDBKeyRange,
|
||||
database,
|
||||
tables,
|
||||
transaction,
|
||||
reactivity
|
||||
}: {
|
||||
readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
|
||||
readonly IDBKeyRange: typeof globalThis.IDBKeyRange
|
||||
readonly tables: ReadonlyMap<string, IndexedDbVersion.Tables<Source>>
|
||||
readonly transaction: globalThis.IDBTransaction
|
||||
readonly reactivity: Reactivity.Reactivity["Service"]
|
||||
}): Transaction<Source> => {
|
||||
const migration = IndexedDbQueryBuilder.make({
|
||||
database,
|
||||
IDBKeyRange,
|
||||
tables,
|
||||
reactivity
|
||||
}) as any
|
||||
|
||||
migration.transaction = transaction
|
||||
|
||||
migration.createObjectStore = Effect.fnUntraced(function*(table: string) {
|
||||
const createTable = yield* Effect.fromNullishOr(tables.get(table)).pipe(
|
||||
Effect.mapError(
|
||||
(cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "MissingTable",
|
||||
cause
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return yield* Effect.try({
|
||||
try: () =>
|
||||
database.current.createObjectStore(createTable.tableName, {
|
||||
keyPath: createTable.keyPath,
|
||||
autoIncrement: createTable.autoIncrement
|
||||
}),
|
||||
catch: (cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "TransactionError",
|
||||
cause
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
migration.deleteObjectStore = Effect.fnUntraced(function*(table: string) {
|
||||
const createTable = yield* Effect.fromNullishOr(tables.get(table)).pipe(
|
||||
Effect.mapError(
|
||||
(cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "MissingTable",
|
||||
cause
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return yield* Effect.try({
|
||||
try: () => database.current.deleteObjectStore(createTable.tableName),
|
||||
catch: (cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "TransactionError",
|
||||
cause
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
migration.createIndex = Effect.fnUntraced(function*(
|
||||
table: string,
|
||||
indexName: string,
|
||||
options?: IDBIndexParameters
|
||||
) {
|
||||
const store = transaction.objectStore(table)
|
||||
const sourceTable = tables.get(table)!
|
||||
|
||||
const keyPath = yield* Effect.fromNullishOr(
|
||||
sourceTable.indexes[indexName]
|
||||
).pipe(
|
||||
Effect.mapError(
|
||||
(cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "MissingIndex",
|
||||
cause
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return yield* Effect.try({
|
||||
try: () => store.createIndex(indexName, keyPath, options),
|
||||
catch: (cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "TransactionError",
|
||||
cause
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
migration.deleteIndex = (table: string, indexName: string) =>
|
||||
Effect.try({
|
||||
try: () => transaction.objectStore(table).deleteIndex(indexName),
|
||||
catch: (cause) =>
|
||||
new IndexedDbDatabaseError({
|
||||
reason: "TransactionError",
|
||||
cause
|
||||
})
|
||||
})
|
||||
|
||||
return migration
|
||||
}
|
||||
2032
repos/effect/packages/platform-browser/src/IndexedDbQueryBuilder.ts
Normal file
2032
repos/effect/packages/platform-browser/src/IndexedDbQueryBuilder.ts
Normal file
File diff suppressed because it is too large
Load Diff
260
repos/effect/packages/platform-browser/src/IndexedDbTable.ts
Normal file
260
repos/effect/packages/platform-browser/src/IndexedDbTable.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Typed object-store descriptors for the browser IndexedDB integration.
|
||||
*
|
||||
* An {@link IndexedDbTable} is the schema-backed description of one IndexedDB
|
||||
* object store. It carries the store name, row schema, key path, index key
|
||||
* paths, auto-increment mode, and transaction durability used by database
|
||||
* versions, migrations, and typed queries. The `make` constructor also derives
|
||||
* the read, array, and auto-increment write schemas used by the query builder.
|
||||
*
|
||||
* @see {@link make} for constructing table descriptors.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import { type Pipeable, pipeArguments } from "effect/Pipeable"
|
||||
import * as Schema from "effect/Schema"
|
||||
import * as Struct from "effect/Struct"
|
||||
import type { NoInfer } from "effect/Types"
|
||||
import * as IndexedDb from "./IndexedDb.ts"
|
||||
import type * as IndexedDbQueryBuilder from "./IndexedDbQueryBuilder.ts"
|
||||
|
||||
const TypeId = "~@effect/platform-browser/IndexedDbTable"
|
||||
|
||||
/**
|
||||
* Typed IndexedDB table definition containing its name, schema, key path, indexes, auto-increment setting, and transaction durability.
|
||||
*
|
||||
* @category interface
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface IndexedDbTable<
|
||||
out Name extends string,
|
||||
out TableSchema extends AnySchemaStruct,
|
||||
out Indexes extends Record<
|
||||
string,
|
||||
IndexedDbQueryBuilder.KeyPath<TableSchema>
|
||||
>,
|
||||
out KeyPath extends Readonly<IDBValidKey | undefined>,
|
||||
out AutoIncrement extends boolean
|
||||
> extends Pipeable {
|
||||
new(_: never): {}
|
||||
readonly [TypeId]: typeof TypeId
|
||||
readonly tableName: Name
|
||||
readonly tableSchema: TableSchema
|
||||
readonly readSchema: Schema.Top
|
||||
readonly autoincrementSchema: Schema.Top
|
||||
readonly arraySchema: Schema.Top
|
||||
readonly keyPath: KeyPath
|
||||
readonly indexes: Indexes
|
||||
readonly autoIncrement: AutoIncrement
|
||||
readonly durability: IDBTransactionDurability
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema constraint for table schemas that expose struct fields.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type AnySchemaStruct = Schema.Top & {
|
||||
readonly fields: Schema.Struct.Fields
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-erased shape of an `IndexedDbTable` used when table type parameters are not needed.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface Any {
|
||||
readonly [TypeId]: typeof TypeId
|
||||
readonly keyPath: any
|
||||
readonly tableName: string
|
||||
readonly tableSchema: Schema.Top
|
||||
readonly readSchema: Schema.Top
|
||||
readonly autoincrementSchema: Schema.Top
|
||||
readonly arraySchema: Schema.Top
|
||||
readonly autoIncrement: boolean
|
||||
readonly indexes: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-erased `IndexedDbTable` retaining the table interface properties with broad type parameters.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type AnyWithProps = IndexedDbTable<
|
||||
string,
|
||||
AnySchemaStruct,
|
||||
any,
|
||||
any,
|
||||
boolean
|
||||
>
|
||||
|
||||
/**
|
||||
* Extracts the table name type from an `IndexedDbTable`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type TableName<Table extends Any> = Table["tableName"]
|
||||
/**
|
||||
* Extracts the key-path type from an `IndexedDbTable`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type KeyPath<Table extends Any> = Table["keyPath"]
|
||||
|
||||
/**
|
||||
* Extracts the auto-increment flag type from an `IndexedDbTable`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type AutoIncrement<Table extends Any> = Table["autoIncrement"]
|
||||
|
||||
/**
|
||||
* Extracts the schema type from an `IndexedDbTable`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type TableSchema<Table extends Any> = Table["tableSchema"]
|
||||
/**
|
||||
* Extracts the decoding or encoding service requirements needed by an `IndexedDbTable` schema.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type Context<Table extends Any> =
|
||||
| Table["tableSchema"]["DecodingServices"]
|
||||
| Table["tableSchema"]["EncodingServices"]
|
||||
|
||||
/**
|
||||
* Extracts the encoded row type from an `IndexedDbTable` schema.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type Encoded<Table extends Any> = Table["tableSchema"]["Encoded"]
|
||||
|
||||
/**
|
||||
* Extracts the index definition map from an `IndexedDbTable`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type Indexes<Table extends Any> = Table["indexes"]
|
||||
|
||||
/**
|
||||
* Selects the table with the given name from a union of `IndexedDbTable` types.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type WithName<Table extends Any, TableName extends string> = Extract<
|
||||
Table,
|
||||
{ readonly tableName: TableName }
|
||||
>
|
||||
|
||||
const Proto = {
|
||||
[TypeId]: TypeId,
|
||||
pipe() {
|
||||
return pipeArguments(this, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a typed IndexedDB table definition from its name, schema, optional key path, indexes, auto-increment flag, and durability.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use to define a typed object-store descriptor for inclusion in an
|
||||
* `IndexedDbVersion` and for migration or query APIs.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* `autoIncrement` defaults to `false` and `durability` defaults to `"relaxed"`.
|
||||
* Tables without a key path get a read schema that includes an out-of-line
|
||||
* `key`, while auto-increment tables use a write schema where the generated key
|
||||
* may be omitted.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* Tables without a key path cannot define a `key` field in their row schema.
|
||||
* Key paths and index paths must point to encoded fields whose values are valid
|
||||
* IndexedDB keys, and declared indexes still need to be created during
|
||||
* database migrations.
|
||||
*
|
||||
* @see `IndexedDbVersion.make` for grouping table definitions into a schema version
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make = <
|
||||
const Name extends string,
|
||||
TableSchema extends AnySchemaStruct,
|
||||
const Indexes extends Record<
|
||||
string,
|
||||
IndexedDbQueryBuilder.KeyPath<TableSchema>
|
||||
>,
|
||||
const KeyPath extends
|
||||
| (AutoIncrement extends true ? IndexedDbQueryBuilder.KeyPathNumber<NoInfer<TableSchema>>
|
||||
: IndexedDbQueryBuilder.KeyPath<NoInfer<TableSchema>>)
|
||||
| undefined = undefined,
|
||||
const AutoIncrement extends boolean = false
|
||||
>(options: {
|
||||
readonly name: Name
|
||||
readonly schema: [KeyPath] extends [undefined]
|
||||
? "key" extends keyof TableSchema["fields"] ? "Cannot have a 'key' field when keyPath is undefined"
|
||||
: TableSchema
|
||||
: TableSchema
|
||||
readonly keyPath?: KeyPath
|
||||
readonly indexes?: Indexes | undefined
|
||||
readonly autoIncrement?: IsValidAutoIncrementKeyPath<
|
||||
TableSchema,
|
||||
KeyPath
|
||||
> extends true ? AutoIncrement | undefined
|
||||
: never
|
||||
readonly durability?: IDBTransactionDurability | undefined
|
||||
}): IndexedDbTable<
|
||||
Name,
|
||||
TableSchema,
|
||||
Indexes,
|
||||
Extract<KeyPath, Readonly<IDBValidKey | undefined>>,
|
||||
AutoIncrement
|
||||
> => {
|
||||
// oxlint-disable-next-line typescript/no-extraneous-class
|
||||
class Table {}
|
||||
Object.assign(Table, Proto)
|
||||
const readSchema = options.keyPath === undefined
|
||||
? Schema.Struct({
|
||||
...(options.schema as Schema.Struct<{}>).fields,
|
||||
key: IndexedDb.IDBValidKey
|
||||
})
|
||||
: options.schema
|
||||
;(Table as any).tableName = options.name
|
||||
;(Table as any).tableSchema = options.schema
|
||||
;(Table as any).readSchema = readSchema
|
||||
;(Table as any).arraySchema = Schema.Array(readSchema as any)
|
||||
;(Table as any).autoincrementSchema = options.autoIncrement
|
||||
? Schema.Struct(Struct.omit((options.schema as Schema.Struct<{}>).fields, [options.keyPath!] as any))
|
||||
: options.schema
|
||||
;(Table as any).keyPath = options.keyPath
|
||||
;(Table as any).indexes = options.indexes
|
||||
;(Table as any).autoIncrement = options.autoIncrement === true
|
||||
;(Table as any).durability = options.durability ?? "relaxed"
|
||||
return Table as any
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// internal
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type IsValidAutoIncrementKeyPath<
|
||||
TableSchema extends AnySchemaStruct,
|
||||
KeyPath
|
||||
> = KeyPath extends keyof TableSchema["Encoded"] ? TableSchema["Encoded"][KeyPath] extends number ? true
|
||||
: false
|
||||
: false
|
||||
138
repos/effect/packages/platform-browser/src/IndexedDbVersion.ts
Normal file
138
repos/effect/packages/platform-browser/src/IndexedDbVersion.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Typed IndexedDB schema version definitions.
|
||||
*
|
||||
* This module represents one logical IndexedDB database version as a non-empty set of `IndexedDbTable` definitions.
|
||||
* Versions are consumed by `IndexedDbDatabase.make` and `.add` to type query builders and migration transactions, so
|
||||
* applications can describe the tables available after initialization or after each schema upgrade.
|
||||
*
|
||||
* Use an `IndexedDbVersion` when defining the initial stores for a browser database, adding or removing object stores,
|
||||
* changing indexes, or moving data between differently shaped table schemas. The version value is a typed description of
|
||||
* the target schema; creating and deleting object stores or indexes still happens explicitly inside the corresponding
|
||||
* `IndexedDbDatabase` migration callback.
|
||||
*
|
||||
* IndexedDB versioning is ordered by the migration chain rather than by a number stored here. Each `.add` step becomes
|
||||
* the next browser database version, and only migrations after the browser's current version are run. Include every table
|
||||
* that should be queryable in each target version, avoid duplicate table names, and remember that key-path or
|
||||
* auto-increment changes usually require creating a new object store and copying data during the upgrade transaction.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type { NonEmptyReadonlyArray } from "effect/Array"
|
||||
import type { Pipeable } from "effect/Pipeable"
|
||||
import { pipeArguments } from "effect/Pipeable"
|
||||
import type * as IndexedDbTable from "./IndexedDbTable.ts"
|
||||
|
||||
const TypeId = "~@effect/platform-browser/IndexedDbVersion"
|
||||
|
||||
/**
|
||||
* Typed IndexedDB version definition containing the tables available in that schema version.
|
||||
*
|
||||
* @category interface
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface IndexedDbVersion<
|
||||
out Tables extends IndexedDbTable.AnyWithProps
|
||||
> extends Pipeable {
|
||||
new(_: never): {}
|
||||
readonly [TypeId]: typeof TypeId
|
||||
readonly tables: ReadonlyMap<string, Tables>
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-erased shape of an `IndexedDbVersion`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface Any {
|
||||
readonly [TypeId]: typeof TypeId
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-erased `IndexedDbVersion` retaining version properties with broad table types.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type AnyWithProps = IndexedDbVersion<IndexedDbTable.AnyWithProps>
|
||||
|
||||
/**
|
||||
* Extracts the table union from an `IndexedDbVersion`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type Tables<Db extends Any> = Db extends IndexedDbVersion<infer _Tables> ? _Tables : never
|
||||
|
||||
/**
|
||||
* Selects a table by name from an `IndexedDbVersion`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type TableWithName<
|
||||
Db extends Any,
|
||||
TableName extends string
|
||||
> = IndexedDbTable.WithName<Tables<Db>, TableName>
|
||||
|
||||
/**
|
||||
* Extracts the schema for a named table within an `IndexedDbVersion`.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type SchemaWithName<
|
||||
Db extends Any,
|
||||
TableName extends string
|
||||
> = IndexedDbTable.TableSchema<IndexedDbTable.WithName<Tables<Db>, TableName>>
|
||||
|
||||
const Proto = {
|
||||
[TypeId]: TypeId,
|
||||
pipe() {
|
||||
return pipeArguments(this, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
const makeProto = <Tables extends IndexedDbTable.AnyWithProps>(options: {
|
||||
readonly tables: ReadonlyMap<string, Tables>
|
||||
}): IndexedDbVersion<Tables> => {
|
||||
// oxlint-disable-next-line typescript/no-extraneous-class
|
||||
class Version {}
|
||||
Object.assign(Version, Proto)
|
||||
;(Version as any).tables = options.tables
|
||||
return Version as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an `IndexedDbVersion` from one or more table definitions.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need a typed description of the target IndexedDB schema that a
|
||||
* database migration will materialize.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* The returned version exposes a `tables` map keyed by each table's
|
||||
* `tableName`, and its type is the union of the supplied table definitions.
|
||||
*
|
||||
* **Gotchas**
|
||||
*
|
||||
* This constructor only describes the target schema; object stores and indexes
|
||||
* still need to be created in the corresponding `IndexedDbDatabase` migration.
|
||||
* Duplicate table names are not rejected, and the runtime map keeps the later
|
||||
* table for a repeated key.
|
||||
*
|
||||
* @see {@link IndexedDbTable.make} for creating table definitions consumed by this constructor
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make = <
|
||||
const Tables extends NonEmptyReadonlyArray<IndexedDbTable.AnyWithProps>
|
||||
>(
|
||||
...tables: Tables
|
||||
): IndexedDbVersion<Tables[number]> =>
|
||||
makeProto({
|
||||
tables: new Map(tables.map((table) => [table.tableName, table]))
|
||||
})
|
||||
146
repos/effect/packages/platform-browser/src/Permissions.ts
Normal file
146
repos/effect/packages/platform-browser/src/Permissions.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Effect service for the browser Permissions API.
|
||||
*
|
||||
* This module defines a `Permissions` service backed by
|
||||
* `navigator.permissions`. The service exposes `query`, which returns the
|
||||
* browser `PermissionStatus` for a permission name. Failed browser operations
|
||||
* are represented as `PermissionsError` values with `InvalidStateError` or
|
||||
* `TypeError` reasons, and `layer` provides the browser-backed service.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Context from "effect/Context"
|
||||
import * as Data from "effect/Data"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
|
||||
const TypeId = "~@effect/platform-browser/Permissions"
|
||||
const ErrorTypeId = "~@effect/platform-browser/Permissions/PermissionsError"
|
||||
|
||||
/**
|
||||
* Wrapper on the Permission API (`navigator.permissions`) with methods for
|
||||
* querying status of permissions.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface Permissions {
|
||||
readonly [TypeId]: typeof TypeId
|
||||
|
||||
/**
|
||||
* Returns the state of a user permission on the global scope.
|
||||
*/
|
||||
readonly query: <Name extends PermissionName>(
|
||||
name: Name
|
||||
) => Effect.Effect<
|
||||
// `name` is identical to the name passed to Permissions.query
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/PermissionStatus
|
||||
Omit<PermissionStatus, "name"> & { name: Name },
|
||||
PermissionsError
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
* Error reason for an `InvalidStateError` raised by the browser Permissions API.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class PermissionsInvalidStateError extends Data.TaggedError("InvalidStateError")<{
|
||||
readonly cause: unknown
|
||||
}> {
|
||||
override get message(): string {
|
||||
return this._tag
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error reason for a `TypeError` raised by the browser Permissions API.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class PermissionsTypeError extends Data.TaggedError("TypeError")<{
|
||||
readonly cause: unknown
|
||||
}> {
|
||||
override get message(): string {
|
||||
return this._tag
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Union of browser Permissions API error reasons represented by the service.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export type PermissionsErrorReason = PermissionsInvalidStateError | PermissionsTypeError
|
||||
|
||||
/**
|
||||
* Tagged error wrapping a browser Permissions API failure reason.
|
||||
*
|
||||
* @category errors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class PermissionsError extends Data.TaggedError("PermissionsError")<{
|
||||
readonly reason: PermissionsErrorReason
|
||||
}> {
|
||||
constructor(props: { readonly reason: PermissionsErrorReason }) {
|
||||
super({
|
||||
...props,
|
||||
cause: props.reason.cause
|
||||
} as any)
|
||||
}
|
||||
|
||||
readonly [ErrorTypeId] = ErrorTypeId
|
||||
|
||||
override get message(): string {
|
||||
return this.reason.message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Service tag for browser permission querying.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need to require or provide browser permission querying through
|
||||
* Effect's context.
|
||||
*
|
||||
* @category services
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const Permissions: Context.Service<Permissions, Permissions> = Context.Service<Permissions>(TypeId)
|
||||
|
||||
/**
|
||||
* Provides the `Permissions` service using the browser `navigator.permissions` API.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use when you need a live browser `Permissions` service backed by the ambient
|
||||
* `navigator.permissions` implementation.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* `query` delegates to `navigator.permissions.query({ name })` and wraps
|
||||
* rejected browser operations in `PermissionsError`.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<Permissions> = Layer.succeed(
|
||||
Permissions,
|
||||
Permissions.of({
|
||||
[TypeId]: TypeId,
|
||||
query: (name) =>
|
||||
Effect.tryPromise({
|
||||
try: () => navigator.permissions.query({ name }) as Promise<any>,
|
||||
catch: (cause) =>
|
||||
new PermissionsError({
|
||||
reason: cause instanceof DOMException
|
||||
? new PermissionsInvalidStateError({ cause })
|
||||
: new PermissionsTypeError({ cause })
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
90
repos/effect/packages/platform-browser/src/index.ts
Normal file
90
repos/effect/packages/platform-browser/src/index.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
|
||||
// @barrel: Auto-generated exports. Do not edit manually.
|
||||
|
||||
/**
|
||||
* @since 1.0.0
|
||||
*/
|
||||
export * as BrowserCrypto from "./BrowserCrypto.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserHttpClient from "./BrowserHttpClient.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserKeyValueStore from "./BrowserKeyValueStore.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserPersistence from "./BrowserPersistence.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserRuntime from "./BrowserRuntime.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserSocket from "./BrowserSocket.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserStream from "./BrowserStream.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserWorker from "./BrowserWorker.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as BrowserWorkerRunner from "./BrowserWorkerRunner.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as Clipboard from "./Clipboard.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as Geolocation from "./Geolocation.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as IndexedDb from "./IndexedDb.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as IndexedDbDatabase from "./IndexedDbDatabase.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as IndexedDbQueryBuilder from "./IndexedDbQueryBuilder.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as IndexedDbTable from "./IndexedDbTable.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as IndexedDbVersion from "./IndexedDbVersion.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as Permissions from "./Permissions.ts"
|
||||
@@ -0,0 +1,74 @@
|
||||
import * as BrowserCrypto from "@effect/platform-browser/BrowserCrypto"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Layer } from "effect"
|
||||
import * as Crypto from "effect/Crypto"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as TestClock from "effect/testing/TestClock"
|
||||
|
||||
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
||||
const uuidV7Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
||||
|
||||
const getRandomValues = <T extends ArrayBufferView | null>(array: T): T => {
|
||||
if (array instanceof Uint8Array) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
array[i] = i & 0xff
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
describe("BrowserCrypto", () => {
|
||||
it.effect("generates UUIDv4 values from getRandomValues", () =>
|
||||
Effect.gen(function*() {
|
||||
const crypto = yield* Crypto.Crypto
|
||||
const uuid = yield* crypto.randomUUIDv4
|
||||
assert.strictEqual(uuid, "00010203-0405-4607-8809-0a0b0c0d0e0f")
|
||||
assert.match(uuid, uuidV4Regex)
|
||||
}).pipe(Effect.provide(BrowserCrypto.layer.pipe(
|
||||
Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, {
|
||||
...crypto,
|
||||
getRandomValues(array) {
|
||||
return getRandomValues(array)
|
||||
}
|
||||
}))
|
||||
))))
|
||||
|
||||
it.effect("generates UUIDv7 values from getRandomValues and the Clock", () =>
|
||||
Effect.gen(function*() {
|
||||
yield* TestClock.setTime(0x0123456789ab)
|
||||
const crypto = yield* Crypto.Crypto
|
||||
const uuid = yield* crypto.randomUUIDv7
|
||||
assert.strictEqual(uuid, "01234567-89ab-7607-8809-0a0b0c0d0e0f")
|
||||
assert.match(uuid, uuidV7Regex)
|
||||
}).pipe(Effect.provide(BrowserCrypto.layer.pipe(
|
||||
Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, {
|
||||
...crypto,
|
||||
getRandomValues(array) {
|
||||
return getRandomValues(array)
|
||||
}
|
||||
}))
|
||||
))))
|
||||
|
||||
it.effect("computes digests with subtle crypto", () => {
|
||||
const buffer = new ArrayBuffer(3)
|
||||
new Uint8Array(buffer).set([1, 2, 3])
|
||||
|
||||
return Effect.gen(function*() {
|
||||
const crypto = yield* Crypto.Crypto
|
||||
const digest = yield* crypto.digest("SHA-256", new Uint8Array(buffer))
|
||||
assert.deepStrictEqual(digest, new Uint8Array([1, 2, 3]))
|
||||
}).pipe(
|
||||
Effect.provide(BrowserCrypto.layer.pipe(
|
||||
Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, {
|
||||
...crypto,
|
||||
subtle: {
|
||||
...crypto.subtle,
|
||||
digest() {
|
||||
return Promise.resolve(buffer)
|
||||
}
|
||||
}
|
||||
}))
|
||||
))
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as BrowserHttpClient from "@effect/platform-browser/BrowserHttpClient"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Stream from "effect/Stream"
|
||||
import * as Cookies from "effect/unstable/http/Cookies"
|
||||
import * as HttpClient from "effect/unstable/http/HttpClient"
|
||||
import * as MXHR from "mock-xmlhttprequest"
|
||||
|
||||
const layer = (...args: Parameters<typeof MXHR.newServer>) =>
|
||||
Layer.unwrap(Effect.sync(() => {
|
||||
const server = MXHR.newServer(...args)
|
||||
return BrowserHttpClient.layerXMLHttpRequest.pipe(
|
||||
Layer.provide(Layer.succeed(BrowserHttpClient.XMLHttpRequest, server.xhrFactory))
|
||||
)
|
||||
}))
|
||||
|
||||
describe("BrowserHttpClient", () => {
|
||||
it.effect("json", () =>
|
||||
Effect.gen(function*() {
|
||||
const body = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
|
||||
Effect.flatMap((_) => _.json)
|
||||
)
|
||||
assert.deepStrictEqual(body, { message: "Success!" })
|
||||
}).pipe(Effect.provide(layer({
|
||||
get: ["http://localhost:8080/my/url", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: "{ \"message\": \"Success!\" }"
|
||||
}]
|
||||
}))))
|
||||
|
||||
it.effect("stream", () =>
|
||||
Effect.gen(function*() {
|
||||
const body = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
|
||||
Effect.flatMap((response) =>
|
||||
response.stream.pipe(
|
||||
Stream.decodeText(),
|
||||
Stream.mkString
|
||||
)
|
||||
)
|
||||
)
|
||||
assert.deepStrictEqual(body, "{ \"message\": \"Success!\" }")
|
||||
}).pipe(Effect.provide(layer({
|
||||
get: ["http://localhost:8080/my/url", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: "{ \"message\": \"Success!\" }"
|
||||
}]
|
||||
}))))
|
||||
|
||||
it.effect("cookies", () =>
|
||||
Effect.gen(function*() {
|
||||
const cookies = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
|
||||
Effect.map((response) => response.cookies)
|
||||
)
|
||||
assert.deepStrictEqual(Cookies.toRecord(cookies), {
|
||||
foo: "bar"
|
||||
})
|
||||
}).pipe(Effect.provide(layer({
|
||||
get: ["http://localhost:8080/my/url", {
|
||||
headers: { "Content-Type": "application/json", "Set-Cookie": "foo=bar; HttpOnly; Secure" },
|
||||
body: "{ \"message\": \"Success!\" }"
|
||||
}]
|
||||
}))))
|
||||
|
||||
it.effect("arrayBuffer", () =>
|
||||
Effect.gen(function*() {
|
||||
const body = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
|
||||
Effect.flatMap((response) => response.arrayBuffer),
|
||||
BrowserHttpClient.withXHRArrayBuffer
|
||||
)
|
||||
assert.strictEqual(new TextDecoder().decode(body), "{ \"message\": \"Success!\" }")
|
||||
}).pipe(Effect.provide(layer({
|
||||
get: ["http://localhost:8080/my/url", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: "{ \"message\": \"Success!\" }"
|
||||
}]
|
||||
}))))
|
||||
|
||||
it.effect("arrayBuffer without withXHRArrayBuffer", () =>
|
||||
Effect.gen(function*() {
|
||||
const body = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
|
||||
Effect.flatMap((response) => response.arrayBuffer)
|
||||
)
|
||||
assert.strictEqual(new TextDecoder().decode(body), "{ \"message\": \"Success!\" }")
|
||||
}).pipe(Effect.provide(layer({
|
||||
get: ["http://localhost:8080/my/url", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: "{ \"message\": \"Success!\" }"
|
||||
}]
|
||||
}))))
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as BrowserKeyValueStore from "@effect/platform-browser/BrowserKeyValueStore"
|
||||
import * as IndexedDb from "@effect/platform-browser/IndexedDb"
|
||||
import { describe } from "@effect/vitest"
|
||||
import { Layer } from "effect"
|
||||
import { testLayer } from "effect-test/unstable/persistence/KeyValueStore.test"
|
||||
import { IDBKeyRange, indexedDB } from "fake-indexeddb"
|
||||
|
||||
describe("KeyValueStore / layerLocalStorage", () => testLayer(BrowserKeyValueStore.layerLocalStorage))
|
||||
|
||||
describe("KeyValueStore / layerSessionStorage", () => testLayer(BrowserKeyValueStore.layerSessionStorage))
|
||||
|
||||
describe("KeyValueStore / layerIndexedDb", () => {
|
||||
const layerFakeIndexedDb = Layer.succeed(
|
||||
IndexedDb.IndexedDb,
|
||||
IndexedDb.make({ indexedDB, IDBKeyRange })
|
||||
)
|
||||
|
||||
testLayer(
|
||||
BrowserKeyValueStore.layerIndexedDb({ database: "kvs_test_db" }).pipe(
|
||||
Layer.provide(layerFakeIndexedDb)
|
||||
)
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,209 @@
|
||||
import * as BrowserPersistence from "@effect/platform-browser/BrowserPersistence"
|
||||
import { afterEach, assert, beforeEach, describe, it } from "@effect/vitest"
|
||||
import * as Duration from "effect/Duration"
|
||||
import * as Effect from "effect/Effect"
|
||||
import { TestClock } from "effect/testing"
|
||||
import * as Persistence from "effect/unstable/persistence/Persistence"
|
||||
import { indexedDB as fakeIndexedDb } from "fake-indexeddb"
|
||||
|
||||
const defaultDatabase = "effect_persistence"
|
||||
const customDatabase = "effect_persistence_custom"
|
||||
|
||||
interface EntryRow {
|
||||
readonly storeId: string
|
||||
readonly id: string
|
||||
readonly value: object
|
||||
readonly expires: number | null
|
||||
}
|
||||
|
||||
let previousIndexedDb: unknown
|
||||
|
||||
beforeEach(() => {
|
||||
previousIndexedDb = Reflect.get(globalThis, "indexedDB")
|
||||
Reflect.set(globalThis, "indexedDB", fakeIndexedDb)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fakeIndexedDb.deleteDatabase(defaultDatabase)
|
||||
fakeIndexedDb.deleteDatabase(customDatabase)
|
||||
if (previousIndexedDb === undefined) {
|
||||
Reflect.deleteProperty(globalThis, "indexedDB")
|
||||
return
|
||||
}
|
||||
Reflect.set(globalThis, "indexedDB", previousIndexedDb)
|
||||
})
|
||||
|
||||
describe.sequential("BrowserPersistence", () => {
|
||||
it.effect("set + get", () =>
|
||||
Effect.gen(function*() {
|
||||
const backing = yield* Persistence.BackingPersistence
|
||||
const store = yield* backing.make("users")
|
||||
|
||||
yield* store.set("key1", { name: "Alice" }, undefined)
|
||||
|
||||
const value = yield* store.get("key1")
|
||||
assert.deepStrictEqual(value, { name: "Alice" })
|
||||
}).pipe(
|
||||
Effect.provide(BrowserPersistence.layerBackingIndexedDb())
|
||||
))
|
||||
|
||||
it.effect("setMany + getMany preserves order, missing keys, and duplicates", () =>
|
||||
Effect.gen(function*() {
|
||||
const backing = yield* Persistence.BackingPersistence
|
||||
const store = yield* backing.make("users")
|
||||
|
||||
yield* store.setMany([
|
||||
["key1", { name: "Alice" }, undefined],
|
||||
["key2", { name: "Bob" }, undefined],
|
||||
["key3", { name: "Charlie" }, undefined]
|
||||
])
|
||||
|
||||
const values = yield* store.getMany(["key3", "missing", "key1", "key1", "key2"])
|
||||
assert.deepStrictEqual(values, [
|
||||
{ name: "Charlie" },
|
||||
undefined,
|
||||
{ name: "Alice" },
|
||||
{ name: "Alice" },
|
||||
{ name: "Bob" }
|
||||
])
|
||||
}).pipe(
|
||||
Effect.provide(BrowserPersistence.layerBackingIndexedDb())
|
||||
))
|
||||
|
||||
it.effect("remove", () =>
|
||||
Effect.gen(function*() {
|
||||
const backing = yield* Persistence.BackingPersistence
|
||||
const store = yield* backing.make("users")
|
||||
|
||||
yield* store.setMany([
|
||||
["key1", { name: "Alice" }, undefined],
|
||||
["key2", { name: "Bob" }, undefined]
|
||||
])
|
||||
|
||||
yield* store.remove("key1")
|
||||
|
||||
const values = yield* store.getMany(["key1", "key2"])
|
||||
assert.deepStrictEqual(values, [undefined, { name: "Bob" }])
|
||||
}).pipe(
|
||||
Effect.provide(BrowserPersistence.layerBackingIndexedDb())
|
||||
))
|
||||
|
||||
it.effect("clear only affects the current storeId", () =>
|
||||
Effect.gen(function*() {
|
||||
const backing = yield* Persistence.BackingPersistence
|
||||
const storeA = yield* backing.make("store-a")
|
||||
const storeB = yield* backing.make("store-b")
|
||||
|
||||
yield* storeA.set("shared", { owner: "A" }, undefined)
|
||||
yield* storeA.set("only-a", { owner: "A" }, undefined)
|
||||
yield* storeB.set("shared", { owner: "B" }, undefined)
|
||||
|
||||
yield* storeA.clear
|
||||
|
||||
assert.deepStrictEqual(yield* storeA.get("shared"), undefined)
|
||||
assert.deepStrictEqual(yield* storeA.get("only-a"), undefined)
|
||||
assert.deepStrictEqual(yield* storeB.get("shared"), { owner: "B" })
|
||||
}).pipe(
|
||||
Effect.provide(BrowserPersistence.layerBackingIndexedDb())
|
||||
))
|
||||
|
||||
it.effect("TTL expiry performs lazy deletion", () =>
|
||||
Effect.gen(function*() {
|
||||
const backing = yield* Persistence.BackingPersistence
|
||||
const store = yield* backing.make("users")
|
||||
|
||||
yield* store.set("ttl-key", { name: "expiring" }, Duration.seconds(10))
|
||||
|
||||
const rowBefore = yield* getRawEntry(defaultDatabase, "users", "ttl-key").pipe(Effect.orDie)
|
||||
assert.isTrue(rowBefore !== undefined)
|
||||
|
||||
yield* TestClock.adjust(Duration.seconds(11))
|
||||
|
||||
const expired = yield* store.get("ttl-key")
|
||||
assert.deepStrictEqual(expired, undefined)
|
||||
|
||||
const rowAfter = yield* getRawEntry(defaultDatabase, "users", "ttl-key").pipe(Effect.orDie)
|
||||
assert.deepStrictEqual(rowAfter, undefined)
|
||||
}).pipe(
|
||||
Effect.provide(BrowserPersistence.layerBackingIndexedDb())
|
||||
))
|
||||
|
||||
it.effect("store isolation across storeIds", () =>
|
||||
Effect.gen(function*() {
|
||||
const backing = yield* Persistence.BackingPersistence
|
||||
const storeA = yield* backing.make("store-a")
|
||||
const storeB = yield* backing.make("store-b")
|
||||
|
||||
yield* storeA.set("same-key", { value: "A" }, undefined)
|
||||
yield* storeB.set("same-key", { value: "B" }, undefined)
|
||||
|
||||
yield* storeA.remove("same-key")
|
||||
|
||||
assert.deepStrictEqual(yield* storeA.get("same-key"), undefined)
|
||||
assert.deepStrictEqual(yield* storeB.get("same-key"), { value: "B" })
|
||||
}).pipe(
|
||||
Effect.provide(BrowserPersistence.layerBackingIndexedDb())
|
||||
))
|
||||
|
||||
it.effect("custom database option", () =>
|
||||
Effect.gen(function*() {
|
||||
yield* withStore("users", undefined, (store) => store.set("shared", { database: "default" }, undefined))
|
||||
|
||||
yield* withStore("users", { database: customDatabase }, (store) =>
|
||||
store.set("shared", { database: "custom" }, undefined))
|
||||
|
||||
const fromDefault = yield* withStore("users", undefined, (store) =>
|
||||
store.get("shared"))
|
||||
const fromCustom = yield* withStore("users", { database: customDatabase }, (store) =>
|
||||
store.get("shared"))
|
||||
|
||||
assert.deepStrictEqual(fromDefault, { database: "default" })
|
||||
assert.deepStrictEqual(fromCustom, { database: "custom" })
|
||||
}))
|
||||
})
|
||||
|
||||
const withStore = <A>(
|
||||
storeId: string,
|
||||
options: {
|
||||
readonly database?: string | undefined
|
||||
} | undefined,
|
||||
f: (store: Persistence.BackingPersistenceStore) => Effect.Effect<A, Persistence.PersistenceError>
|
||||
) =>
|
||||
Effect.gen(function*() {
|
||||
const backing = yield* Persistence.BackingPersistence
|
||||
const store = yield* backing.make(storeId)
|
||||
return yield* f(store)
|
||||
}).pipe(Effect.provide(BrowserPersistence.layerBackingIndexedDb(options)))
|
||||
|
||||
const withDatabase = <A>(
|
||||
database: string,
|
||||
f: (db: IDBDatabase) => Effect.Effect<A, unknown>
|
||||
): Effect.Effect<A, unknown> =>
|
||||
Effect.acquireUseRelease(
|
||||
idbRequest(() => globalThis.indexedDB.open(database, 1)),
|
||||
f,
|
||||
(db) => Effect.sync(() => db.close())
|
||||
)
|
||||
|
||||
const getRawEntry = (database: string, storeId: string, id: string): Effect.Effect<EntryRow | undefined, unknown> =>
|
||||
withDatabase(
|
||||
database,
|
||||
(db) => idbRequest(() => db.transaction("entries", "readonly").objectStore("entries").get([storeId, id]))
|
||||
)
|
||||
|
||||
const idbRequest = <A>(evaluate: () => IDBRequest<A>): Effect.Effect<A, unknown> =>
|
||||
Effect.flatMap(
|
||||
Effect.try({
|
||||
try: evaluate,
|
||||
catch: (cause) => cause
|
||||
}),
|
||||
(request) =>
|
||||
Effect.callback<A, unknown>((resume) => {
|
||||
if (request.readyState === "done") {
|
||||
resume(Effect.succeed(request.result))
|
||||
return
|
||||
}
|
||||
request.onsuccess = () => resume(Effect.succeed(request.result))
|
||||
request.onerror = () => resume(Effect.fail(request.error))
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as BrowserPersistence from "@effect/platform-browser/BrowserPersistence"
|
||||
import { afterEach, beforeEach, describe } from "@effect/vitest"
|
||||
import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest"
|
||||
import { indexedDB as fakeIndexedDb } from "fake-indexeddb"
|
||||
|
||||
const database = "effect_persistence_integration"
|
||||
|
||||
let previousIndexedDb: unknown
|
||||
|
||||
beforeEach(() => {
|
||||
previousIndexedDb = Reflect.get(globalThis, "indexedDB")
|
||||
Reflect.set(globalThis, "indexedDB", fakeIndexedDb)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fakeIndexedDb.deleteDatabase(database)
|
||||
if (previousIndexedDb === undefined) {
|
||||
Reflect.deleteProperty(globalThis, "indexedDB")
|
||||
return
|
||||
}
|
||||
Reflect.set(globalThis, "indexedDB", previousIndexedDb)
|
||||
})
|
||||
|
||||
describe.sequential("BrowserPersistence / PersistedCache", () => {
|
||||
PersistedCacheTest.suite(
|
||||
"browser-indexeddb",
|
||||
BrowserPersistence.layerIndexedDb({ database })
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,253 @@
|
||||
import { IndexedDb, IndexedDbDatabase, IndexedDbTable, IndexedDbVersion } from "@effect/platform-browser"
|
||||
import { afterEach, assert, describe, it } from "@effect/vitest"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { IDBKeyRange, indexedDB } from "fake-indexeddb"
|
||||
|
||||
const databaseName = "db"
|
||||
|
||||
const layerFakeIndexedDb = Layer.succeed(
|
||||
IndexedDb.IndexedDb,
|
||||
IndexedDb.make({ indexedDB, IDBKeyRange })
|
||||
)
|
||||
|
||||
const provideMigration = (database: IndexedDbDatabase.Any) =>
|
||||
Effect.provide(
|
||||
database.layer(databaseName).pipe(Layer.provide(layerFakeIndexedDb))
|
||||
)
|
||||
|
||||
afterEach(() => {
|
||||
indexedDB.deleteDatabase(databaseName)
|
||||
})
|
||||
|
||||
describe.sequential("IndexedDbDatabase", () => {
|
||||
it.effect("insert and read todos", () => {
|
||||
const Table = IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id",
|
||||
indexes: { titleIndex: "title" }
|
||||
})
|
||||
|
||||
const V1 = IndexedDbVersion.make(Table)
|
||||
|
||||
class Db extends IndexedDbDatabase.make(V1, (api) =>
|
||||
Effect.gen(function*() {
|
||||
yield* api.createObjectStore("todo")
|
||||
yield* api.createIndex("todo", "titleIndex")
|
||||
yield* api
|
||||
.from("todo")
|
||||
.insert({ id: 1, title: "test", completed: false })
|
||||
}))
|
||||
{}
|
||||
|
||||
return Effect.gen(function*() {
|
||||
const db = yield* IndexedDbDatabase.IndexedDbDatabase
|
||||
const api = yield* Db.getQueryBuilder
|
||||
let todo = yield* api.from("todo").select()
|
||||
|
||||
const name = yield* api.use((database) => database.name)
|
||||
const version = yield* api.use((database) => database.version)
|
||||
const objectStoreNames = yield* api.use(
|
||||
(database) => database.objectStoreNames
|
||||
)
|
||||
const indexNames = yield* api.use(
|
||||
(database) => database.transaction("todo").objectStore("todo").indexNames
|
||||
)
|
||||
const index = yield* api.use((database) => database.transaction("todo").objectStore("todo").index("titleIndex"))
|
||||
|
||||
assert.equal(name, "db")
|
||||
assert.equal(version, 1)
|
||||
assert.deepStrictEqual(todo, [
|
||||
{ id: 1, title: "test", completed: false }
|
||||
])
|
||||
assert.deepStrictEqual(Array.from(objectStoreNames), ["todo"])
|
||||
assert.deepStrictEqual(Array.from(indexNames), ["titleIndex"])
|
||||
assert.deepStrictEqual(index.keyPath, "title")
|
||||
|
||||
yield* api.from("todo").insert({ id: 2, title: "test2", completed: false })
|
||||
|
||||
yield* db.rebuild
|
||||
todo = yield* api.from("todo").select()
|
||||
assert.deepStrictEqual(todo, [
|
||||
{ id: 1, title: "test", completed: false }
|
||||
])
|
||||
}).pipe(provideMigration(Db))
|
||||
})
|
||||
|
||||
it.effect("transaction", () => {
|
||||
const Table = IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id",
|
||||
indexes: { titleIndex: "title" }
|
||||
})
|
||||
|
||||
const Table2 = IndexedDbTable.make({
|
||||
name: "user",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
name: Schema.String
|
||||
}),
|
||||
keyPath: "id"
|
||||
})
|
||||
|
||||
const Db = IndexedDbVersion.make(Table, Table2)
|
||||
|
||||
class Migration extends IndexedDbDatabase.make(Db, (api) =>
|
||||
Effect.gen(function*() {
|
||||
yield* api.createObjectStore("todo")
|
||||
yield* api.createIndex("todo", "titleIndex")
|
||||
yield* api
|
||||
.from("todo")
|
||||
.insert({ id: 1, title: "test", completed: false })
|
||||
}))
|
||||
{}
|
||||
|
||||
return Effect.gen(function*() {
|
||||
const api = yield* Migration.getQueryBuilder
|
||||
yield* api.withTransaction({ tables: ["todo"], mode: "readwrite" })(
|
||||
api.from("todo").insert({ id: 2, title: "test2", completed: false })
|
||||
)
|
||||
|
||||
const todo = yield* api.from("todo").select()
|
||||
assert.deepStrictEqual(todo, [
|
||||
{ id: 1, title: "test", completed: false },
|
||||
{
|
||||
id: 2,
|
||||
title: "test2",
|
||||
completed: false
|
||||
}
|
||||
])
|
||||
}).pipe(provideMigration(Migration))
|
||||
})
|
||||
|
||||
it.effect("migration sequence", () => {
|
||||
const Table1 = IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id",
|
||||
indexes: { titleIndex: "title" }
|
||||
})
|
||||
|
||||
const Table2 = IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
uuid: Schema.String.check(Schema.isUUID(4)),
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "uuid"
|
||||
})
|
||||
|
||||
const V1 = IndexedDbVersion.make(Table1)
|
||||
const V2 = IndexedDbVersion.make(Table2)
|
||||
const uuid = "9535a059-a61f-42e1-a2e0-35ec87203c24"
|
||||
|
||||
class Db extends IndexedDbDatabase.make(V1, (api) =>
|
||||
Effect.gen(function*() {
|
||||
yield* api.createObjectStore("todo")
|
||||
yield* api.createIndex("todo", "titleIndex")
|
||||
yield* api
|
||||
.from("todo")
|
||||
.insert({ id: 1, title: "test", completed: false })
|
||||
})).add(V2, (from, to) =>
|
||||
Effect.gen(function*() {
|
||||
const todo = yield* from.from("todo").select()
|
||||
yield* from.deleteIndex("todo", "titleIndex")
|
||||
yield* from.deleteObjectStore("todo")
|
||||
yield* to.createObjectStore("todo")
|
||||
yield* to.from("todo").insertAll(
|
||||
todo.map((t) => ({
|
||||
uuid,
|
||||
title: t.title,
|
||||
completed: t.completed
|
||||
}))
|
||||
)
|
||||
}))
|
||||
{}
|
||||
|
||||
return Effect.gen(function*() {
|
||||
const api = yield* Db.getQueryBuilder
|
||||
const todo = yield* api.from("todo").select()
|
||||
const name = yield* api.use((database) => database.name)
|
||||
const version = yield* api.use((database) => database.version)
|
||||
const objectStoreNames = yield* api.use(
|
||||
(database) => database.objectStoreNames
|
||||
)
|
||||
const indexNames = yield* api.use(
|
||||
(database) => database.transaction("todo").objectStore("todo").indexNames
|
||||
)
|
||||
|
||||
assert.equal(name, "db")
|
||||
assert.equal(version, 2)
|
||||
assert.deepStrictEqual(todo, [{ uuid, title: "test", completed: false }])
|
||||
assert.deepStrictEqual(Array.from(objectStoreNames), ["todo"])
|
||||
assert.deepStrictEqual(Array.from(indexNames), [])
|
||||
}).pipe(provideMigration(Db))
|
||||
})
|
||||
|
||||
it.effect("delete object store migration", () => {
|
||||
const Table1 = IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id"
|
||||
})
|
||||
|
||||
const Table2 = IndexedDbTable.make({
|
||||
name: "user",
|
||||
schema: Schema.Struct({
|
||||
userId: Schema.Number,
|
||||
name: Schema.String,
|
||||
email: Schema.String
|
||||
}),
|
||||
keyPath: "userId"
|
||||
})
|
||||
|
||||
const V1 = IndexedDbVersion.make(Table1)
|
||||
const V2 = IndexedDbVersion.make(Table2, Table1)
|
||||
|
||||
class Migration extends IndexedDbDatabase.make(V1, (api) => api.createObjectStore("todo")).add(
|
||||
V2,
|
||||
Effect.fnUntraced(function*(from, to) {
|
||||
yield* from.deleteObjectStore("todo")
|
||||
yield* to.createObjectStore("user")
|
||||
yield* to.from("user").insert({
|
||||
userId: 1,
|
||||
name: "John Doe",
|
||||
email: "john.doe@example.com"
|
||||
})
|
||||
})
|
||||
) {}
|
||||
|
||||
return Effect.gen(function*() {
|
||||
const api = yield* Migration.getQueryBuilder
|
||||
const user = yield* api.from("user").select()
|
||||
|
||||
const name = yield* api.use((database) => database.name)
|
||||
const version = yield* api.use((database) => database.version)
|
||||
const objectStoreNames = yield* api.use((database) => database.objectStoreNames)
|
||||
assert.equal(name, "db")
|
||||
assert.equal(version, 2)
|
||||
assert.deepStrictEqual(user, [
|
||||
{ userId: 1, name: "John Doe", email: "john.doe@example.com" }
|
||||
])
|
||||
assert.deepStrictEqual(Array.from(objectStoreNames), ["user"])
|
||||
}).pipe(provideMigration(Migration))
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
import { IndexedDb, IndexedDbTable } from "@effect/platform-browser"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Schema } from "effect"
|
||||
|
||||
describe("IndexedDbTable", () => {
|
||||
it("make", () => {
|
||||
class Table extends IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id"
|
||||
}) {}
|
||||
|
||||
assert.equal(Table.tableName, "todo")
|
||||
assert.equal(Table.autoIncrement, false)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.id, Schema.Number)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.title, Schema.String)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.completed, Schema.Boolean)
|
||||
assert.equal(Table.keyPath, "id")
|
||||
})
|
||||
|
||||
it("autoIncrement", () => {
|
||||
class Table1 extends IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: IndexedDb.AutoIncrement,
|
||||
title: Schema.String
|
||||
}),
|
||||
keyPath: "id",
|
||||
autoIncrement: true
|
||||
}) {}
|
||||
|
||||
class Table2 extends IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: IndexedDb.AutoIncrement,
|
||||
title: Schema.String
|
||||
}),
|
||||
keyPath: "id",
|
||||
autoIncrement: false
|
||||
}) {}
|
||||
|
||||
assert.equal(Table1.tableName, "todo")
|
||||
assert.equal(Table1.autoIncrement, true)
|
||||
assert.deepStrictEqual(
|
||||
Table1.tableSchema.fields.id,
|
||||
IndexedDb.AutoIncrement
|
||||
)
|
||||
assert.deepStrictEqual(Table1.tableSchema.fields.title, Schema.String)
|
||||
assert.equal(Table1.keyPath, "id")
|
||||
|
||||
assert.equal(Table2.tableName, "todo")
|
||||
assert.equal(Table2.autoIncrement, false)
|
||||
assert.deepStrictEqual(
|
||||
Table2.tableSchema.fields.id,
|
||||
IndexedDb.AutoIncrement
|
||||
)
|
||||
assert.deepStrictEqual(Table2.tableSchema.fields.title, Schema.String)
|
||||
assert.equal(Table2.keyPath, "id")
|
||||
})
|
||||
|
||||
it("multiple keyPath", () => {
|
||||
const Table = IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: ["id", "title"]
|
||||
})
|
||||
|
||||
assert.equal(Table.tableName, "todo")
|
||||
assert.equal(Table.autoIncrement, false)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.id, Schema.Number)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.title, Schema.String)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.completed, Schema.Boolean)
|
||||
assert.deepStrictEqual(Table.keyPath, ["id", "title"])
|
||||
})
|
||||
|
||||
it("no keyPath", () => {
|
||||
const Table = IndexedDbTable.make({
|
||||
name: "todo",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
})
|
||||
})
|
||||
|
||||
assert.equal(Table.tableName, "todo")
|
||||
assert.equal(Table.autoIncrement, false)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.id, Schema.Number)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.title, Schema.String)
|
||||
assert.deepStrictEqual(Table.tableSchema.fields.completed, Schema.Boolean)
|
||||
assert.deepStrictEqual(Table.keyPath, undefined)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
import { IndexedDbTable, IndexedDbVersion } from "@effect/platform-browser"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Schema } from "effect"
|
||||
|
||||
describe("IndexedDbVersion", () => {
|
||||
it("make single table", () => {
|
||||
class Table1 extends IndexedDbTable.make({
|
||||
name: "todo1",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id"
|
||||
}) {}
|
||||
|
||||
class Db extends IndexedDbVersion.make(Table1) {}
|
||||
|
||||
assert.equal(Db.tables.size, 1)
|
||||
assert.equal(Db.tables.get("todo1"), Table1)
|
||||
})
|
||||
|
||||
it("make multiple tables", () => {
|
||||
class Table1 extends IndexedDbTable.make({
|
||||
name: "todo1",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id"
|
||||
}) {}
|
||||
|
||||
class Table2 extends IndexedDbTable.make({
|
||||
name: "todo2",
|
||||
schema: Schema.Struct({
|
||||
id: Schema.Number,
|
||||
title: Schema.String,
|
||||
completed: Schema.Boolean
|
||||
}),
|
||||
keyPath: "id"
|
||||
}) {}
|
||||
|
||||
class Db extends IndexedDbVersion.make(Table1, Table2) {}
|
||||
|
||||
assert.equal(Db.tables.size, 2)
|
||||
assert.equal(Db.tables.get("todo1"), Table1)
|
||||
assert.equal(Db.tables.get("todo2"), Table2)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import * as Permissions from "@effect/platform-browser/Permissions"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import * as Effect from "effect/Effect"
|
||||
|
||||
describe("Permissions", () => {
|
||||
it.effect("should be able to query permissions", () =>
|
||||
Effect.gen(function*() {
|
||||
const service = yield* Permissions.Permissions
|
||||
const permissions = yield* service.query("geolocation")
|
||||
assert.strictEqual(permissions.state, "granted")
|
||||
}).pipe(Effect.provide(Permissions.layer)))
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as BrowserWorker from "@effect/platform-browser/BrowserWorker"
|
||||
import { describe } from "@effect/vitest"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as RpcClient from "effect/unstable/rpc/RpcClient"
|
||||
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
||||
import { e2eSuite, UsersClient } from "./fixtures/rpc-e2e.ts"
|
||||
|
||||
describe("RpcWorker", () => {
|
||||
const WorkerClient = UsersClient.layer.pipe(
|
||||
Layer.provide(RpcClient.layerProtocolWorker({ size: 1 })),
|
||||
Layer.provide(BrowserWorker.layer(() => new Worker(new URL("./fixtures/rpc-worker.ts", import.meta.url)))),
|
||||
Layer.merge(
|
||||
Layer.succeed(RpcServer.Protocol)({
|
||||
supportsAck: true
|
||||
} as any)
|
||||
)
|
||||
)
|
||||
e2eSuite("e2e worker", WorkerClient, false)
|
||||
})
|
||||
141
repos/effect/packages/platform-browser/test/fixtures/rpc-e2e.ts
vendored
Normal file
141
repos/effect/packages/platform-browser/test/fixtures/rpc-e2e.ts
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Cause, Context, Effect, Fiber, Option, Stream } from "effect"
|
||||
// @ts-ignore
|
||||
// oxlint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { NodeInspectSymbol } from "effect/Inspectable"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as RpcClient from "effect/unstable/rpc/RpcClient"
|
||||
import type { RpcClientError } from "effect/unstable/rpc/RpcClientError"
|
||||
import type * as RpcGroup from "effect/unstable/rpc/RpcGroup"
|
||||
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
||||
import * as RpcTest from "effect/unstable/rpc/RpcTest"
|
||||
import { AuthClient, AuthLive, TimingLive, User, UserRpcs, UsersLive } from "./rpc-schemas.ts"
|
||||
|
||||
export class UsersClient extends Context.Service<
|
||||
UsersClient,
|
||||
RpcClient.RpcClient<RpcGroup.Rpcs<typeof UserRpcs>, RpcClientError>
|
||||
>()("UsersClient") {
|
||||
static readonly layer = Layer.effect(UsersClient)(RpcClient.make(UserRpcs)).pipe(
|
||||
Layer.provide(AuthClient)
|
||||
)
|
||||
static layerTest = Layer.effect(UsersClient)(RpcTest.makeClient(UserRpcs)).pipe(
|
||||
Layer.provide([UsersLive, AuthLive, TimingLive, AuthClient])
|
||||
)
|
||||
}
|
||||
|
||||
export const e2eSuite = <E>(
|
||||
name: string,
|
||||
layer: Layer.Layer<UsersClient | RpcServer.Protocol, E>,
|
||||
concurrent = true
|
||||
) => {
|
||||
describe(name, { concurrent, timeout: 30_000 }, () => {
|
||||
it.effect("should get user", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const user = yield* client.GetUser({ id: "1" })
|
||||
assert.instanceOf(user, User)
|
||||
assert.deepStrictEqual(user, new User({ id: "1", name: "Logged in user" }))
|
||||
}).pipe(Effect.provide(layer)))
|
||||
|
||||
it.effect("nested method", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
yield* client["nested.test"]()
|
||||
}).pipe(Effect.provide(layer)))
|
||||
|
||||
it.effect("should not flatten Option", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const user = yield* client.GetUserOption({ id: "1" })
|
||||
assert.deepStrictEqual(user, Option.some(new User({ id: "1", name: "John" })))
|
||||
}).pipe(Effect.provide(layer)))
|
||||
|
||||
it.effect("headers", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const user = yield* client.GetUser({ id: "1" })
|
||||
assert.instanceOf(user, User)
|
||||
assert.deepStrictEqual(user, new User({ id: "123", name: "Logged in user" }))
|
||||
}).pipe(
|
||||
RpcClient.withHeaders({ userId: "123" }),
|
||||
Effect.provide(layer)
|
||||
))
|
||||
|
||||
it.live("Stream", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const users: Array<User> = []
|
||||
yield* client.StreamUsers({ id: "1" }).pipe(
|
||||
Stream.take(5),
|
||||
Stream.runForEach((user) =>
|
||||
Effect.sync(() => {
|
||||
users.push(user)
|
||||
})
|
||||
),
|
||||
Effect.forkChild
|
||||
)
|
||||
|
||||
yield* Effect.sleep(2000)
|
||||
assert.lengthOf(users, 5)
|
||||
|
||||
// test interrupts
|
||||
const interrupts = yield* client.GetInterrupts()
|
||||
assert.equal(interrupts, 1)
|
||||
|
||||
const { supportsAck } = yield* RpcServer.Protocol
|
||||
|
||||
// test backpressure
|
||||
if (supportsAck) {
|
||||
const emits = yield* client.GetEmits()
|
||||
assert.equal(emits, 5)
|
||||
}
|
||||
}).pipe(Effect.provide(layer)), { timeout: 20000 })
|
||||
|
||||
it.effect("defect", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const cause = yield* client.ProduceDefect().pipe(
|
||||
Effect.sandbox,
|
||||
Effect.flip
|
||||
)
|
||||
assert.deepStrictEqual(cause, Cause.die("boom"))
|
||||
}).pipe(
|
||||
RpcClient.withHeaders({ userId: "123" }),
|
||||
Effect.provide(layer)
|
||||
))
|
||||
|
||||
it.live("never", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const fiber = yield* client.Never().pipe(
|
||||
Effect.forkChild
|
||||
)
|
||||
yield* Effect.sleep(500)
|
||||
assert.isUndefined(fiber.pollUnsafe())
|
||||
|
||||
yield* Fiber.interrupt(fiber)
|
||||
yield* Effect.sleep(100)
|
||||
|
||||
const { supportsAck } = yield* RpcServer.Protocol
|
||||
if (supportsAck) {
|
||||
const interrupts = yield* client.GetInterrupts()
|
||||
assert.equal(interrupts, 1)
|
||||
}
|
||||
}).pipe(
|
||||
RpcClient.withHeaders({ userId: "123" }),
|
||||
Effect.provide(layer)
|
||||
))
|
||||
|
||||
it.effect("timing middleware", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const result = yield* client.TimedMethod({ shouldFail: false })
|
||||
assert.equal(result, 1)
|
||||
yield* client.TimedMethod({ shouldFail: true }).pipe(Effect.exit)
|
||||
const { count, defect, success } = yield* client.GetTimingMiddlewareMetrics()
|
||||
assert.notEqual(count, 0)
|
||||
assert.notEqual(defect, 0)
|
||||
assert.notEqual(success, 0)
|
||||
}).pipe(Effect.provide(layer)))
|
||||
})
|
||||
}
|
||||
157
repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts
vendored
Normal file
157
repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Context, Effect, Layer, Metric, Option, Queue, Schema } from "effect"
|
||||
import { Headers } from "effect/unstable/http"
|
||||
import * as Rpc from "effect/unstable/rpc/Rpc"
|
||||
import * as RpcGroup from "effect/unstable/rpc/RpcGroup"
|
||||
import * as RpcMiddleware from "effect/unstable/rpc/RpcMiddleware"
|
||||
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
||||
|
||||
export class User extends Schema.Class<User>("User")({
|
||||
id: Schema.String,
|
||||
name: Schema.String
|
||||
}) {}
|
||||
|
||||
class StreamUsers extends Rpc.make("StreamUsers", {
|
||||
success: User,
|
||||
payload: {
|
||||
id: Schema.String
|
||||
},
|
||||
stream: true
|
||||
}) {}
|
||||
|
||||
class CurrentUser extends Context.Service<CurrentUser, User>()("CurrentUser") {}
|
||||
|
||||
class Unauthorized extends Schema.ErrorClass<Unauthorized>("Unauthorized")({
|
||||
_tag: Schema.tag("Unauthorized")
|
||||
}) {}
|
||||
|
||||
class AuthMiddleware extends RpcMiddleware.Service<AuthMiddleware, {
|
||||
provides: CurrentUser
|
||||
}>()("AuthMiddleware", {
|
||||
error: Unauthorized,
|
||||
requiredForClient: true
|
||||
}) {}
|
||||
|
||||
class TimingMiddleware extends RpcMiddleware.Service<TimingMiddleware>()("TimingMiddleware") {}
|
||||
|
||||
class GetUser extends Rpc.make("GetUser", {
|
||||
success: User,
|
||||
payload: { id: Schema.String }
|
||||
}) {}
|
||||
|
||||
export const UserRpcs = RpcGroup.make(
|
||||
GetUser,
|
||||
Rpc.make("GetUserOption", {
|
||||
success: Schema.Option(User),
|
||||
payload: { id: Schema.String }
|
||||
}),
|
||||
StreamUsers,
|
||||
Rpc.make("GetInterrupts", {
|
||||
success: Schema.Number
|
||||
}),
|
||||
Rpc.make("GetEmits", {
|
||||
success: Schema.Number
|
||||
}),
|
||||
Rpc.make("ProduceDefect"),
|
||||
Rpc.make("Never"),
|
||||
Rpc.make("nested.test"),
|
||||
Rpc.make("TimedMethod", {
|
||||
payload: {
|
||||
shouldFail: Schema.Boolean
|
||||
},
|
||||
success: Schema.Number
|
||||
}).middleware(TimingMiddleware),
|
||||
Rpc.make("GetTimingMiddlewareMetrics", {
|
||||
success: Schema.Struct({
|
||||
success: Schema.Number,
|
||||
defect: Schema.Number,
|
||||
count: Schema.Number
|
||||
})
|
||||
})
|
||||
).middleware(AuthMiddleware)
|
||||
|
||||
export const AuthLive = Layer.succeed(AuthMiddleware)(
|
||||
AuthMiddleware.of((effect, options) =>
|
||||
Effect.provideService(
|
||||
effect,
|
||||
CurrentUser,
|
||||
new User({ id: options.headers.userid ?? "1", name: options.headers.name ?? "Fallback name" })
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const rpcSuccesses = Metric.counter("rpc_middleware_success")
|
||||
const rpcDefects = Metric.counter("rpc_middleware_defects")
|
||||
const rpcCount = Metric.counter("rpc_middleware_count")
|
||||
export const TimingLive = Layer.succeed(TimingMiddleware)(
|
||||
TimingMiddleware.of((effect) =>
|
||||
effect.pipe(
|
||||
Effect.tap(Metric.update(rpcSuccesses, 1)),
|
||||
Effect.tapDefect(() => Metric.update(rpcDefects, 1)),
|
||||
Effect.ensuring(Metric.update(rpcCount, 1))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
export const UsersLive = UserRpcs.toLayer(Effect.gen(function*() {
|
||||
let interrupts = 0
|
||||
let emits = 0
|
||||
return UserRpcs.of({
|
||||
GetUser: (_) =>
|
||||
CurrentUser.pipe(
|
||||
Rpc.fork
|
||||
),
|
||||
GetUserOption: Effect.fnUntraced(function*(req) {
|
||||
return Option.some(new User({ id: req.id, name: "John" }))
|
||||
}),
|
||||
StreamUsers: Effect.fnUntraced(function*(req, _) {
|
||||
const mailbox = yield* Queue.bounded<User>(0)
|
||||
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.sync(() => {
|
||||
interrupts++
|
||||
})
|
||||
)
|
||||
|
||||
yield* Queue.offer(mailbox, new User({ id: req.id, name: "John" })).pipe(
|
||||
Effect.tap(() =>
|
||||
Effect.sync(() => {
|
||||
emits++
|
||||
})
|
||||
),
|
||||
Effect.delay(100),
|
||||
Effect.forever,
|
||||
Effect.forkScoped
|
||||
)
|
||||
|
||||
return mailbox
|
||||
}),
|
||||
GetInterrupts: () => Effect.sync(() => interrupts),
|
||||
GetEmits: () => Effect.sync(() => emits),
|
||||
ProduceDefect: () => Effect.die("boom"),
|
||||
Never: () => Effect.never.pipe(Effect.onInterrupt(() => Effect.sync(() => interrupts++))),
|
||||
"nested.test": () => Effect.void,
|
||||
TimedMethod: (_) => _.shouldFail ? Effect.die("boom") : Effect.succeed(1),
|
||||
GetTimingMiddlewareMetrics: () =>
|
||||
Effect.all({
|
||||
defect: Metric.value(rpcDefects).pipe(Effect.map((_) => _.count)),
|
||||
success: Metric.value(rpcSuccesses).pipe(Effect.map((_) => _.count)),
|
||||
count: Metric.value(rpcCount).pipe(Effect.map((_) => _.count))
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
export const RpcLive = RpcServer.layer(UserRpcs, {
|
||||
disableFatalDefects: true
|
||||
}).pipe(
|
||||
Layer.provide([
|
||||
UsersLive,
|
||||
AuthLive,
|
||||
TimingLive
|
||||
])
|
||||
)
|
||||
|
||||
export const AuthClient = RpcMiddleware.layerClient(AuthMiddleware, ({ next, request }) =>
|
||||
next({
|
||||
...request,
|
||||
headers: Headers.set(request.headers, "name", "Logged in user")
|
||||
}))
|
||||
12
repos/effect/packages/platform-browser/test/fixtures/rpc-worker.ts
vendored
Normal file
12
repos/effect/packages/platform-browser/test/fixtures/rpc-worker.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as BrowserWorkerRunner from "@effect/platform-browser/BrowserWorkerRunner"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
||||
import { RpcLive } from "./rpc-schemas.ts"
|
||||
|
||||
const MainLive = RpcLive.pipe(
|
||||
Layer.provide(RpcServer.layerProtocolWorkerRunner),
|
||||
Layer.provide(BrowserWorkerRunner.layer)
|
||||
)
|
||||
|
||||
Effect.runFork(Layer.launch(MainLive))
|
||||
8
repos/effect/packages/platform-browser/tsconfig.json
Normal file
8
repos/effect/packages/platform-browser/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{ "path": "../effect" }
|
||||
]
|
||||
}
|
||||
17
repos/effect/packages/platform-browser/vitest.config.ts
Normal file
17
repos/effect/packages/platform-browser/vitest.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as os from "node:os"
|
||||
import * as path from "node:path"
|
||||
import { mergeConfig, type ViteUserConfig } from "vitest/config"
|
||||
import shared from "../../vitest.shared.ts"
|
||||
|
||||
const config: ViteUserConfig = {
|
||||
test: {
|
||||
environment: "happy-dom",
|
||||
execArgv: [
|
||||
"--localstorage-file",
|
||||
path.resolve(os.tmpdir(), `vitest-${process.pid}.localstorage`)
|
||||
],
|
||||
setupFiles: "./vitest.setup.ts"
|
||||
}
|
||||
}
|
||||
|
||||
export default mergeConfig(shared, config)
|
||||
5
repos/effect/packages/platform-browser/vitest.setup.ts
Normal file
5
repos/effect/packages/platform-browser/vitest.setup.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { defineWebWorkers } from "@vitest/web-worker/pure"
|
||||
|
||||
defineWebWorkers({
|
||||
clone: "none"
|
||||
})
|
||||
Reference in New Issue
Block a user