Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'

This commit is contained in:
-Puter
2026-07-19 03:25:10 +05:30
parent dd1071cc10
commit 2daf979036
2214 changed files with 673090 additions and 0 deletions

View 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

View 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.

View File

@@ -0,0 +1 @@
# `@effect/platform-browser`

View 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 }
]
}
}

View 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"
}
}

View 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
})
})
)

View 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)
)

View File

@@ -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)
}

View 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)
})

View 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)
})
})

View 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

View 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)

View 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)
})
)
}
})
)

View File

@@ -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))

View 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"
})
})
})
)

View 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)
))

View 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"))
}
})
)

View 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
}

File diff suppressed because it is too large Load Diff

View 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

View 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]))
})

View 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 })
})
})
})
)

View 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"

View File

@@ -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)
}
}
}))
))
)
})
})

View File

@@ -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!\" }"
}]
}))))
})

View File

@@ -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)
)
)
})

View File

@@ -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))
})
)

View File

@@ -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 })
)
})

View File

@@ -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

View File

@@ -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)
})
})

View File

@@ -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)
})
})

View File

@@ -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)))
})

View File

@@ -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)
})

View 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)))
})
}

View 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")
}))

View 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))

View File

@@ -0,0 +1,8 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.base.json",
"include": ["src"],
"references": [
{ "path": "../effect" }
]
}

View 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)

View File

@@ -0,0 +1,5 @@
import { defineWebWorkers } from "@vitest/web-worker/pure"
defineWebWorkers({
clone: "none"
})