Merge commit '3c60637c1a27da8ba66888de518d58d5707801f2' as 'repos/effect-smol'
This commit is contained in:
724
repos/effect-smol/packages/platform-node-shared/CHANGELOG.md
Normal file
724
repos/effect-smol/packages/platform-node-shared/CHANGELOG.md
Normal file
@@ -0,0 +1,724 @@
|
||||
# @effect/platform-node-shared
|
||||
|
||||
## 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
|
||||
|
||||
- [#2523](https://github.com/Effect-TS/effect-smol/pull/2523) [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c) Thanks @rajzik! - Add glob to filesystem
|
||||
|
||||
- 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
|
||||
|
||||
- [#2235](https://github.com/Effect-TS/effect-smol/pull/2235) [`5805569`](https://github.com/Effect-TS/effect-smol/commit/5805569ef5ac47db49924fa34abf03fbdc1675f3) Thanks @gcanti! - Add the package root barrel export.
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- [#2098](https://github.com/Effect-TS/effect-smol/pull/2098) [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec) Thanks @tim-smart! - generate binary arrays from streams with less copying
|
||||
|
||||
- [#2098](https://github.com/Effect-TS/effect-smol/pull/2098) [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec) Thanks @tim-smart! - improve http body consumption
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- [`a48d439`](https://github.com/Effect-TS/effect-smol/commit/a48d439efad075728bbaee45b77a8ab41f552505) Thanks @tim-smart! - don't remove SIGINT listener until fiber exit
|
||||
|
||||
- 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
|
||||
|
||||
- [#1960](https://github.com/Effect-TS/effect-smol/pull/1960) [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf) Thanks @IMax153! - Add `ChildProcessHandle.unref`, returning an `Effect` that restores the child process reference when run.
|
||||
|
||||
- [#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.
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- [#1671](https://github.com/Effect-TS/effect-smol/pull/1671) [`174a88d`](https://github.com/Effect-TS/effect-smol/commit/174a88d37b2c1fd52af2340c3b19c8272bccc8d0) Thanks @tim-smart! - default to endOnDone: false in NodeStdio
|
||||
|
||||
- [#1671](https://github.com/Effect-TS/effect-smol/pull/1671) [`174a88d`](https://github.com/Effect-TS/effect-smol/commit/174a88d37b2c1fd52af2340c3b19c8272bccc8d0) Thanks @tim-smart! - catch errors in pullIntoWritable
|
||||
|
||||
- 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
|
||||
|
||||
- [#1658](https://github.com/Effect-TS/effect-smol/pull/1658) [`0fc977b`](https://github.com/Effect-TS/effect-smol/commit/0fc977b20d08caee7f64b2065e68f84afe124316) Thanks @nikelborm! - Add `endOnDone` option to Stdio stdout / stderr
|
||||
|
||||
- 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
|
||||
|
||||
- [#1526](https://github.com/Effect-TS/effect-smol/pull/1526) [`b9951d6`](https://github.com/Effect-TS/effect-smol/commit/b9951d6b178b4c2173112fa7454b6abd8030585f) Thanks @tim-smart! - fix fs.stat when blksize is undefined
|
||||
|
||||
- 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
|
||||
|
||||
- [#1349](https://github.com/Effect-TS/effect-smol/pull/1349) [`5207c20`](https://github.com/Effect-TS/effect-smol/commit/5207c2054a3f60c8fd795faa6cf3f179926b8878) Thanks @tim-smart! - simplify NodeChildSpawner stdout streams
|
||||
|
||||
- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]:
|
||||
- effect@4.0.0-beta.6
|
||||
|
||||
## 4.0.0-beta.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]:
|
||||
- effect@4.0.0-beta.5
|
||||
|
||||
## 4.0.0-beta.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]:
|
||||
- effect@4.0.0-beta.4
|
||||
|
||||
## 4.0.0-beta.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]:
|
||||
- effect@4.0.0-beta.3
|
||||
|
||||
## 4.0.0-beta.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]:
|
||||
- effect@4.0.0-beta.2
|
||||
|
||||
## 4.0.0-beta.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]:
|
||||
- effect@4.0.0-beta.1
|
||||
|
||||
## 4.0.0-beta.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]:
|
||||
- effect@4.0.0-beta.0
|
||||
21
repos/effect-smol/packages/platform-node-shared/LICENSE
Normal file
21
repos/effect-smol/packages/platform-node-shared/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Effectful Technologies Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
# `@effect/platform-node`
|
||||
|
||||
Provides Node.js-specific implementations for the abstractions defined in [`@effect/platform`](https://github.com/Effect-TS/effect/tree/main/packages/platform), allowing you to write platform-independent code that integrates smoothly with Node.js.
|
||||
|
||||
## Documentation
|
||||
|
||||
- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/platform-node).
|
||||
37
repos/effect-smol/packages/platform-node-shared/docgen.json
Normal file
37
repos/effect-smol/packages/platform-node-shared/docgen.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "../../node_modules/@effect/docgen/schema.json",
|
||||
"srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/platform-node/src/",
|
||||
"exclude": ["src/internal/**/*.ts"],
|
||||
"examplesCompilerOptions": {
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"module": "ES2022",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"paths": {
|
||||
"effect": ["../../../effect/src/index.js"],
|
||||
"effect/*": ["../../../effect/src/*.js"],
|
||||
"@effect/cluster": ["../../../cluster/src/index.js"],
|
||||
"@effect/cluster/*": ["../../../cluster/src/*.js"],
|
||||
"@effect/experimental": ["../../../experimental/src/index.js"],
|
||||
"@effect/experimental/*": ["../../../experimental/src/*.js"],
|
||||
"@effect/platform": ["../../../platform/src/index.js"],
|
||||
"@effect/platform/*": ["../../../platform/src/*.js"],
|
||||
"@effect/platform-node": ["../../../platform-node/src/index.js"],
|
||||
"@effect/platform-node/*": ["../../../platform-node/src/*.js"],
|
||||
"@effect/platform-node-shared": ["../../../platform-node-shared/src/index.js"],
|
||||
"@effect/platform-node-shared/*": ["../../../platform-node-shared/src/*.js"],
|
||||
"@effect/rpc": ["../../../rpc/src/index.js"],
|
||||
"@effect/rpc/*": ["../../../rpc/src/*.js"],
|
||||
"effect/unstable/sql": ["../../../sql/src/index.js"],
|
||||
"effect/unstable/sql/*": ["../../../sql/src/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
{ "name": "@effect/language-service", "includeSuggestionsInTsc": false }
|
||||
]
|
||||
}
|
||||
}
|
||||
75
repos/effect-smol/packages/platform-node-shared/package.json
Normal file
75
repos/effect-smol/packages/platform-node-shared/package.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "@effect/platform-node-shared",
|
||||
"type": "module",
|
||||
"version": "4.0.0-beta.98",
|
||||
"license": "MIT",
|
||||
"description": "Unified interfaces for common platform-specific services",
|
||||
"homepage": "https://effect.website",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Effect-TS/effect-smol.git",
|
||||
"directory": "packages/platform-node-shared"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Effect-TS/effect-smol/issues"
|
||||
},
|
||||
"tags": [
|
||||
"typescript",
|
||||
"algebraic-data-types",
|
||||
"functional-programming"
|
||||
],
|
||||
"keywords": [
|
||||
"typescript",
|
||||
"algebraic-data-types",
|
||||
"functional-programming"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"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": {
|
||||
"@types/node": "^26.1.1",
|
||||
"effect": "workspace:^",
|
||||
"tar": "^7.5.19"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/ws": "^8.18.1",
|
||||
"ws": "^8.21.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,724 @@
|
||||
/**
|
||||
* Shared Node.js implementation of the child process spawner service.
|
||||
*
|
||||
* This module adapts `node:child_process.spawn` to the Effect
|
||||
* `ChildProcessSpawner` service. Provide {@link layer} to run `ChildProcess`
|
||||
* commands in Node-compatible runtimes: commands get scoped process handles
|
||||
* with stdin sinks, stdout and stderr streams, exit-code waiting,
|
||||
* interruption-time cleanup, process killing, and custom file-descriptor pipes.
|
||||
*
|
||||
* The implementation sits below the command-building API. It validates and
|
||||
* resolves `cwd` through the Effect `FileSystem` and `Path` services,
|
||||
* translates Node errno failures to `PlatformError`, and uses scopes to
|
||||
* terminate referenced children when the owning effect is interrupted or
|
||||
* finalized. Pipelines are flattened by {@link flattenCommand} and spawned one
|
||||
* process at a time, wiring the selected source stream (`stdout`, `stderr`,
|
||||
* `all`, or `fdN`) to the destination `stdin` or `fdN`.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type * as Arr from "effect/Array"
|
||||
import * as Deferred from "effect/Deferred"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Exit from "effect/Exit"
|
||||
import * as FileSystem from "effect/FileSystem"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Path from "effect/Path"
|
||||
import type * as PlatformError from "effect/PlatformError"
|
||||
import * as Predicate from "effect/Predicate"
|
||||
import type * as Scope from "effect/Scope"
|
||||
import * as Sink from "effect/Sink"
|
||||
import * as Stream from "effect/Stream"
|
||||
import * as ChildProcess from "effect/unstable/process/ChildProcess"
|
||||
import type { ChildProcessHandle } from "effect/unstable/process/ChildProcessSpawner"
|
||||
import {
|
||||
ChildProcessSpawner,
|
||||
ExitCode,
|
||||
make as makeSpawner,
|
||||
makeHandle,
|
||||
ProcessId
|
||||
} from "effect/unstable/process/ChildProcessSpawner"
|
||||
import * as NodeChildProcess from "node:child_process"
|
||||
import { PassThrough } from "node:stream"
|
||||
import { handleErrnoException } from "./internal/utils.ts"
|
||||
import * as NodeSink from "./NodeSink.ts"
|
||||
import * as NodeStream from "./NodeStream.ts"
|
||||
|
||||
const toError = (error: unknown): Error =>
|
||||
error instanceof globalThis.Error
|
||||
? error
|
||||
: new globalThis.Error(String(error))
|
||||
|
||||
const toPlatformError = (
|
||||
method: string,
|
||||
error: NodeJS.ErrnoException,
|
||||
command: ChildProcess.Command
|
||||
): PlatformError.PlatformError => {
|
||||
const { commands } = flattenCommand(command)
|
||||
const commandStr = commands.reduce((acc, curr) => {
|
||||
const cmd = `${curr.command} ${curr.args.join(" ")}`
|
||||
return acc.length === 0 ? cmd : `${acc} | ${cmd}`
|
||||
}, "")
|
||||
return handleErrnoException("ChildProcess", method)(error, [commandStr])
|
||||
}
|
||||
|
||||
type ExitCodeWithSignal = readonly [code: number | null, signal: NodeJS.Signals | null]
|
||||
type ExitSignal = Deferred.Deferred<ExitCodeWithSignal>
|
||||
|
||||
const make = Effect.gen(function*() {
|
||||
const fs = yield* FileSystem.FileSystem
|
||||
const path = yield* Path.Path
|
||||
|
||||
const resolveWorkingDirectory = Effect.fnUntraced(
|
||||
function*(options: ChildProcess.CommandOptions) {
|
||||
if (Predicate.isUndefined(options.cwd)) return undefined
|
||||
// Validate that the specified directory is accessible
|
||||
yield* fs.access(options.cwd)
|
||||
return path.resolve(options.cwd)
|
||||
}
|
||||
)
|
||||
|
||||
const resolveEnvironment = (options: ChildProcess.CommandOptions) => {
|
||||
return options.extendEnv
|
||||
? { ...globalThis.process.env, ...options.env }
|
||||
: options.env
|
||||
}
|
||||
|
||||
const inputToStdioOption = (input: ChildProcess.CommandInput | undefined): NodeChildProcess.IOType | undefined =>
|
||||
Stream.isStream(input) ? "pipe" : input
|
||||
|
||||
const outputToStdioOption = (input: ChildProcess.CommandOutput | undefined): NodeChildProcess.IOType | undefined =>
|
||||
Sink.isSink(input) ? "pipe" : input
|
||||
|
||||
const resolveStdinOption = (options: ChildProcess.CommandOptions): ChildProcess.StdinConfig => {
|
||||
const defaultConfig: ChildProcess.StdinConfig = { stream: "pipe", encoding: "utf-8", endOnDone: true }
|
||||
if (Predicate.isUndefined(options.stdin)) {
|
||||
return defaultConfig
|
||||
}
|
||||
if (typeof options.stdin === "string") {
|
||||
return { ...defaultConfig, stream: options.stdin }
|
||||
}
|
||||
if (Stream.isStream(options.stdin)) {
|
||||
return { ...defaultConfig, stream: options.stdin }
|
||||
}
|
||||
return {
|
||||
stream: options.stdin.stream,
|
||||
encoding: options.stdin.encoding ?? defaultConfig.encoding,
|
||||
endOnDone: options.stdin.endOnDone ?? defaultConfig.endOnDone
|
||||
}
|
||||
}
|
||||
|
||||
const resolveOutputOption = (
|
||||
options: ChildProcess.CommandOptions,
|
||||
streamName: "stdout" | "stderr"
|
||||
): ChildProcess.StdoutConfig => {
|
||||
const option = options[streamName]
|
||||
if (Predicate.isUndefined(option)) {
|
||||
return { stream: "pipe" }
|
||||
}
|
||||
if (typeof option === "string") {
|
||||
return { stream: option }
|
||||
}
|
||||
if (Sink.isSink(option)) {
|
||||
return { stream: option }
|
||||
}
|
||||
return { stream: option.stream }
|
||||
}
|
||||
|
||||
interface ResolvedAdditionalFd {
|
||||
readonly fd: number
|
||||
readonly config: ChildProcess.AdditionalFdConfig
|
||||
}
|
||||
|
||||
const resolveAdditionalFds = (
|
||||
options: ChildProcess.CommandOptions
|
||||
): ReadonlyArray<ResolvedAdditionalFd> => {
|
||||
if (Predicate.isUndefined(options.additionalFds)) {
|
||||
return []
|
||||
}
|
||||
const result: Array<ResolvedAdditionalFd> = []
|
||||
for (const [name, config] of Object.entries(options.additionalFds)) {
|
||||
const fd = ChildProcess.parseFdName(name)
|
||||
if (Predicate.isNotUndefined(fd)) {
|
||||
result.push({ fd, config })
|
||||
}
|
||||
}
|
||||
// Sort by fd number to ensure correct ordering
|
||||
return result.sort((a, b) => a.fd - b.fd)
|
||||
}
|
||||
|
||||
const buildStdioArray = (
|
||||
stdinConfig: ChildProcess.StdinConfig,
|
||||
stdoutConfig: ChildProcess.StdoutConfig,
|
||||
stderrConfig: ChildProcess.StderrConfig,
|
||||
additionalFds: ReadonlyArray<ResolvedAdditionalFd>
|
||||
): NodeChildProcess.StdioOptions => {
|
||||
const stdio: Array<NodeChildProcess.IOType | undefined> = [
|
||||
inputToStdioOption(stdinConfig.stream),
|
||||
outputToStdioOption(stdoutConfig.stream),
|
||||
outputToStdioOption(stderrConfig.stream)
|
||||
]
|
||||
|
||||
if (additionalFds.length === 0) {
|
||||
return stdio as NodeChildProcess.StdioOptions
|
||||
}
|
||||
|
||||
// Find the maximum fd number to size the array correctly
|
||||
const maxFd = additionalFds.reduce((max, { fd }) => Math.max(max, fd), 2)
|
||||
|
||||
// Fill gaps with "ignore"
|
||||
for (let i = 3; i <= maxFd; i++) {
|
||||
stdio[i] = "ignore"
|
||||
}
|
||||
|
||||
// Set up additional fds as "pipe"
|
||||
for (const { fd } of additionalFds) {
|
||||
stdio[fd] = "pipe"
|
||||
}
|
||||
|
||||
return stdio as NodeChildProcess.StdioOptions
|
||||
}
|
||||
|
||||
const setupAdditionalFds = Effect.fnUntraced(function*(
|
||||
command: ChildProcess.StandardCommand,
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
additionalFds: ReadonlyArray<ResolvedAdditionalFd>
|
||||
) {
|
||||
if (additionalFds.length === 0) {
|
||||
return {
|
||||
getInputFd: () => Sink.drain,
|
||||
getOutputFd: () => Stream.empty
|
||||
}
|
||||
}
|
||||
|
||||
const inputSinks = new Map<number, Sink.Sink<void, Uint8Array, never, PlatformError.PlatformError>>()
|
||||
const outputStreams = new Map<number, Stream.Stream<Uint8Array, PlatformError.PlatformError>>()
|
||||
|
||||
for (const { config, fd } of additionalFds) {
|
||||
const nodeStream = childProcess.stdio[fd]
|
||||
|
||||
switch (config.type) {
|
||||
case "input": {
|
||||
// Create a sink to write to for input file descriptors
|
||||
let sink: Sink.Sink<void, Uint8Array, never, PlatformError.PlatformError> = Sink.drain
|
||||
if (nodeStream && "write" in nodeStream) {
|
||||
sink = NodeSink.fromWritable({
|
||||
evaluate: () => nodeStream,
|
||||
onError: (error) => toPlatformError(`fromWritable(fd${fd})`, toError(error), command)
|
||||
})
|
||||
}
|
||||
|
||||
// If user provided a stream, pipe it into the sink
|
||||
if (config.stream) {
|
||||
yield* Effect.forkScoped(Stream.run(config.stream, sink))
|
||||
}
|
||||
|
||||
inputSinks.set(fd, sink)
|
||||
|
||||
break
|
||||
}
|
||||
case "output": {
|
||||
// Create a stream to read from for output file descriptors
|
||||
let stream: Stream.Stream<Uint8Array, PlatformError.PlatformError> = Stream.empty
|
||||
if (nodeStream && "read" in nodeStream) {
|
||||
const passThrough = new PassThrough()
|
||||
nodeStream.on("error", (error) => passThrough.destroy(error))
|
||||
nodeStream.pipe(passThrough)
|
||||
stream = NodeStream.fromReadable({
|
||||
evaluate: () => passThrough,
|
||||
onError: (error) => toPlatformError(`fromReadable(fd${fd})`, toError(error), command)
|
||||
})
|
||||
}
|
||||
|
||||
// If user provided a sink, transduce the stream through it
|
||||
if (config.sink) {
|
||||
stream = Stream.transduce(stream, config.sink)
|
||||
}
|
||||
|
||||
outputStreams.set(fd, stream)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getInputFd: (fd: number) => inputSinks.get(fd) ?? Sink.drain,
|
||||
getOutputFd: (fd: number) => outputStreams.get(fd) ?? Stream.empty
|
||||
}
|
||||
})
|
||||
|
||||
const setupChildStdin = (
|
||||
command: ChildProcess.StandardCommand,
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
config: ChildProcess.StdinConfig
|
||||
) =>
|
||||
Effect.suspend(() => {
|
||||
// If the child process has a standard input stream, connect it to the
|
||||
// sink that will attached to the process handle
|
||||
let sink: Sink.Sink<void, unknown, never, PlatformError.PlatformError> = Sink.drain
|
||||
if (Predicate.isNotNull(childProcess.stdin)) {
|
||||
sink = NodeSink.fromWritable({
|
||||
evaluate: () => childProcess.stdin!,
|
||||
onError: (error) => toPlatformError("fromWritable(stdin)", toError(error), command),
|
||||
endOnDone: config.endOnDone,
|
||||
encoding: config.encoding
|
||||
})
|
||||
}
|
||||
|
||||
// If the user provided a `Stream`, run it into the stdin sink
|
||||
if (Stream.isStream(config.stream)) {
|
||||
return Effect.as(Effect.forkScoped(Stream.run(config.stream, sink)), sink)
|
||||
}
|
||||
|
||||
return Effect.succeed(sink)
|
||||
})
|
||||
|
||||
const setupChildOutputStreams = (
|
||||
command: ChildProcess.StandardCommand,
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
stdoutConfig: ChildProcess.StdoutConfig,
|
||||
stderrConfig: ChildProcess.StderrConfig
|
||||
): {
|
||||
stdout: Stream.Stream<Uint8Array, PlatformError.PlatformError>
|
||||
stderr: Stream.Stream<Uint8Array, PlatformError.PlatformError>
|
||||
all: Stream.Stream<Uint8Array, PlatformError.PlatformError>
|
||||
} => {
|
||||
let stdout = childProcess.stdout ?
|
||||
(() => {
|
||||
const passThrough = new PassThrough()
|
||||
childProcess.stdout!.on("error", (error) => passThrough.destroy(error))
|
||||
childProcess.stdout!.pipe(passThrough)
|
||||
return NodeStream.fromReadable({
|
||||
evaluate: () => passThrough,
|
||||
onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command)
|
||||
})
|
||||
})() :
|
||||
Stream.empty
|
||||
let stderr = childProcess.stderr ?
|
||||
(() => {
|
||||
const passThrough = new PassThrough()
|
||||
childProcess.stderr!.on("error", (error) => passThrough.destroy(error))
|
||||
childProcess.stderr!.pipe(passThrough)
|
||||
return NodeStream.fromReadable({
|
||||
evaluate: () => passThrough,
|
||||
onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command)
|
||||
})
|
||||
})() :
|
||||
Stream.empty
|
||||
|
||||
if (Sink.isSink(stdoutConfig.stream)) {
|
||||
stdout = Stream.transduce(stdout, stdoutConfig.stream)
|
||||
}
|
||||
if (Sink.isSink(stderrConfig.stream)) {
|
||||
stderr = Stream.transduce(stderr, stderrConfig.stream)
|
||||
}
|
||||
|
||||
const all = Stream.merge(stdout, stderr)
|
||||
|
||||
return { stdout, stderr, all }
|
||||
}
|
||||
|
||||
const spawn = (
|
||||
command: ChildProcess.StandardCommand,
|
||||
spawnOptions: NodeChildProcess.SpawnOptions
|
||||
) =>
|
||||
Effect.callback<
|
||||
readonly [NodeChildProcess.ChildProcess, ExitSignal],
|
||||
PlatformError.PlatformError
|
||||
>((resume) => {
|
||||
const deferred = Deferred.makeUnsafe<ExitCodeWithSignal>()
|
||||
const handle = NodeChildProcess.spawn(
|
||||
command.command,
|
||||
command.args,
|
||||
spawnOptions
|
||||
)
|
||||
handle.on("error", (error) => {
|
||||
resume(Effect.fail(toPlatformError("spawn", error, command)))
|
||||
})
|
||||
handle.on("exit", (...args) => {
|
||||
Deferred.doneUnsafe(deferred, Exit.succeed(args))
|
||||
})
|
||||
handle.on("spawn", () => {
|
||||
resume(Effect.succeed([handle, deferred]))
|
||||
})
|
||||
return Effect.sync(() => {
|
||||
handle.kill("SIGTERM")
|
||||
})
|
||||
})
|
||||
|
||||
const killProcessGroup = (
|
||||
command: ChildProcess.StandardCommand,
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
signal: NodeJS.Signals
|
||||
) => {
|
||||
if (globalThis.process.platform === "win32") {
|
||||
return Effect.callback<void, PlatformError.PlatformError>((resume) => {
|
||||
NodeChildProcess.exec(`taskkill /pid ${childProcess.pid} /T /F`, (error) => {
|
||||
if (error) {
|
||||
resume(Effect.fail(toPlatformError("kill", toError(error), command)))
|
||||
} else {
|
||||
resume(Effect.void)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
return Effect.try({
|
||||
try: () => {
|
||||
globalThis.process.kill(-childProcess.pid!, signal)
|
||||
},
|
||||
catch: (error) => toPlatformError("kill", toError(error), command)
|
||||
})
|
||||
}
|
||||
|
||||
const killProcessGroupOnExit = (
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
signal: NodeJS.Signals
|
||||
): void => {
|
||||
if (globalThis.process.platform === "win32") {
|
||||
NodeChildProcess.exec(`taskkill /pid ${childProcess.pid} /T /F`, () => {
|
||||
// ignore errors during best-effort cleanup
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
globalThis.process.kill(-childProcess.pid!, signal)
|
||||
} catch {
|
||||
// ignore errors during best-effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
const killProcess = (
|
||||
command: ChildProcess.StandardCommand,
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
signal: NodeJS.Signals
|
||||
) =>
|
||||
Effect.suspend(() => {
|
||||
const killed = childProcess.kill(signal)
|
||||
if (!killed) {
|
||||
const error = new globalThis.Error("Failed to kill child process")
|
||||
return Effect.fail(toPlatformError("kill", error, command))
|
||||
}
|
||||
return Effect.void
|
||||
})
|
||||
|
||||
const withTimeout = (
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
command: ChildProcess.StandardCommand,
|
||||
options: ChildProcess.KillOptions | undefined
|
||||
) =>
|
||||
<A, E, R>(
|
||||
kill: (
|
||||
command: ChildProcess.StandardCommand,
|
||||
childProcess: NodeChildProcess.ChildProcess,
|
||||
signal: NodeJS.Signals
|
||||
) => Effect.Effect<A, E, R>
|
||||
) => {
|
||||
const killSignal = options?.killSignal ?? "SIGTERM"
|
||||
return Predicate.isUndefined(options?.forceKillAfter)
|
||||
? kill(command, childProcess, killSignal)
|
||||
: Effect.timeoutOrElse(kill(command, childProcess, killSignal), {
|
||||
duration: options.forceKillAfter,
|
||||
orElse: () => kill(command, childProcess, "SIGKILL")
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate source stream from a process handle based on the
|
||||
* `from` pipe option.
|
||||
*/
|
||||
const getSourceStream = (
|
||||
handle: ChildProcessHandle,
|
||||
from: ChildProcess.PipeFromOption | undefined
|
||||
): Stream.Stream<Uint8Array, PlatformError.PlatformError> => {
|
||||
const fromOption = from ?? "stdout"
|
||||
switch (fromOption) {
|
||||
case "stdout":
|
||||
return handle.stdout
|
||||
case "stderr":
|
||||
return handle.stderr
|
||||
case "all":
|
||||
return handle.all
|
||||
default: {
|
||||
// Handle fd3, fd4, etc.
|
||||
const fd = ChildProcess.parseFdName(fromOption)
|
||||
if (Predicate.isNotUndefined(fd)) {
|
||||
return handle.getOutputFd(fd)
|
||||
}
|
||||
// Fallback to stdout for invalid fd names
|
||||
return handle.stdout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const spawnCommand: (
|
||||
command: ChildProcess.Command
|
||||
) => Effect.Effect<
|
||||
ChildProcessHandle,
|
||||
PlatformError.PlatformError,
|
||||
Scope.Scope
|
||||
> = Effect.fnUntraced(function*(cmd) {
|
||||
switch (cmd._tag) {
|
||||
case "StandardCommand": {
|
||||
const stdinConfig = resolveStdinOption(cmd.options)
|
||||
const stdoutConfig = resolveOutputOption(cmd.options, "stdout")
|
||||
const stderrConfig = resolveOutputOption(cmd.options, "stderr")
|
||||
const resolvedAdditionalFds = resolveAdditionalFds(cmd.options)
|
||||
let isReferenced = true
|
||||
|
||||
const cwd = yield* resolveWorkingDirectory(cmd.options)
|
||||
const env = resolveEnvironment(cmd.options)
|
||||
const stdio = buildStdioArray(stdinConfig, stdoutConfig, stderrConfig, resolvedAdditionalFds)
|
||||
|
||||
const [childProcess, exitSignal] = yield* Effect.acquireRelease(
|
||||
spawn(cmd, {
|
||||
cwd,
|
||||
env,
|
||||
stdio,
|
||||
detached: cmd.options.detached ?? process.platform !== "win32",
|
||||
shell: cmd.options.shell
|
||||
}),
|
||||
Effect.fnUntraced(function*([childProcess, exitSignal]) {
|
||||
const exited = yield* Deferred.isDone(exitSignal)
|
||||
const killWithTimeout = withTimeout(childProcess, cmd, cmd.options)
|
||||
if (exited) {
|
||||
// Process already exited, check if children need cleanup
|
||||
const [code] = yield* Deferred.await(exitSignal)
|
||||
if (code !== 0 && Predicate.isNotNull(code)) {
|
||||
// Non-zero exit code ,attempt to clean up process group
|
||||
return yield* Effect.ignore(killWithTimeout(killProcessGroup))
|
||||
}
|
||||
return yield* Effect.void
|
||||
}
|
||||
if (!isReferenced) {
|
||||
return yield* Effect.void
|
||||
}
|
||||
// Process is still running, kill it
|
||||
return yield* killWithTimeout((command, childProcess, signal) =>
|
||||
Effect.catch(
|
||||
killProcessGroup(command, childProcess, signal),
|
||||
() => killProcess(command, childProcess, signal)
|
||||
)
|
||||
).pipe(
|
||||
Effect.andThen(Deferred.await(exitSignal)),
|
||||
Effect.ignore
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
const pid = ProcessId(childProcess.pid!)
|
||||
childProcess.on("exit", (code) => {
|
||||
if (code !== 0 && Predicate.isNotNull(code)) {
|
||||
killProcessGroupOnExit(childProcess, cmd.options.killSignal ?? "SIGTERM")
|
||||
}
|
||||
})
|
||||
const reref = Effect.sync(() => {
|
||||
if (!isReferenced) {
|
||||
childProcess.ref()
|
||||
isReferenced = true
|
||||
}
|
||||
})
|
||||
const unref = Effect.sync(() => {
|
||||
if (isReferenced) {
|
||||
childProcess.unref()
|
||||
isReferenced = false
|
||||
}
|
||||
return reref
|
||||
})
|
||||
const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig)
|
||||
const { all, stderr, stdout } = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig)
|
||||
const { getInputFd, getOutputFd } = yield* setupAdditionalFds(cmd, childProcess, resolvedAdditionalFds)
|
||||
const isRunning = Effect.map(Deferred.isDone(exitSignal), (done) => !done)
|
||||
const exitCode = Effect.flatMap(Deferred.await(exitSignal), ([code, signal]) => {
|
||||
if (Predicate.isNotNull(code)) {
|
||||
return Effect.succeed(ExitCode(code))
|
||||
}
|
||||
// If code is `null`, then `signal` must be defined. See the NodeJS
|
||||
// documentation for the `"exit"` event on a `child_process`.
|
||||
// https://nodejs.org/api/child_process.html#child_process_event_exit
|
||||
const error = new globalThis.Error(`Process interrupted due to receipt of signal: '${signal}'`)
|
||||
return Effect.fail(toPlatformError("exitCode", error, cmd))
|
||||
})
|
||||
const kill = (options?: ChildProcess.KillOptions | undefined) => {
|
||||
const killWithTimeout = withTimeout(childProcess, cmd, options)
|
||||
return killWithTimeout((command, childProcess, signal) =>
|
||||
Effect.catch(
|
||||
killProcessGroup(command, childProcess, signal),
|
||||
() => killProcess(command, childProcess, signal)
|
||||
)
|
||||
).pipe(
|
||||
Effect.andThen(Deferred.await(exitSignal)),
|
||||
Effect.asVoid
|
||||
)
|
||||
}
|
||||
|
||||
return makeHandle({
|
||||
pid,
|
||||
exitCode,
|
||||
isRunning,
|
||||
kill,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
all,
|
||||
getInputFd,
|
||||
getOutputFd,
|
||||
unref
|
||||
})
|
||||
}
|
||||
case "PipedCommand": {
|
||||
const { commands, pipeOptions } = flattenCommand(cmd)
|
||||
const [root, ...pipeline] = commands
|
||||
|
||||
const handles = [yield* spawnCommand(root)]
|
||||
|
||||
for (let i = 0; i < pipeline.length; i++) {
|
||||
const command = pipeline[i]
|
||||
const options = pipeOptions[i] ?? {}
|
||||
const stdinConfig = resolveStdinOption(command.options)
|
||||
|
||||
// Get the appropriate stream from the source based on `from` option
|
||||
const sourceStream = Stream.unwrap(
|
||||
Effect.succeed(getSourceStream(handles[handles.length - 1], options.from))
|
||||
)
|
||||
|
||||
// Determine where to pipe: stdin or custom fd
|
||||
const toOption = options.to ?? "stdin"
|
||||
|
||||
if (toOption === "stdin") {
|
||||
// Pipe to stdin (default behavior)
|
||||
handles.push(
|
||||
yield* spawnCommand(ChildProcess.make(command.command, command.args, {
|
||||
...command.options,
|
||||
stdin: { ...stdinConfig, stream: sourceStream }
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
// Pipe to custom fd (fd3, fd4, etc.)
|
||||
const fd = ChildProcess.parseFdName(toOption)
|
||||
if (Predicate.isNotUndefined(fd)) {
|
||||
const fdName = ChildProcess.fdName(fd) as `fd${number}`
|
||||
const existingFds = command.options.additionalFds ?? {}
|
||||
handles.push(
|
||||
yield* spawnCommand(ChildProcess.make(command.command, command.args, {
|
||||
...command.options,
|
||||
additionalFds: {
|
||||
...existingFds,
|
||||
[fdName]: { type: "input" as const, stream: sourceStream }
|
||||
}
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
// Invalid fd name, fall back to stdin
|
||||
handles.push(
|
||||
yield* spawnCommand(ChildProcess.make(command.command, command.args, {
|
||||
...command.options,
|
||||
stdin: { ...stdinConfig, stream: sourceStream }
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handle = handles[handles.length - 1]
|
||||
const unref = Effect.gen(function*() {
|
||||
const rerefs: Array<Effect.Effect<void, PlatformError.PlatformError>> = []
|
||||
for (const handle of handles) {
|
||||
rerefs.push(yield* handle.unref)
|
||||
}
|
||||
return Effect.forEach([...rerefs].reverse(), (reref) => reref, { discard: true })
|
||||
})
|
||||
|
||||
return makeHandle({
|
||||
pid: handle.pid,
|
||||
exitCode: handle.exitCode,
|
||||
isRunning: handle.isRunning,
|
||||
kill: handle.kill,
|
||||
stdin: handle.stdin,
|
||||
stdout: handle.stdout,
|
||||
stderr: handle.stderr,
|
||||
all: handle.all,
|
||||
getInputFd: handle.getInputFd,
|
||||
getOutputFd: handle.getOutputFd,
|
||||
unref
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return makeSpawner(spawnCommand)
|
||||
})
|
||||
|
||||
/**
|
||||
* Layer that provides the `NodeChildProcessSpawner` implementation.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<
|
||||
ChildProcessSpawner,
|
||||
never,
|
||||
FileSystem.FileSystem | Path.Path
|
||||
> = Layer.effect(ChildProcessSpawner, make)
|
||||
|
||||
// =============================================================================
|
||||
// Internal Helpers
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Result of flattening a pipeline of commands.
|
||||
*
|
||||
* @category models
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export interface FlattenedPipeline {
|
||||
readonly commands: Arr.NonEmptyReadonlyArray<ChildProcess.StandardCommand>
|
||||
readonly pipeOptions: ReadonlyArray<ChildProcess.PipeOptions>
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens a `Command` into an array of `StandardCommand`s along with pipe
|
||||
* options for each connection.
|
||||
*
|
||||
* @category transforming
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const flattenCommand = (
|
||||
command: ChildProcess.Command
|
||||
): FlattenedPipeline => {
|
||||
const commands: Array<ChildProcess.StandardCommand> = []
|
||||
const pipeOptions: Array<ChildProcess.PipeOptions> = []
|
||||
|
||||
const flatten = (cmd: ChildProcess.Command): void => {
|
||||
switch (cmd._tag) {
|
||||
case "StandardCommand": {
|
||||
commands.push(cmd)
|
||||
break
|
||||
}
|
||||
case "PipedCommand": {
|
||||
// Recursively flatten left side first
|
||||
flatten(cmd.left)
|
||||
// Store the pipe options for this connection
|
||||
pipeOptions.push(cmd.options)
|
||||
// Then flatten right side
|
||||
flatten(cmd.right)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flatten(command)
|
||||
|
||||
// The commands array is guaranteed to be non-empty since we always have at
|
||||
// least one command in the input. We validate this at runtime and return a
|
||||
// properly typed tuple.
|
||||
if (commands.length === 0) {
|
||||
// This should never happen given a valid Command input
|
||||
throw new Error("flattenCommand produced empty commands array")
|
||||
}
|
||||
|
||||
const [first, ...rest] = commands
|
||||
const nonEmptyCommands: Arr.NonEmptyReadonlyArray<ChildProcess.StandardCommand> = [first, ...rest]
|
||||
|
||||
return { commands: nonEmptyCommands, pipeOptions }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Node TCP socket transport for Effect Cluster runner-to-runner RPC.
|
||||
*
|
||||
* This module provides the shared Node layers used by socket-based cluster
|
||||
* transports. `layerClientProtocol` opens TCP sockets to peer runner addresses
|
||||
* and wraps them in the current RPC serialization protocol. `layerSocketServer`
|
||||
* exposes the socket server that receives incoming runner RPC traffic.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Option from "effect/Option"
|
||||
import * as Runners from "effect/unstable/cluster/Runners"
|
||||
import * as ShardingConfig from "effect/unstable/cluster/ShardingConfig"
|
||||
import * as RpcClient from "effect/unstable/rpc/RpcClient"
|
||||
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization"
|
||||
import { Socket } from "effect/unstable/socket/Socket"
|
||||
import type * as SocketServer from "effect/unstable/socket/SocketServer"
|
||||
import * as NodeSocket from "./NodeSocket.ts"
|
||||
import * as NodeSocketServer from "./NodeSocketServer.ts"
|
||||
|
||||
/**
|
||||
* Provides the cluster `RpcClientProtocol` by opening TCP sockets to runner
|
||||
* addresses and using the current RPC serialization service.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerClientProtocol: Layer.Layer<
|
||||
Runners.RpcClientProtocol,
|
||||
never,
|
||||
RpcSerialization.RpcSerialization
|
||||
> = Layer.effect(Runners.RpcClientProtocol)(
|
||||
Effect.gen(function*() {
|
||||
const serialization = yield* RpcSerialization.RpcSerialization
|
||||
return Effect.fnUntraced(function*(address) {
|
||||
const socket = yield* NodeSocket.makeNet({
|
||||
openTimeout: 1000,
|
||||
timeout: 5500,
|
||||
host: address.host,
|
||||
port: address.port
|
||||
})
|
||||
return yield* RpcClient.makeProtocolSocket().pipe(
|
||||
Effect.provideService(Socket, socket),
|
||||
Effect.provideService(RpcSerialization.RpcSerialization, serialization)
|
||||
)
|
||||
}, Effect.orDie)
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Provides the socket server used by cluster runners, listening on
|
||||
* `ShardingConfig.runnerListenAddress` or `runnerAddress`.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerSocketServer: Layer.Layer<
|
||||
SocketServer.SocketServer,
|
||||
SocketServer.SocketServerError,
|
||||
ShardingConfig.ShardingConfig
|
||||
> = Effect.gen(function*() {
|
||||
const config = yield* ShardingConfig.ShardingConfig
|
||||
const listenAddress = Option.orElse(config.runnerListenAddress, () => config.runnerAddress)
|
||||
if (Option.isNone(listenAddress)) {
|
||||
return yield* Effect.die("layerSocketServer: ShardingConfig.runnerListenAddress is None")
|
||||
}
|
||||
return NodeSocketServer.layer(listenAddress.value)
|
||||
}).pipe(Layer.unwrap)
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Node-compatible implementation of Effect's `Crypto` service.
|
||||
*
|
||||
* This module builds the service from `node:crypto`, using `randomBytes` for
|
||||
* random data and `createHash` for supported digest algorithms. It exports
|
||||
* `make` as the concrete service value and `layer` for providing it through
|
||||
* Effect context.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
import * as EffectCrypto from "effect/Crypto"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as PlatformError from "effect/PlatformError"
|
||||
import * as NodeCrypto from "node:crypto"
|
||||
|
||||
const toHashAlgorithm = (algorithm: EffectCrypto.DigestAlgorithm): string => {
|
||||
switch (algorithm) {
|
||||
case "SHA-1":
|
||||
return "sha1"
|
||||
case "SHA-256":
|
||||
return "sha256"
|
||||
case "SHA-384":
|
||||
return "sha384"
|
||||
case "SHA-512":
|
||||
return "sha512"
|
||||
}
|
||||
}
|
||||
|
||||
const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) =>
|
||||
Effect.try({
|
||||
try: () => Uint8Array.from(NodeCrypto.createHash(toHashAlgorithm(algorithm)).update(data).digest()),
|
||||
catch: (cause) =>
|
||||
PlatformError.systemError({
|
||||
module: "Crypto",
|
||||
method: "digest",
|
||||
_tag: "Unknown",
|
||||
description: "Could not compute digest",
|
||||
cause
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* The default Node.js Crypto service implementation.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 1.0.0
|
||||
*/
|
||||
export const make: EffectCrypto.Crypto = EffectCrypto.make({
|
||||
randomBytes: NodeCrypto.randomBytes,
|
||||
digest
|
||||
})
|
||||
|
||||
/**
|
||||
* Layer that provides the Node.js Crypto service implementation.
|
||||
*
|
||||
* @category layers
|
||||
* @since 1.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<EffectCrypto.Crypto> = Layer.succeed(EffectCrypto.Crypto, make)
|
||||
@@ -0,0 +1,668 @@
|
||||
/**
|
||||
* Shared Node-compatible implementation of Effect's `FileSystem` service.
|
||||
*
|
||||
* This module adapts Node's `node:fs`, `node:os`, and `node:path` APIs into a
|
||||
* `FileSystem` layer for Effect programs running on Node-compatible runtimes.
|
||||
* Platform packages use it to provide file and directory I/O, permissions,
|
||||
* links, metadata, temporary files and directories, and file watching through
|
||||
* the shared `FileSystem` service.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Cause from "effect/Cause"
|
||||
import * as Effect from "effect/Effect"
|
||||
import { effectify } from "effect/Effect"
|
||||
import * as FileSystem from "effect/FileSystem"
|
||||
import { pipe } from "effect/Function"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Option from "effect/Option"
|
||||
import * as Error from "effect/PlatformError"
|
||||
import * as Queue from "effect/Queue"
|
||||
import * as Stream from "effect/Stream"
|
||||
import * as Crypto from "node:crypto"
|
||||
import * as NFS from "node:fs"
|
||||
import * as OS from "node:os"
|
||||
import * as Path from "node:path"
|
||||
import { handleErrnoException } from "./internal/utils.ts"
|
||||
|
||||
const handleBadArgument = (method: string) => (err: unknown) =>
|
||||
Error.badArgument({
|
||||
module: "FileSystem",
|
||||
method,
|
||||
description: (err as Error).message ?? String(err)
|
||||
})
|
||||
|
||||
// == access
|
||||
|
||||
const access = ((): FileSystem.FileSystem["access"] => {
|
||||
const nodeAccess = effectify(
|
||||
NFS.access,
|
||||
handleErrnoException("FileSystem", "access"),
|
||||
handleBadArgument("access")
|
||||
)
|
||||
return (path, options) => {
|
||||
let mode = NFS.constants.F_OK
|
||||
if (options?.readable) {
|
||||
mode |= NFS.constants.R_OK
|
||||
}
|
||||
if (options?.writable) {
|
||||
mode |= NFS.constants.W_OK
|
||||
}
|
||||
return nodeAccess(path, mode)
|
||||
}
|
||||
})()
|
||||
|
||||
// == copy
|
||||
|
||||
const copy = ((): FileSystem.FileSystem["copy"] => {
|
||||
const nodeCp = effectify(
|
||||
NFS.cp,
|
||||
handleErrnoException("FileSystem", "copy"),
|
||||
handleBadArgument("copy")
|
||||
)
|
||||
return (fromPath, toPath, options) =>
|
||||
nodeCp(fromPath, toPath, {
|
||||
force: options?.overwrite ?? false,
|
||||
preserveTimestamps: options?.preserveTimestamps ?? false,
|
||||
recursive: true
|
||||
})
|
||||
})()
|
||||
|
||||
// == copyFile
|
||||
|
||||
const copyFile = (() => {
|
||||
const nodeCopyFile = effectify(
|
||||
NFS.copyFile,
|
||||
handleErrnoException("FileSystem", "copyFile"),
|
||||
handleBadArgument("copyFile")
|
||||
)
|
||||
return (fromPath: string, toPath: string) => nodeCopyFile(fromPath, toPath)
|
||||
})()
|
||||
|
||||
// == chmod
|
||||
|
||||
const chmod = (() => {
|
||||
const nodeChmod = effectify(
|
||||
NFS.chmod,
|
||||
handleErrnoException("FileSystem", "chmod"),
|
||||
handleBadArgument("chmod")
|
||||
)
|
||||
return (path: string, mode: number) => nodeChmod(path, mode)
|
||||
})()
|
||||
|
||||
// == chown
|
||||
|
||||
const chown = (() => {
|
||||
const nodeChown = effectify(
|
||||
NFS.chown,
|
||||
handleErrnoException("FileSystem", "chown"),
|
||||
handleBadArgument("chown")
|
||||
)
|
||||
return (path: string, uid: number, gid: number) => nodeChown(path, uid, gid)
|
||||
})()
|
||||
|
||||
// == glob
|
||||
|
||||
const glob = ((): FileSystem.FileSystem["glob"] => {
|
||||
const nodeGlob = effectify(
|
||||
NFS.glob,
|
||||
handleErrnoException("FileSystem", "glob"),
|
||||
handleBadArgument("glob")
|
||||
)
|
||||
return (pattern: string, options) =>
|
||||
nodeGlob(pattern, {
|
||||
cwd: options?.root,
|
||||
exclude: options?.exclude
|
||||
})
|
||||
})()
|
||||
|
||||
// == link
|
||||
|
||||
const link = (() => {
|
||||
const nodeLink = effectify(
|
||||
NFS.link,
|
||||
handleErrnoException("FileSystem", "link"),
|
||||
handleBadArgument("link")
|
||||
)
|
||||
return (existingPath: string, newPath: string) => nodeLink(existingPath, newPath)
|
||||
})()
|
||||
|
||||
// == makeDirectory
|
||||
|
||||
const makeDirectory = ((): FileSystem.FileSystem["makeDirectory"] => {
|
||||
const nodeMkdir = effectify(
|
||||
NFS.mkdir,
|
||||
handleErrnoException("FileSystem", "makeDirectory"),
|
||||
handleBadArgument("makeDirectory")
|
||||
)
|
||||
return (path, options) =>
|
||||
nodeMkdir(path, {
|
||||
recursive: options?.recursive ?? false,
|
||||
mode: options?.mode
|
||||
})
|
||||
})()
|
||||
|
||||
// == makeTempDirectory
|
||||
|
||||
const makeTempDirectoryFactory = (method: string): FileSystem.FileSystem["makeTempDirectory"] => {
|
||||
const nodeMkdtemp = effectify(
|
||||
NFS.mkdtemp,
|
||||
handleErrnoException("FileSystem", method),
|
||||
handleBadArgument(method)
|
||||
)
|
||||
return (options) =>
|
||||
Effect.suspend(() => {
|
||||
const prefix = options?.prefix ?? ""
|
||||
const directory = typeof options?.directory === "string"
|
||||
? Path.join(options.directory, ".")
|
||||
: OS.tmpdir()
|
||||
|
||||
return nodeMkdtemp(prefix ? Path.join(directory, prefix) : directory + "/")
|
||||
})
|
||||
}
|
||||
const makeTempDirectory = makeTempDirectoryFactory("makeTempDirectory")
|
||||
|
||||
// == remove
|
||||
|
||||
const removeFactory = (method: string): FileSystem.FileSystem["remove"] => {
|
||||
const nodeRm = effectify(
|
||||
NFS.rm,
|
||||
handleErrnoException("FileSystem", method),
|
||||
handleBadArgument(method)
|
||||
)
|
||||
return (path, options) =>
|
||||
nodeRm(
|
||||
path,
|
||||
{ recursive: options?.recursive ?? false, force: options?.force ?? false }
|
||||
)
|
||||
}
|
||||
const remove = removeFactory("remove")
|
||||
|
||||
// == makeTempDirectoryScoped
|
||||
|
||||
const makeTempDirectoryScoped = ((): FileSystem.FileSystem["makeTempDirectoryScoped"] => {
|
||||
const makeDirectory = makeTempDirectoryFactory("makeTempDirectoryScoped")
|
||||
const removeDirectory = removeFactory("makeTempDirectoryScoped")
|
||||
return (options) =>
|
||||
Effect.acquireRelease(
|
||||
makeDirectory(options),
|
||||
(directory) => Effect.orDie(removeDirectory(directory, { recursive: true }))
|
||||
)
|
||||
})()
|
||||
|
||||
// == open
|
||||
|
||||
const openFactory = (method: string): FileSystem.FileSystem["open"] => {
|
||||
const nodeOpen = effectify(
|
||||
NFS.open,
|
||||
handleErrnoException("FileSystem", method),
|
||||
handleBadArgument(method)
|
||||
)
|
||||
const nodeClose = effectify(
|
||||
NFS.close,
|
||||
handleErrnoException("FileSystem", method),
|
||||
handleBadArgument(method)
|
||||
)
|
||||
|
||||
return (path, options) =>
|
||||
pipe(
|
||||
Effect.acquireRelease(
|
||||
nodeOpen(path, options?.flag ?? "r", options?.mode),
|
||||
(fd) => Effect.orDie(nodeClose(fd))
|
||||
),
|
||||
Effect.map((fd) => makeFile(FileSystem.FileDescriptor(fd), options?.flag?.startsWith("a") ?? false))
|
||||
)
|
||||
}
|
||||
const open = openFactory("open")
|
||||
|
||||
const makeFile = (() => {
|
||||
const nodeReadFactory = (method: string) =>
|
||||
effectify(
|
||||
NFS.read,
|
||||
handleErrnoException("FileSystem", method),
|
||||
handleBadArgument(method)
|
||||
)
|
||||
const nodeRead = nodeReadFactory("read")
|
||||
const nodeReadAlloc = nodeReadFactory("readAlloc")
|
||||
const nodeStat = effectify(
|
||||
NFS.fstat,
|
||||
handleErrnoException("FileSystem", "stat"),
|
||||
handleBadArgument("stat")
|
||||
)
|
||||
const nodeTruncate = effectify(
|
||||
NFS.ftruncate,
|
||||
handleErrnoException("FileSystem", "truncate"),
|
||||
handleBadArgument("truncate")
|
||||
)
|
||||
|
||||
const nodeSync = effectify(
|
||||
NFS.fsync,
|
||||
handleErrnoException("FileSystem", "sync"),
|
||||
handleBadArgument("sync")
|
||||
)
|
||||
|
||||
const nodeWriteFactory = (method: string) =>
|
||||
effectify(
|
||||
NFS.write,
|
||||
handleErrnoException("FileSystem", method),
|
||||
handleBadArgument(method)
|
||||
)
|
||||
const nodeWrite = nodeWriteFactory("write")
|
||||
const nodeWriteAll = nodeWriteFactory("writeAll")
|
||||
|
||||
class FileImpl implements FileSystem.File {
|
||||
readonly [FileSystem.FileTypeId]: typeof FileSystem.FileTypeId
|
||||
readonly fd: FileSystem.File.Descriptor
|
||||
private readonly append: boolean
|
||||
|
||||
private position: bigint = BigInt(0)
|
||||
|
||||
constructor(
|
||||
fd: FileSystem.File.Descriptor,
|
||||
append: boolean
|
||||
) {
|
||||
this[FileSystem.FileTypeId] = FileSystem.FileTypeId
|
||||
this.fd = fd
|
||||
this.append = append
|
||||
}
|
||||
|
||||
get stat() {
|
||||
return Effect.map(nodeStat(this.fd), makeFileInfo)
|
||||
}
|
||||
|
||||
get sync() {
|
||||
return nodeSync(this.fd)
|
||||
}
|
||||
|
||||
seek(offset: FileSystem.SizeInput, from: FileSystem.SeekMode) {
|
||||
const offsetSize = FileSystem.Size(offset)
|
||||
return Effect.sync(() => {
|
||||
if (from === "start") {
|
||||
this.position = offsetSize
|
||||
} else if (from === "current") {
|
||||
this.position = this.position + offsetSize
|
||||
}
|
||||
|
||||
return this.position
|
||||
})
|
||||
}
|
||||
|
||||
read(buffer: Uint8Array) {
|
||||
return Effect.suspend(() => {
|
||||
const position = this.position
|
||||
return Effect.map(
|
||||
nodeRead(this.fd, { buffer, position }),
|
||||
(bytesRead) => {
|
||||
const sizeRead = FileSystem.Size(bytesRead)
|
||||
this.position = position + sizeRead
|
||||
return sizeRead
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
readAlloc(size: FileSystem.SizeInput) {
|
||||
const sizeNumber = Number(size)
|
||||
return Effect.suspend(() => {
|
||||
const buffer = Buffer.allocUnsafeSlow(sizeNumber)
|
||||
const position = this.position
|
||||
return Effect.map(
|
||||
nodeReadAlloc(this.fd, { buffer, position }),
|
||||
(bytesRead): Option.Option<Buffer> => {
|
||||
if (bytesRead === 0) {
|
||||
return Option.none()
|
||||
}
|
||||
|
||||
this.position = position + BigInt(bytesRead)
|
||||
if (bytesRead === sizeNumber) {
|
||||
return Option.some(buffer)
|
||||
}
|
||||
|
||||
const dst = Buffer.allocUnsafeSlow(bytesRead)
|
||||
buffer.copy(dst, 0, 0, bytesRead)
|
||||
return Option.some(dst)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
truncate(length?: FileSystem.SizeInput) {
|
||||
return Effect.map(nodeTruncate(this.fd, length ? Number(length) : undefined), () => {
|
||||
if (!this.append) {
|
||||
const len = BigInt(length ?? 0)
|
||||
if (this.position > len) {
|
||||
this.position = len
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
write(buffer: Uint8Array) {
|
||||
return Effect.suspend(() => {
|
||||
const position = this.position
|
||||
return Effect.map(
|
||||
nodeWrite(this.fd, buffer, undefined, undefined, this.append ? undefined : Number(position)),
|
||||
(bytesWritten) => {
|
||||
const sizeWritten = FileSystem.Size(bytesWritten)
|
||||
if (!this.append) {
|
||||
this.position = position + sizeWritten
|
||||
}
|
||||
return sizeWritten
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private writeAllChunk(buffer: Uint8Array): Effect.Effect<void, Error.PlatformError> {
|
||||
return Effect.suspend(() => {
|
||||
const position = this.position
|
||||
return Effect.flatMap(
|
||||
nodeWriteAll(this.fd, buffer, undefined, undefined, this.append ? undefined : Number(position)),
|
||||
(bytesWritten) => {
|
||||
if (bytesWritten === 0) {
|
||||
return Effect.fail(
|
||||
Error.systemError({
|
||||
module: "FileSystem",
|
||||
method: "writeAll",
|
||||
_tag: "WriteZero",
|
||||
pathOrDescriptor: this.fd,
|
||||
description: "write returned 0 bytes written"
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.append) {
|
||||
this.position = position + BigInt(bytesWritten)
|
||||
}
|
||||
|
||||
return bytesWritten < buffer.length ? this.writeAllChunk(buffer.subarray(bytesWritten)) : Effect.void
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
writeAll(buffer: Uint8Array) {
|
||||
return this.writeAllChunk(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
return (fd: FileSystem.File.Descriptor, append: boolean): FileSystem.File => new FileImpl(fd, append)
|
||||
})()
|
||||
|
||||
// == makeTempFile
|
||||
|
||||
const makeTempFileFactory = (method: string): FileSystem.FileSystem["makeTempFile"] => {
|
||||
const makeDirectory = makeTempDirectoryFactory(method)
|
||||
return Effect.fnUntraced(function*(options) {
|
||||
const directory = yield* makeDirectory(options)
|
||||
const random = Crypto.randomBytes(6).toString("hex")
|
||||
const name = Path.join(directory, options?.suffix ? `${random}${options.suffix}` : random)
|
||||
yield* writeFile(name, new Uint8Array(0))
|
||||
return name
|
||||
})
|
||||
}
|
||||
const makeTempFile = makeTempFileFactory("makeTempFile")
|
||||
|
||||
// == makeTempFileScoped
|
||||
|
||||
const makeTempFileScoped = ((): FileSystem.FileSystem["makeTempFileScoped"] => {
|
||||
const makeFile = makeTempFileFactory("makeTempFileScoped")
|
||||
const removeDirectory = removeFactory("makeTempFileScoped")
|
||||
return (options) =>
|
||||
Effect.acquireRelease(
|
||||
makeFile(options),
|
||||
(file) => Effect.orDie(removeDirectory(Path.dirname(file), { recursive: true }))
|
||||
)
|
||||
})()
|
||||
|
||||
// == readDirectory
|
||||
|
||||
const readDirectory: FileSystem.FileSystem["readDirectory"] = (path, options) =>
|
||||
Effect.tryPromise({
|
||||
try: () => NFS.promises.readdir(path, options),
|
||||
catch: (err) => handleErrnoException("FileSystem", "readDirectory")(err as any, [path])
|
||||
})
|
||||
|
||||
// == readFile
|
||||
|
||||
const readFile = (path: string) =>
|
||||
Effect.callback<Uint8Array, Error.PlatformError>((resume, signal) => {
|
||||
try {
|
||||
NFS.readFile(path, { signal }, (err, data) => {
|
||||
if (err) {
|
||||
resume(Effect.fail(handleErrnoException("FileSystem", "readFile")(err, [path])))
|
||||
} else {
|
||||
resume(Effect.succeed(data))
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
resume(Effect.fail(handleBadArgument("readFile")(err)))
|
||||
}
|
||||
})
|
||||
|
||||
// == readLink
|
||||
|
||||
const readLink = (() => {
|
||||
const nodeReadLink = effectify(
|
||||
NFS.readlink,
|
||||
handleErrnoException("FileSystem", "readLink"),
|
||||
handleBadArgument("readLink")
|
||||
)
|
||||
return (path: string) => nodeReadLink(path)
|
||||
})()
|
||||
|
||||
// == realPath
|
||||
|
||||
const realPath = (() => {
|
||||
const nodeRealPath = effectify(
|
||||
NFS.realpath,
|
||||
handleErrnoException("FileSystem", "realPath"),
|
||||
handleBadArgument("realPath")
|
||||
)
|
||||
return (path: string) => nodeRealPath(path)
|
||||
})()
|
||||
|
||||
// == rename
|
||||
|
||||
const rename = (() => {
|
||||
const nodeRename = effectify(
|
||||
NFS.rename,
|
||||
handleErrnoException("FileSystem", "rename"),
|
||||
handleBadArgument("rename")
|
||||
)
|
||||
return (oldPath: string, newPath: string) => nodeRename(oldPath, newPath)
|
||||
})()
|
||||
|
||||
// == stat
|
||||
|
||||
const makeFileInfo = (stat: NFS.Stats): FileSystem.File.Info => ({
|
||||
type: stat.isFile() ?
|
||||
"File" :
|
||||
stat.isDirectory() ?
|
||||
"Directory" :
|
||||
stat.isSymbolicLink() ?
|
||||
"SymbolicLink" :
|
||||
stat.isBlockDevice() ?
|
||||
"BlockDevice" :
|
||||
stat.isCharacterDevice() ?
|
||||
"CharacterDevice" :
|
||||
stat.isFIFO() ?
|
||||
"FIFO" :
|
||||
stat.isSocket() ?
|
||||
"Socket" :
|
||||
"Unknown",
|
||||
mtime: Option.fromNullishOr(stat.mtime),
|
||||
atime: Option.fromNullishOr(stat.atime),
|
||||
birthtime: Option.fromNullishOr(stat.birthtime),
|
||||
dev: stat.dev,
|
||||
rdev: Option.fromNullishOr(stat.rdev),
|
||||
ino: Option.fromNullishOr(stat.ino),
|
||||
mode: stat.mode,
|
||||
nlink: Option.fromNullishOr(stat.nlink),
|
||||
uid: Option.fromNullishOr(stat.uid),
|
||||
gid: Option.fromNullishOr(stat.gid),
|
||||
size: FileSystem.Size(stat.size),
|
||||
blksize: stat.blksize !== undefined ? Option.some(FileSystem.Size(stat.blksize)) : Option.none(),
|
||||
blocks: Option.fromNullishOr(stat.blocks)
|
||||
})
|
||||
const stat = (() => {
|
||||
const nodeStat = effectify(
|
||||
NFS.stat,
|
||||
handleErrnoException("FileSystem", "stat"),
|
||||
handleBadArgument("stat")
|
||||
)
|
||||
return (path: string) => Effect.map(nodeStat(path), makeFileInfo)
|
||||
})()
|
||||
|
||||
// == symlink
|
||||
|
||||
const symlink = (() => {
|
||||
const nodeSymlink = effectify(
|
||||
NFS.symlink,
|
||||
handleErrnoException("FileSystem", "symlink"),
|
||||
handleBadArgument("symlink")
|
||||
)
|
||||
return (target: string, path: string) => nodeSymlink(target, path)
|
||||
})()
|
||||
|
||||
// == truncate
|
||||
|
||||
const truncate = (() => {
|
||||
const nodeTruncate = effectify(
|
||||
NFS.truncate,
|
||||
handleErrnoException("FileSystem", "truncate"),
|
||||
handleBadArgument("truncate")
|
||||
)
|
||||
return (path: string, length?: FileSystem.SizeInput) =>
|
||||
nodeTruncate(path, length !== undefined ? Number(length) : undefined)
|
||||
})()
|
||||
|
||||
// == utimes
|
||||
|
||||
const utimes = (() => {
|
||||
const nodeUtimes = effectify(
|
||||
NFS.utimes,
|
||||
handleErrnoException("FileSystem", "utime"),
|
||||
handleBadArgument("utime")
|
||||
)
|
||||
return (path: string, atime: number | Date, mtime: number | Date) => nodeUtimes(path, atime, mtime)
|
||||
})()
|
||||
|
||||
// == watch
|
||||
|
||||
const watchNode = (path: string) =>
|
||||
Stream.callback<FileSystem.WatchEvent, Error.PlatformError>((queue) =>
|
||||
Effect.acquireRelease(
|
||||
Effect.sync(() => {
|
||||
const watcher = NFS.watch(path, {
|
||||
recursive: true
|
||||
}, (event, path) => {
|
||||
if (!path) return
|
||||
switch (event) {
|
||||
case "rename": {
|
||||
Effect.runFork(Effect.matchEffect(stat(path), {
|
||||
onSuccess: (_) => Queue.offer(queue, { _tag: "Create", path }),
|
||||
onFailure: (_) => Queue.offer(queue, { _tag: "Remove", path })
|
||||
}))
|
||||
return
|
||||
}
|
||||
case "change": {
|
||||
Queue.offerUnsafe(queue, { _tag: "Update", path })
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
watcher.on("error", (error) => {
|
||||
Queue.failCauseUnsafe(
|
||||
queue,
|
||||
Cause.fail(
|
||||
Error.systemError({
|
||||
module: "FileSystem",
|
||||
_tag: "Unknown",
|
||||
method: "watch",
|
||||
pathOrDescriptor: path,
|
||||
cause: error
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
watcher.on("close", () => {
|
||||
Queue.endUnsafe(queue)
|
||||
})
|
||||
return watcher
|
||||
}),
|
||||
(watcher) => Effect.sync(() => watcher.close())
|
||||
)
|
||||
)
|
||||
|
||||
const watch = (backend: Option.Option<FileSystem.WatchBackend["Service"]>, path: string) =>
|
||||
stat(path).pipe(
|
||||
Effect.map((stat) =>
|
||||
backend.pipe(
|
||||
Option.flatMap((_) => _.register(path, stat)),
|
||||
Option.getOrElse(() => watchNode(path))
|
||||
)
|
||||
),
|
||||
Stream.unwrap
|
||||
)
|
||||
|
||||
// == writeFile
|
||||
|
||||
const writeFile: FileSystem.FileSystem["writeFile"] = (path, data, options) =>
|
||||
Effect.callback<void, Error.PlatformError>((resume, signal) => {
|
||||
try {
|
||||
NFS.writeFile(path, data, {
|
||||
signal,
|
||||
flag: options?.flag,
|
||||
mode: options?.mode
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
resume(Effect.fail(handleErrnoException("FileSystem", "writeFile")(err, [path])))
|
||||
} else {
|
||||
resume(Effect.void)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
resume(Effect.fail(handleBadArgument("writeFile")(err)))
|
||||
}
|
||||
})
|
||||
|
||||
const makeFileSystem = Effect.map(Effect.serviceOption(FileSystem.WatchBackend), (backend) =>
|
||||
FileSystem.make({
|
||||
access,
|
||||
chmod,
|
||||
chown,
|
||||
copy,
|
||||
copyFile,
|
||||
glob,
|
||||
link,
|
||||
makeDirectory,
|
||||
makeTempDirectory,
|
||||
makeTempDirectoryScoped,
|
||||
makeTempFile,
|
||||
makeTempFileScoped,
|
||||
open,
|
||||
readDirectory,
|
||||
readFile,
|
||||
readLink,
|
||||
realPath,
|
||||
remove,
|
||||
rename,
|
||||
stat,
|
||||
symlink,
|
||||
truncate,
|
||||
utimes,
|
||||
watch(path) {
|
||||
return watch(backend, path)
|
||||
},
|
||||
writeFile
|
||||
}))
|
||||
|
||||
/**
|
||||
* Provides the `FileSystem` service backed by Node filesystem APIs, including
|
||||
* file operations, directory operations, links, metadata, and file watching.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<FileSystem.FileSystem> = Layer.effect(FileSystem.FileSystem)(makeFileSystem)
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Node-backed provider for Effect's `Path` service.
|
||||
*
|
||||
* This module turns Node's `node:path` and `node:url` APIs into `Path` layers.
|
||||
* `layer` uses the host platform path implementation, while `layerPosix` and
|
||||
* `layerWin32` provide fixed POSIX and Windows variants. All three layers also
|
||||
* include helpers for converting between file paths and file URLs.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import { Path, TypeId } from "effect/Path"
|
||||
import { BadArgument } from "effect/PlatformError"
|
||||
import * as NodePath from "node:path"
|
||||
import * as NodeUrl from "node:url"
|
||||
|
||||
const fromFileUrl = (url: URL): Effect.Effect<string, BadArgument> =>
|
||||
Effect.try({
|
||||
try: () => NodeUrl.fileURLToPath(url),
|
||||
catch: (cause) =>
|
||||
new BadArgument({
|
||||
module: "Path",
|
||||
method: "fromFileUrl",
|
||||
cause
|
||||
})
|
||||
})
|
||||
|
||||
const toFileUrl = (path: string): Effect.Effect<URL, BadArgument> =>
|
||||
Effect.try({
|
||||
try: () => NodeUrl.pathToFileURL(path),
|
||||
catch: (cause) =>
|
||||
new BadArgument({
|
||||
module: "Path",
|
||||
method: "toFileUrl",
|
||||
cause
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides the `Path` service using Node's POSIX path implementation plus
|
||||
* file URL conversion helpers.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerPosix: Layer.Layer<Path> = Layer.succeed(Path)({
|
||||
[TypeId]: TypeId,
|
||||
...NodePath.posix,
|
||||
fromFileUrl,
|
||||
toFileUrl
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides the `Path` service using Node's Windows path implementation plus
|
||||
* file URL conversion helpers.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerWin32: Layer.Layer<Path> = Layer.succeed(Path)({
|
||||
[TypeId]: TypeId,
|
||||
...NodePath.win32,
|
||||
fromFileUrl,
|
||||
toFileUrl
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides the default `Path` service using the host platform's Node path
|
||||
* implementation plus file URL conversion helpers.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<Path> = Layer.succeed(Path)({
|
||||
[TypeId]: TypeId,
|
||||
...NodePath,
|
||||
fromFileUrl,
|
||||
toFileUrl
|
||||
})
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Node-compatible process runner for Effect programs.
|
||||
*
|
||||
* This module provides the shared `runMain` implementation used by
|
||||
* Node-compatible platform packages. It runs one Effect as the main process
|
||||
* fiber, interrupts that fiber on `SIGINT` or `SIGTERM`, and delegates final
|
||||
* exit-code handling to the configured teardown.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type { Effect } from "effect/Effect"
|
||||
import * as Runtime from "effect/Runtime"
|
||||
|
||||
/**
|
||||
* Runs an Effect as the Node process main program, interrupting the fiber on
|
||||
* `SIGINT` or `SIGTERM` and invoking the configured teardown to determine the
|
||||
* process exit code.
|
||||
*
|
||||
* @category running
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const runMain: {
|
||||
(
|
||||
options?: {
|
||||
readonly disableErrorReporting?: boolean | undefined
|
||||
readonly teardown?: Runtime.Teardown | undefined
|
||||
}
|
||||
): <E, A>(effect: Effect<A, E>) => void
|
||||
<E, A>(
|
||||
effect: Effect<A, E>,
|
||||
options?: {
|
||||
readonly disableErrorReporting?: boolean | undefined
|
||||
readonly teardown?: Runtime.Teardown | undefined
|
||||
}
|
||||
): void
|
||||
} = Runtime.makeRunMain(({
|
||||
fiber,
|
||||
teardown
|
||||
}) => {
|
||||
let receivedSignal = false
|
||||
|
||||
fiber.addObserver((exit) => {
|
||||
process.removeListener("SIGINT", onSigint)
|
||||
process.removeListener("SIGTERM", onSigint)
|
||||
teardown(exit, (code) => {
|
||||
if (receivedSignal || code !== 0) {
|
||||
process.exit(code)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function onSigint() {
|
||||
receivedSignal = true
|
||||
fiber.interruptUnsafe(fiber.id)
|
||||
}
|
||||
|
||||
process.on("SIGINT", onSigint)
|
||||
process.on("SIGTERM", onSigint)
|
||||
})
|
||||
116
repos/effect-smol/packages/platform-node-shared/src/NodeSink.ts
Normal file
116
repos/effect-smol/packages/platform-node-shared/src/NodeSink.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Sink adapters for writing Effect chunks into Node writable streams.
|
||||
*
|
||||
* `fromWritable` creates a `Sink`, `fromWritableChannel` creates a lower-level
|
||||
* `Channel`, and `pullIntoWritable` writes from an existing pull loop. All
|
||||
* three adapters respect writable-stream backpressure, map writable errors with
|
||||
* the supplied `onError` function, and can end the writable when the upstream
|
||||
* data is done.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type { NonEmptyReadonlyArray } from "effect/Array"
|
||||
import * as Cause from "effect/Cause"
|
||||
import * as Channel from "effect/Channel"
|
||||
import * as Effect from "effect/Effect"
|
||||
import { identity, type LazyArg } from "effect/Function"
|
||||
import * as Pull from "effect/Pull"
|
||||
import * as Sink from "effect/Sink"
|
||||
import type { Writable } from "node:stream"
|
||||
|
||||
/**
|
||||
* Creates a `Sink` that writes chunks to a Node writable stream, respecting
|
||||
* backpressure, mapping writable errors with `onError`, and ending the stream
|
||||
* on completion unless `endOnDone` is `false`.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromWritable = <E, A = Uint8Array | string>(
|
||||
options: {
|
||||
readonly evaluate: LazyArg<Writable | NodeJS.WritableStream>
|
||||
readonly onError: (error: unknown) => E
|
||||
readonly endOnDone?: boolean | undefined
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
}
|
||||
): Sink.Sink<void, A, never, E> =>
|
||||
Sink.fromChannel(Channel.mapDone(fromWritableChannel<never, E, A>(options), (_) => [_]))
|
||||
|
||||
/**
|
||||
* Creates a `Channel` that pulls chunks from upstream and writes them to a
|
||||
* Node writable stream, respecting backpressure and optionally ending the
|
||||
* writable when upstream is done.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromWritableChannel = <IE, E, A = Uint8Array | string>(
|
||||
options: {
|
||||
readonly evaluate: LazyArg<Writable | NodeJS.WritableStream>
|
||||
readonly onError: (error: unknown) => E
|
||||
readonly endOnDone?: boolean | undefined
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
}
|
||||
): Channel.Channel<never, IE | E, void, NonEmptyReadonlyArray<A>, IE> =>
|
||||
Channel.fromTransform((pull: Pull.Pull<NonEmptyReadonlyArray<A>, IE, unknown>) => {
|
||||
const writable = options.evaluate() as Writable
|
||||
return Effect.succeed(pullIntoWritable({ ...options, writable, pull }))
|
||||
})
|
||||
|
||||
/**
|
||||
* Writes Effect chunks into a Node writable stream.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use to implement custom Node stream adapters that already have an upstream
|
||||
* pull and need direct control over a writable stream.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* The loop waits for `drain` when needed, fails on writable errors, and ends
|
||||
* the writable on upstream completion unless `endOnDone` is `false`.
|
||||
*
|
||||
* @category converting
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const pullIntoWritable = <A, IE, E>(options: {
|
||||
readonly pull: Pull.Pull<NonEmptyReadonlyArray<A>, IE, unknown>
|
||||
readonly writable: Writable
|
||||
readonly onError: (error: unknown) => E
|
||||
readonly endOnDone?: boolean | undefined
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
}): Pull.Pull<never, IE | E, unknown> =>
|
||||
options.pull.pipe(
|
||||
Effect.flatMap((chunk) => {
|
||||
let i = 0
|
||||
return Effect.callback<void, E>(function loop(resume) {
|
||||
for (; i < chunk.length;) {
|
||||
const success = options.writable.write(chunk[i++], options.encoding as any)
|
||||
if (!success) {
|
||||
options.writable.once("drain", () => (loop as any)(resume))
|
||||
return
|
||||
}
|
||||
}
|
||||
resume(Effect.void)
|
||||
})
|
||||
}),
|
||||
Effect.forever({ disableYield: true }),
|
||||
Effect.raceFirst(Effect.callback<never, E>((resume) => {
|
||||
const onError = (error: unknown) => resume(Effect.fail(options.onError(error)))
|
||||
options.writable.once("error", onError)
|
||||
return Effect.sync(() => {
|
||||
options.writable.off("error", onError)
|
||||
})
|
||||
})),
|
||||
options.endOnDone !== false ?
|
||||
Pull.catchDone((_) => {
|
||||
if ("closed" in options.writable && options.writable.closed) {
|
||||
return Cause.done(_)
|
||||
}
|
||||
return Effect.callback<never, E | Cause.Done<unknown>>((resume) => {
|
||||
options.writable.once("finish", () => resume(Cause.done(_)))
|
||||
options.writable.end()
|
||||
})
|
||||
}) :
|
||||
identity
|
||||
)
|
||||
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* Node socket adapters for Effect sockets.
|
||||
*
|
||||
* This module opens `node:net` connections or wraps existing Node `Duplex`
|
||||
* streams and presents them as `Socket.Socket` values, socket channels, or
|
||||
* layers. It also exposes the current underlying `NetSocket` service for code
|
||||
* running inside a socket handler and re-exports the `ws` package namespace.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type { Array } from "effect"
|
||||
import * as Channel from "effect/Channel"
|
||||
import * as Context from "effect/Context"
|
||||
import * as Deferred from "effect/Deferred"
|
||||
import type * as Duration from "effect/Duration"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as FiberSet from "effect/FiberSet"
|
||||
import * as Function from "effect/Function"
|
||||
import { identity } from "effect/Function"
|
||||
import * as Latch from "effect/Latch"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Scope from "effect/Scope"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
import * as Net from "node:net"
|
||||
import type { Duplex } from "node:stream"
|
||||
|
||||
/**
|
||||
* @category re-exports
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeWS from "ws"
|
||||
|
||||
/**
|
||||
* Service tag for the underlying Node `net.Socket` associated with the current
|
||||
* socket connection.
|
||||
*
|
||||
* @category services
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class NetSocket extends Context.Service<NetSocket, Net.Socket>()(
|
||||
"@effect/platform-node/NodeSocket/NetSocket"
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Opens a Node TCP connection as an Effect socket.
|
||||
*
|
||||
* **When to use**
|
||||
*
|
||||
* Use to create a scoped `Socket.Socket` from Node `net.createConnection`.
|
||||
*
|
||||
* **Details**
|
||||
*
|
||||
* Supports `openTimeout` and closes or destroys the underlying socket when the
|
||||
* enclosing scope is finalized.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const makeNet = (
|
||||
options: Net.NetConnectOpts & {
|
||||
readonly openTimeout?: Duration.Input | undefined
|
||||
}
|
||||
): Effect.Effect<Socket.Socket> =>
|
||||
fromDuplex(
|
||||
Effect.contextWith((context: Context.Context<Scope.Scope>) => {
|
||||
let conn: Net.Socket | undefined
|
||||
return Effect.flatMap(
|
||||
Scope.addFinalizer(
|
||||
Context.get(context, Scope.Scope),
|
||||
Effect.sync(() => {
|
||||
if (!conn) return
|
||||
if (conn.closed === false) {
|
||||
if ("destroySoon" in conn) {
|
||||
conn.destroySoon()
|
||||
} else {
|
||||
;(conn as Net.Socket).destroy()
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
() =>
|
||||
Effect.callback<Net.Socket, Socket.SocketError, never>((resume) => {
|
||||
conn = Net.createConnection(options)
|
||||
conn.once("connect", () => {
|
||||
resume(Effect.succeed(conn!))
|
||||
})
|
||||
conn.on("error", (cause: Error) => {
|
||||
resume(Effect.fail(
|
||||
new Socket.SocketError({
|
||||
reason: new Socket.SocketOpenError({ kind: "Unknown", cause })
|
||||
})
|
||||
))
|
||||
})
|
||||
})
|
||||
)
|
||||
}),
|
||||
options
|
||||
)
|
||||
|
||||
/**
|
||||
* Adapts a Node `Duplex` into a `Socket.Socket`, wiring data events to socket
|
||||
* handlers, providing a scoped writer, and mapping open, read, write, and close
|
||||
* failures to `SocketError`.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromDuplex = <RO>(
|
||||
open: Effect.Effect<Duplex, Socket.SocketError, RO>,
|
||||
options?: {
|
||||
readonly openTimeout?: Duration.Input | undefined
|
||||
}
|
||||
): Effect.Effect<Socket.Socket, never, Exclude<RO, Scope.Scope>> =>
|
||||
Effect.withFiber<Socket.Socket, never, Exclude<RO, Scope.Scope>>((fiber) => {
|
||||
let currentSocket: Duplex | undefined
|
||||
const latch = Latch.makeUnsafe(false)
|
||||
const openServices = fiber.context as Context.Context<RO>
|
||||
|
||||
const run = <R, E, _>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void, opts?: {
|
||||
readonly onOpen?: Effect.Effect<void> | undefined
|
||||
}) =>
|
||||
Effect.scopedWith(Effect.fnUntraced(function*(scope) {
|
||||
const fiberSet = yield* FiberSet.make<any, E | Socket.SocketError>().pipe(
|
||||
Scope.provide(scope)
|
||||
)
|
||||
let conn: Duplex | undefined = undefined
|
||||
yield* Scope.addFinalizer(
|
||||
scope,
|
||||
Effect.sync(() => {
|
||||
if (!conn) return
|
||||
conn.off("data", onData)
|
||||
conn.off("end", onEnd)
|
||||
conn.off("error", onError)
|
||||
conn.off("close", onClose)
|
||||
})
|
||||
)
|
||||
conn = yield* Scope.provide(open, scope).pipe(
|
||||
options?.openTimeout ?
|
||||
Effect.timeoutOrElse({
|
||||
duration: options.openTimeout,
|
||||
orElse: () =>
|
||||
Effect.fail(
|
||||
new Socket.SocketError({
|
||||
reason: new Socket.SocketOpenError({ kind: "Timeout", cause: new Error("Connection timed out") })
|
||||
})
|
||||
)
|
||||
}) :
|
||||
identity
|
||||
)
|
||||
conn.on("end", onEnd)
|
||||
conn.on("error", onError)
|
||||
conn.on("close", onClose)
|
||||
const run = yield* Effect.provideService(FiberSet.runtime(fiberSet)<R>(), NetSocket, conn as Net.Socket)
|
||||
conn.on("data", onData)
|
||||
|
||||
currentSocket = conn
|
||||
latch.openUnsafe()
|
||||
if (opts?.onOpen) {
|
||||
yield* opts.onOpen
|
||||
}
|
||||
|
||||
return yield* FiberSet.join(fiberSet)
|
||||
|
||||
function onData(chunk: Uint8Array) {
|
||||
const result = handler(chunk)
|
||||
if (Effect.isEffect(result)) {
|
||||
run(result)
|
||||
}
|
||||
}
|
||||
function onEnd() {
|
||||
Deferred.doneUnsafe(fiberSet.deferred, Effect.void)
|
||||
}
|
||||
function onError(cause: Error) {
|
||||
Deferred.doneUnsafe(
|
||||
fiberSet.deferred,
|
||||
Effect.fail(
|
||||
new Socket.SocketError({
|
||||
reason: new Socket.SocketReadError({ cause })
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
function onClose(hadError: boolean) {
|
||||
Deferred.doneUnsafe(
|
||||
fiberSet.deferred,
|
||||
Effect.fail(
|
||||
new Socket.SocketError({
|
||||
reason: new Socket.SocketCloseError({ code: hadError ? 1006 : 1000 })
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
})).pipe(
|
||||
Effect.updateContext((input: Context.Context<R>) => Context.merge(openServices, input)),
|
||||
Effect.onExit(() =>
|
||||
Effect.sync(() => {
|
||||
latch.closeUnsafe()
|
||||
currentSocket = undefined
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
const write = (chunk: Uint8Array | string | Socket.CloseEvent) =>
|
||||
latch.whenOpen(Effect.callback<void, Socket.SocketError>((resume) => {
|
||||
const conn = currentSocket!
|
||||
if (Socket.isCloseEvent(chunk)) {
|
||||
conn.destroy(chunk.code > 1000 ? new Error(`closed with code ${chunk.code}`) : undefined)
|
||||
return resume(Effect.void)
|
||||
}
|
||||
currentSocket!.write(chunk, (cause) => {
|
||||
resume(
|
||||
cause
|
||||
? Effect.fail(
|
||||
new Socket.SocketError({
|
||||
reason: new Socket.SocketWriteError({ cause: cause! })
|
||||
})
|
||||
)
|
||||
: Effect.void
|
||||
)
|
||||
})
|
||||
}))
|
||||
|
||||
const writer = Effect.acquireRelease(
|
||||
Effect.succeed(write),
|
||||
() =>
|
||||
Effect.sync(() => {
|
||||
if (!currentSocket || currentSocket.writableEnded) return
|
||||
currentSocket.end()
|
||||
})
|
||||
)
|
||||
|
||||
return Effect.succeed(Socket.make({
|
||||
run,
|
||||
runRaw: run,
|
||||
writer
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a `Channel` over a TCP socket, reading arrays of `Uint8Array`
|
||||
* chunks and writing arrays of bytes, strings, or socket close events.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const makeNetChannel = <IE = never>(
|
||||
options: Net.NetConnectOpts
|
||||
): Channel.Channel<
|
||||
Array.NonEmptyReadonlyArray<Uint8Array>,
|
||||
Socket.SocketError | IE,
|
||||
void,
|
||||
Array.NonEmptyReadonlyArray<Uint8Array | string | Socket.CloseEvent>,
|
||||
IE
|
||||
> =>
|
||||
Channel.unwrap(
|
||||
Effect.map(makeNet(options), Socket.toChannelWith<IE>())
|
||||
)
|
||||
|
||||
/**
|
||||
* Provides a `Socket.Socket` by opening a TCP connection with the supplied
|
||||
* Node `net` connection options.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerNet: (options: Net.NetConnectOpts) => Layer.Layer<
|
||||
Socket.Socket,
|
||||
Socket.SocketError
|
||||
> = Function.flow(makeNet, Layer.effect(Socket.Socket))
|
||||
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* Node socket server adapters for Effect's unstable socket server API.
|
||||
*
|
||||
* This module turns `node:net` TCP or Unix-domain servers and `ws` WebSocket
|
||||
* servers into scoped `SocketServer.SocketServer` services. Use the TCP
|
||||
* constructors when handlers should receive a `Socket.Socket` backed by a Node
|
||||
* `net.Socket`; use the WebSocket constructors when handlers should receive a
|
||||
* `Socket.Socket` backed by `ws` and have access to the per-connection
|
||||
* WebSocket and `IncomingMessage` services.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type { Cause } from "effect/Cause"
|
||||
import * as Context from "effect/Context"
|
||||
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 { pipe } from "effect/Function"
|
||||
import * as Function from "effect/Function"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as References from "effect/References"
|
||||
import * as Scope from "effect/Scope"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
import * as SocketServer from "effect/unstable/socket/SocketServer"
|
||||
import type * as Http from "node:http"
|
||||
import * as Net from "node:net"
|
||||
import * as NodeSocket from "./NodeSocket.ts"
|
||||
import { NodeWS } from "./NodeSocket.ts"
|
||||
|
||||
/**
|
||||
* Service tag for the Node `IncomingMessage` associated with the current
|
||||
* WebSocket server connection.
|
||||
*
|
||||
* @category services
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export class IncomingMessage extends Context.Service<
|
||||
IncomingMessage,
|
||||
Http.IncomingMessage
|
||||
>()("@effect/platform-node-shared/NodeSocketServer/IncomingMessage") {}
|
||||
|
||||
/**
|
||||
* Creates a scoped TCP `SocketServer` from a Node `net.Server`, starts
|
||||
* listening with the supplied options, queues pending connections until `run`
|
||||
* is called, and closes the server when the scope ends.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make = Effect.fnUntraced(function*(
|
||||
options: Net.ServerOpts & Net.ListenOptions
|
||||
) {
|
||||
const errorDeferred = Deferred.makeUnsafe<never, Error>()
|
||||
const pending = new Set<Net.Socket>()
|
||||
function defaultOnConnection(conn: Net.Socket) {
|
||||
pending.add(conn)
|
||||
const remove = () => {
|
||||
pending.delete(conn)
|
||||
}
|
||||
conn.on("close", remove)
|
||||
conn.on("error", remove)
|
||||
}
|
||||
let onConnection = defaultOnConnection
|
||||
// oxlint-disable-next-line prefer-const
|
||||
let server: Net.Server | undefined
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.callback<void>((resume) => {
|
||||
server?.close(() => resume(Effect.void))
|
||||
})
|
||||
)
|
||||
server = Net.createServer(options, (conn) => onConnection(conn))
|
||||
server.on("error", (err) => Deferred.doneUnsafe(errorDeferred, Exit.fail(err)))
|
||||
|
||||
yield* Effect.callback<void, SocketServer.SocketServerError>((resume) => {
|
||||
server.listen(options, () => resume(Effect.void))
|
||||
}).pipe(
|
||||
Effect.raceFirst(Effect.mapError(Deferred.await(errorDeferred), (err) =>
|
||||
new SocketServer.SocketServerError({
|
||||
reason: new SocketServer.SocketServerOpenError({
|
||||
cause: err
|
||||
})
|
||||
})))
|
||||
)
|
||||
|
||||
const run = Effect.fnUntraced(function*<R, E, _>(handler: (socket: Socket.Socket) => Effect.Effect<_, E, R>) {
|
||||
const scope = yield* Scope.make()
|
||||
const services = Context.omit(Scope.Scope)(yield* Effect.context<R>()) as Context.Context<R>
|
||||
const trackFiber = Fiber.runIn(scope)
|
||||
const prevOnConnection = onConnection
|
||||
onConnection = function(conn: Net.Socket) {
|
||||
let error: Error | undefined
|
||||
conn.on("error", (err) => {
|
||||
error = err
|
||||
})
|
||||
pipe(
|
||||
NodeSocket.fromDuplex(
|
||||
Effect.acquireRelease(
|
||||
Effect.suspend((): Effect.Effect<Net.Socket, Socket.SocketError> => {
|
||||
if (error) {
|
||||
return Effect.fail(
|
||||
new Socket.SocketError({
|
||||
reason: new Socket.SocketOpenError({
|
||||
kind: "Unknown",
|
||||
cause: error
|
||||
})
|
||||
})
|
||||
)
|
||||
} else if (conn.closed) {
|
||||
return Effect.fail(
|
||||
new Socket.SocketError({
|
||||
reason: new Socket.SocketCloseError({ code: 1000 })
|
||||
})
|
||||
)
|
||||
}
|
||||
return Effect.succeed(conn)
|
||||
}),
|
||||
(conn) =>
|
||||
Effect.sync(() => {
|
||||
if (conn.closed === false) {
|
||||
conn.destroySoon()
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
Effect.flatMap(handler),
|
||||
Effect.catchCause(reportUnhandledError),
|
||||
Effect.runForkWith(Context.add(services, NodeSocket.NetSocket, conn)),
|
||||
trackFiber
|
||||
)
|
||||
}
|
||||
pending.forEach((conn) => {
|
||||
conn.removeAllListeners("error")
|
||||
conn.removeAllListeners("close")
|
||||
onConnection(conn)
|
||||
})
|
||||
pending.clear()
|
||||
return yield* Effect.callback<never>((_resume) => {
|
||||
return Effect.suspend(() => {
|
||||
onConnection = prevOnConnection
|
||||
return Scope.close(scope, Exit.void)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const address = server.address()!
|
||||
return SocketServer.SocketServer.of({
|
||||
address: typeof address === "string" ?
|
||||
{
|
||||
_tag: "UnixAddress",
|
||||
path: address
|
||||
} :
|
||||
{
|
||||
_tag: "TcpAddress",
|
||||
hostname: address.address,
|
||||
port: address.port
|
||||
},
|
||||
run
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides a TCP `SocketServer` by creating and managing a scoped Node
|
||||
* `net.Server` with the supplied server and listen options.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: (
|
||||
options: Net.ServerOpts & Net.ListenOptions
|
||||
) => Layer.Layer<
|
||||
SocketServer.SocketServer,
|
||||
SocketServer.SocketServerError
|
||||
> = Function.flow(make, Layer.effect(SocketServer.SocketServer))
|
||||
|
||||
/**
|
||||
* Creates a scoped WebSocket `SocketServer` backed by the `ws` package,
|
||||
* providing the WebSocket and its Node `IncomingMessage` to connection
|
||||
* handlers and closing the server when the scope ends.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const makeWebSocket: (
|
||||
options: NodeWS.ServerOptions<typeof NodeWS.WebSocket, typeof Http.IncomingMessage>
|
||||
) => Effect.Effect<
|
||||
SocketServer.SocketServer["Service"],
|
||||
SocketServer.SocketServerError,
|
||||
Scope.Scope
|
||||
> = Effect.fnUntraced(function*(
|
||||
options: NodeWS.ServerOptions
|
||||
) {
|
||||
const server = yield* Effect.acquireRelease(
|
||||
Effect.sync(() => new NodeWS.WebSocketServer(options)),
|
||||
(server) =>
|
||||
Effect.callback<void>((resume) => {
|
||||
server.close(() => resume(Effect.void))
|
||||
})
|
||||
)
|
||||
const pendingConnections = new Set<readonly [globalThis.WebSocket, Http.IncomingMessage]>()
|
||||
function defaultHandler(conn: globalThis.WebSocket, req: Http.IncomingMessage) {
|
||||
const entry = [conn, req] as const
|
||||
pendingConnections.add(entry)
|
||||
conn.addEventListener("close", () => {
|
||||
pendingConnections.delete(entry)
|
||||
})
|
||||
}
|
||||
let onConnection = defaultHandler
|
||||
server.on("connection", (conn, req) => onConnection(conn as any, req))
|
||||
|
||||
yield* Effect.callback<void, SocketServer.SocketServerError>((resume) => {
|
||||
server.once("error", (error) => {
|
||||
resume(Effect.fail(
|
||||
new SocketServer.SocketServerError({
|
||||
reason: new SocketServer.SocketServerOpenError({
|
||||
cause: error
|
||||
})
|
||||
})
|
||||
))
|
||||
})
|
||||
server.once("listening", () => {
|
||||
resume(Effect.void)
|
||||
})
|
||||
})
|
||||
|
||||
const run = Effect.fnUntraced(function*<R, E, _>(handler: (socket: Socket.Socket) => Effect.Effect<_, E, R>) {
|
||||
const scope = yield* Scope.make()
|
||||
const services = Context.omit(Scope.Scope)(yield* Effect.context<R>()) as Context.Context<R>
|
||||
const trackFiber = Fiber.runIn(scope)
|
||||
const prevOnConnection = onConnection
|
||||
onConnection = function(conn: globalThis.WebSocket, req: Http.IncomingMessage) {
|
||||
const map = new Map(services.mapUnsafe)
|
||||
map.set(IncomingMessage.key, req)
|
||||
map.set(Socket.WebSocket.key, conn as any)
|
||||
pipe(
|
||||
Socket.fromWebSocket(
|
||||
Effect.acquireRelease(
|
||||
Effect.succeed(conn),
|
||||
(conn) =>
|
||||
Effect.sync(() => {
|
||||
conn.close()
|
||||
})
|
||||
)
|
||||
),
|
||||
Effect.flatMap(handler),
|
||||
Effect.catchCause(reportUnhandledError),
|
||||
Effect.runForkWith(Context.makeUnsafe(map)),
|
||||
trackFiber
|
||||
)
|
||||
}
|
||||
for (const [conn, req] of pendingConnections) {
|
||||
onConnection(conn, req)
|
||||
}
|
||||
pendingConnections.clear()
|
||||
return yield* Effect.callback<never>((_resume) => {
|
||||
return Effect.sync(() => {
|
||||
onConnection = prevOnConnection
|
||||
})
|
||||
}).pipe(
|
||||
Effect.ensuring(Scope.close(scope, Exit.void))
|
||||
)
|
||||
})
|
||||
|
||||
const address = server.address()!
|
||||
return SocketServer.SocketServer.of({
|
||||
address: typeof address === "string" ?
|
||||
{
|
||||
_tag: "UnixAddress",
|
||||
path: address
|
||||
} :
|
||||
{
|
||||
_tag: "TcpAddress",
|
||||
hostname: address.address,
|
||||
port: address.port
|
||||
},
|
||||
run
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides a WebSocket `SocketServer` backed by the `ws` package and managed
|
||||
* with the supplied server options.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layerWebSocket: (
|
||||
options: NodeSocket.NodeWS.ServerOptions<typeof NodeSocket.NodeWS.WebSocket, typeof Http.IncomingMessage>
|
||||
) => Layer.Layer<
|
||||
SocketServer.SocketServer,
|
||||
SocketServer.SocketServerError
|
||||
> = Function.flow(makeWebSocket, Layer.effect(SocketServer.SocketServer))
|
||||
|
||||
const reportUnhandledError = <E>(cause: Cause<E>) =>
|
||||
Effect.withFiber<void>((fiber) => {
|
||||
const unhandledLogLevel = fiber.getRef(References.UnhandledLogLevel)
|
||||
if (unhandledLogLevel) {
|
||||
return Effect.logWithLevel(unhandledLogLevel)(cause, "Unhandled error in SocketServer")
|
||||
}
|
||||
return Effect.void
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Shared Node.js implementation of the Effect `Stdio` service.
|
||||
*
|
||||
* `NodeStdio` provides {@link Stdio.Stdio} from the current Node process. The
|
||||
* exported {@link layer} reads command-line arguments from `process.argv`,
|
||||
* consumes input from `process.stdin`, and writes normal and error output to
|
||||
* `process.stdout` and `process.stderr`. Standard input remains open, and
|
||||
* standard output and error output are not ended unless requested.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import { systemError } from "effect/PlatformError"
|
||||
import * as Stdio from "effect/Stdio"
|
||||
import { fromWritable } from "./NodeSink.ts"
|
||||
import { fromReadable } from "./NodeStream.ts"
|
||||
|
||||
/**
|
||||
* Provides `Stdio` from `process.argv`, `process.stdin`, `process.stdout`,
|
||||
* and `process.stderr`; stdin remains open and stdout/stderr are not ended by
|
||||
* default.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<Stdio.Stdio> = Layer.succeed(
|
||||
Stdio.Stdio,
|
||||
Stdio.make({
|
||||
args: Effect.sync(() => process.argv.slice(2)),
|
||||
stdout: (options) =>
|
||||
fromWritable({
|
||||
evaluate: () => process.stdout,
|
||||
onError: (cause) =>
|
||||
systemError({
|
||||
module: "Stdio",
|
||||
method: "stdout",
|
||||
_tag: "Unknown",
|
||||
cause
|
||||
}),
|
||||
endOnDone: options?.endOnDone ?? false
|
||||
}),
|
||||
stderr: (options) =>
|
||||
fromWritable({
|
||||
evaluate: () => process.stderr,
|
||||
onError: (cause) =>
|
||||
systemError({
|
||||
module: "Stdio",
|
||||
method: "stderr",
|
||||
_tag: "Unknown",
|
||||
cause
|
||||
}),
|
||||
endOnDone: options?.endOnDone ?? false
|
||||
}),
|
||||
stdin: fromReadable({
|
||||
evaluate: () => process.stdin,
|
||||
onError: (cause) =>
|
||||
systemError({
|
||||
module: "Stdio",
|
||||
method: "stdin",
|
||||
_tag: "Unknown",
|
||||
cause
|
||||
}),
|
||||
closeOnDone: false
|
||||
})
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* Adapters between Node streams and Effect streams, channels, and readables.
|
||||
*
|
||||
* This module is the stream boundary for Node APIs. It wraps `Readable` and
|
||||
* `Duplex` values as Effect `Stream`s and `Channel`s, pipes Effect streams
|
||||
* through Node duplex streams, exposes an Effect `Stream` back to Node as a
|
||||
* `Readable`, and collects readable payloads into strings, array buffers, or
|
||||
* `Uint8Array`s with optional byte limits.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import * as Arr from "effect/Array"
|
||||
import * as Cause from "effect/Cause"
|
||||
import * as Channel from "effect/Channel"
|
||||
import * as Context from "effect/Context"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Exit from "effect/Exit"
|
||||
import * as Fiber from "effect/Fiber"
|
||||
import type { SizeInput } from "effect/FileSystem"
|
||||
import { dual, type LazyArg } from "effect/Function"
|
||||
import * as Latch from "effect/Latch"
|
||||
import * as MutableRef from "effect/MutableRef"
|
||||
import * as Pull from "effect/Pull"
|
||||
import * as Scope from "effect/Scope"
|
||||
import * as Stream from "effect/Stream"
|
||||
import type { Duplex } from "node:stream"
|
||||
import { Readable } from "node:stream"
|
||||
import { pullIntoWritable } from "./NodeSink.ts"
|
||||
|
||||
/**
|
||||
* Converts a Node readable stream into an Effect `Stream`, reading chunks with
|
||||
* an optional chunk size, mapping stream errors with `onError`, and destroying
|
||||
* the readable on completion unless `closeOnDone` is `false`.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromReadable = <A = Uint8Array, E = Cause.UnknownError>(options: {
|
||||
readonly evaluate: LazyArg<Readable | NodeJS.ReadableStream>
|
||||
readonly onError?: (error: unknown) => E
|
||||
readonly chunkSize?: number | undefined
|
||||
readonly bufferSize?: number | undefined
|
||||
readonly closeOnDone?: boolean | undefined
|
||||
}): Stream.Stream<A, E> => Stream.fromChannel(fromReadableChannel<A, E>(options))
|
||||
|
||||
/**
|
||||
* Creates a `Channel` that pulls chunks from a Node readable stream, mapping
|
||||
* errors with `onError` and destroying the readable on completion unless
|
||||
* `closeOnDone` is `false`.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromReadableChannel = <A = Uint8Array, E = Cause.UnknownError>(options: {
|
||||
readonly evaluate: LazyArg<Readable | NodeJS.ReadableStream>
|
||||
readonly onError?: (error: unknown) => E
|
||||
readonly chunkSize?: number | undefined
|
||||
readonly closeOnDone?: boolean | undefined
|
||||
}): Channel.Channel<Arr.NonEmptyReadonlyArray<A>, E> =>
|
||||
Channel.fromTransform((_, scope) =>
|
||||
readableToPullUnsafe({
|
||||
scope,
|
||||
readable: options.evaluate(),
|
||||
onError: options.onError ?? defaultOnError as any,
|
||||
chunkSize: options.chunkSize,
|
||||
closeOnDone: options.closeOnDone
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a `Channel` over a Node `Duplex`, writing upstream chunks with
|
||||
* backpressure while emitting chunks read from the duplex and optionally ending
|
||||
* the writable side when upstream completes.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const fromDuplex = <IE, I = Uint8Array, O = Uint8Array, E = Cause.UnknownError>(
|
||||
options: {
|
||||
readonly evaluate: LazyArg<Duplex>
|
||||
readonly onError?: (error: unknown) => E
|
||||
readonly chunkSize?: number | undefined
|
||||
readonly bufferSize?: number | undefined
|
||||
readonly endOnDone?: boolean | undefined
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
}
|
||||
): Channel.Channel<Arr.NonEmptyReadonlyArray<O>, IE | E, void, Arr.NonEmptyReadonlyArray<I>, IE> =>
|
||||
Channel.fromTransform((upstream, scope) => {
|
||||
const duplex = options.evaluate()
|
||||
const exit = MutableRef.make<Exit.Exit<never, IE | E | Cause.Done> | undefined>(undefined)
|
||||
|
||||
return pullIntoWritable({
|
||||
pull: upstream,
|
||||
writable: duplex,
|
||||
onError: options.onError ?? defaultOnError as any,
|
||||
endOnDone: options.endOnDone,
|
||||
encoding: options.encoding
|
||||
}).pipe(
|
||||
Effect.catchCause((cause) => {
|
||||
if (Pull.isDoneCause(cause)) return Effect.void
|
||||
exit.current = Exit.failCause(cause as Cause.Cause<IE | E | Cause.Done>)
|
||||
return Effect.void
|
||||
}),
|
||||
Effect.forkIn(scope),
|
||||
Effect.flatMap(() =>
|
||||
readableToPullUnsafe({
|
||||
scope,
|
||||
exit,
|
||||
readable: duplex,
|
||||
onError: options.onError ?? defaultOnError as any,
|
||||
chunkSize: options.chunkSize
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Pipes an Effect `Stream` through a Node `Duplex`, writing the stream's
|
||||
* chunks to the duplex and emitting chunks read back from it.
|
||||
*
|
||||
* @category combinators
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const pipeThroughDuplex: {
|
||||
<B = Uint8Array, E2 = Cause.UnknownError>(
|
||||
options: {
|
||||
readonly evaluate: LazyArg<Duplex>
|
||||
readonly onError?: (error: unknown) => E2
|
||||
readonly chunkSize?: number | undefined
|
||||
readonly bufferSize?: number | undefined
|
||||
readonly endOnDone?: boolean | undefined
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
}
|
||||
): <R, E, A>(self: Stream.Stream<A, E, R>) => Stream.Stream<B, E2 | E, R>
|
||||
<R, E, A, B = Uint8Array, E2 = Cause.UnknownError>(
|
||||
self: Stream.Stream<A, E, R>,
|
||||
options: {
|
||||
readonly evaluate: LazyArg<Duplex>
|
||||
readonly onError?: (error: unknown) => E2
|
||||
readonly chunkSize?: number | undefined
|
||||
readonly bufferSize?: number | undefined
|
||||
readonly endOnDone?: boolean | undefined
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
}
|
||||
): Stream.Stream<B, E | E2, R>
|
||||
} = dual(2, <R, E, A, B = Uint8Array, E2 = Cause.UnknownError>(
|
||||
self: Stream.Stream<A, E, R>,
|
||||
options: {
|
||||
readonly evaluate: LazyArg<Duplex>
|
||||
readonly onError?: (error: unknown) => E2
|
||||
readonly chunkSize?: number | undefined
|
||||
readonly bufferSize?: number | undefined
|
||||
readonly endOnDone?: boolean | undefined
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
}
|
||||
): Stream.Stream<B, E | E2, R> =>
|
||||
Stream.pipeThroughChannelOrFail(
|
||||
self,
|
||||
fromDuplex(options)
|
||||
))
|
||||
|
||||
/**
|
||||
* Pipes a stream of strings or bytes through a Node `Duplex` using default
|
||||
* options and `Cause.UnknownError` for stream failures.
|
||||
*
|
||||
* @category combinators
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const pipeThroughSimple: {
|
||||
(
|
||||
duplex: LazyArg<Duplex>
|
||||
): <R, E>(self: Stream.Stream<string | Uint8Array, E, R>) => Stream.Stream<Uint8Array, E | Cause.UnknownError, R>
|
||||
<R, E>(
|
||||
self: Stream.Stream<string | Uint8Array, E, R>,
|
||||
duplex: LazyArg<Duplex>
|
||||
): Stream.Stream<Uint8Array, Cause.UnknownError | E, R>
|
||||
} = dual(2, <R, E>(
|
||||
self: Stream.Stream<string | Uint8Array, E, R>,
|
||||
duplex: LazyArg<Duplex>
|
||||
): Stream.Stream<Uint8Array, Cause.UnknownError | E, R> => pipeThroughDuplex(self, { evaluate: duplex }))
|
||||
|
||||
/**
|
||||
* Converts an Effect `Stream` into a Node `Readable`, using the caller's
|
||||
* Effect context to run the stream and destroying the readable if the stream
|
||||
* fails.
|
||||
*
|
||||
* @category converting
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const toReadable = <E, R>(stream: Stream.Stream<string | Uint8Array, E, R>): Effect.Effect<Readable, never, R> =>
|
||||
Effect.map(
|
||||
Effect.context<R>(),
|
||||
(context) => new StreamAdapter(context, stream)
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a service-free Effect `Stream` into a Node `Readable` using an
|
||||
* empty Effect context.
|
||||
*
|
||||
* @category converting
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const toReadableNever = <E>(stream: Stream.Stream<string | Uint8Array, E, never>): Readable =>
|
||||
new StreamAdapter(
|
||||
Context.empty(),
|
||||
stream
|
||||
)
|
||||
|
||||
/**
|
||||
* Consumes a Node readable stream into a string using the selected encoding,
|
||||
* failing through `onError` on stream errors or when `maxBytes` is exceeded
|
||||
* and destroying the stream on interruption or failure.
|
||||
*
|
||||
* @category converting
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const toString = <E = Cause.UnknownError>(
|
||||
readable: LazyArg<Readable | NodeJS.ReadableStream>,
|
||||
options?: {
|
||||
readonly onError?: (error: unknown) => E
|
||||
readonly encoding?: BufferEncoding | undefined
|
||||
readonly maxBytes?: SizeInput | undefined
|
||||
}
|
||||
): Effect.Effect<string, E> => {
|
||||
const maxBytesNumber = options?.maxBytes ? Number(options.maxBytes) : undefined
|
||||
const onError = options?.onError ?? defaultOnError
|
||||
const encoding = options?.encoding ?? "utf8"
|
||||
return Effect.callback((resume) => {
|
||||
const stream = readable() as Readable
|
||||
stream.setEncoding(encoding)
|
||||
|
||||
stream.once("error", (err) => {
|
||||
if ("closed" in stream && !stream.closed) {
|
||||
stream.destroy()
|
||||
}
|
||||
resume(Effect.fail(onError(err) as E))
|
||||
})
|
||||
stream.once("error", (err) => {
|
||||
resume(Effect.fail(onError(err) as E))
|
||||
})
|
||||
|
||||
let string = ""
|
||||
let bytes = 0
|
||||
stream.once("end", () => {
|
||||
resume(Effect.succeed(string))
|
||||
})
|
||||
stream.on("data", (chunk) => {
|
||||
string += chunk
|
||||
bytes += Buffer.byteLength(chunk)
|
||||
if (maxBytesNumber && bytes > maxBytesNumber) {
|
||||
resume(Effect.fail(onError(new Error("maxBytes exceeded")) as E))
|
||||
}
|
||||
})
|
||||
return Effect.sync(() => {
|
||||
if ("closed" in stream && !stream.closed) {
|
||||
stream.destroy()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a Node readable stream into an `ArrayBuffer`, failing through
|
||||
* `onError` on stream errors or when `maxBytes` is exceeded and destroying the
|
||||
* stream on interruption or failure.
|
||||
*
|
||||
* @category converting
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const toArrayBuffer = <E = Cause.UnknownError>(
|
||||
readable: LazyArg<Readable | NodeJS.ReadableStream>,
|
||||
options?: {
|
||||
readonly onError?: (error: unknown) => E
|
||||
readonly maxBytes?: SizeInput | undefined
|
||||
}
|
||||
): Effect.Effect<ArrayBuffer, E> => {
|
||||
const maxBytesNumber = options?.maxBytes ? Number(options.maxBytes) : undefined
|
||||
const onError = options?.onError ?? defaultOnError
|
||||
return Effect.callback((resume) => {
|
||||
const stream = readable() as Readable
|
||||
const buffers: Array<Uint8Array> = []
|
||||
let bytes = 0
|
||||
stream.once("error", (err) => {
|
||||
if ("closed" in stream && !stream.closed) {
|
||||
stream.destroy()
|
||||
}
|
||||
resume(Effect.fail(onError(err) as E))
|
||||
})
|
||||
stream.once("end", () => {
|
||||
const buffer = buffers.length === 1 ? buffers[0] : Buffer.concat(buffers)
|
||||
if (buffer.byteOffset === 0 && buffer.buffer.byteLength === buffer.byteLength) {
|
||||
return resume(Effect.succeed(buffer.buffer as ArrayBuffer))
|
||||
}
|
||||
resume(
|
||||
Effect.succeed(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer)
|
||||
)
|
||||
})
|
||||
stream.on("data", (chunk) => {
|
||||
buffers.push(chunk)
|
||||
bytes += chunk.length
|
||||
if (maxBytesNumber && bytes > maxBytesNumber) {
|
||||
resume(Effect.fail(onError(new Error("maxBytes exceeded")) as E))
|
||||
}
|
||||
})
|
||||
return Effect.sync(() => {
|
||||
if ("closed" in stream && !stream.closed) {
|
||||
stream.destroy()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a Node readable stream into a `Uint8Array`, using the same error
|
||||
* mapping and `maxBytes` handling as `toArrayBuffer`.
|
||||
*
|
||||
* @category converting
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const toUint8Array = <E = Cause.UnknownError>(
|
||||
readable: LazyArg<Readable | NodeJS.ReadableStream>,
|
||||
options?: {
|
||||
readonly onError?: (error: unknown) => E
|
||||
readonly maxBytes?: SizeInput | undefined
|
||||
}
|
||||
): Effect.Effect<Uint8Array, E> => Effect.map(toArrayBuffer(readable, options), (buffer) => new Uint8Array(buffer))
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// internal
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const readableToPullUnsafe = <A, E>(options: {
|
||||
readonly scope: Scope.Scope
|
||||
readonly exit?: MutableRef.MutableRef<Exit.Exit<never, E | Cause.Done> | undefined> | undefined
|
||||
readonly readable: Readable | NodeJS.ReadableStream
|
||||
readonly onError: (error: unknown) => E
|
||||
readonly chunkSize: number | undefined
|
||||
readonly closeOnDone?: boolean | undefined
|
||||
}) => {
|
||||
const readable = options.readable as Readable
|
||||
|
||||
const closeOnDone = options.closeOnDone ?? true
|
||||
const exit = options.exit ?? MutableRef.make(undefined)
|
||||
const latch = Latch.makeUnsafe(false)
|
||||
function onReadable() {
|
||||
latch.openUnsafe()
|
||||
}
|
||||
function onError(error: unknown) {
|
||||
exit.current = Exit.fail(options.onError(error))
|
||||
latch.openUnsafe()
|
||||
}
|
||||
function onEnd() {
|
||||
exit.current = Exit.fail(Cause.Done())
|
||||
latch.openUnsafe()
|
||||
}
|
||||
readable.on("readable", onReadable)
|
||||
readable.once("error", onError)
|
||||
readable.once("end", onEnd)
|
||||
|
||||
const pull = Effect.suspend(function loop(): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> {
|
||||
let item = options.readable.read(options.chunkSize) as A | null
|
||||
if (item === null) {
|
||||
if (exit.current) {
|
||||
return exit.current
|
||||
}
|
||||
if (readable.readableEnded) {
|
||||
return Effect.fail(Cause.Done())
|
||||
}
|
||||
latch.closeUnsafe()
|
||||
return Effect.flatMap(latch.await, loop)
|
||||
}
|
||||
const chunk = Arr.of(item as A)
|
||||
while (true) {
|
||||
item = options.readable.read(options.chunkSize)
|
||||
if (item === null) break
|
||||
chunk.push(item)
|
||||
}
|
||||
return Effect.succeed(chunk)
|
||||
})
|
||||
|
||||
return Effect.as(
|
||||
Scope.addFinalizer(
|
||||
options.scope,
|
||||
Effect.sync(() => {
|
||||
readable.off("readable", onReadable)
|
||||
readable.off("error", onError)
|
||||
readable.off("end", onEnd)
|
||||
if (closeOnDone && "closed" in options.readable && !options.readable.closed) {
|
||||
options.readable.destroy()
|
||||
}
|
||||
})
|
||||
),
|
||||
pull
|
||||
)
|
||||
}
|
||||
|
||||
class StreamAdapter<E, R> extends Readable {
|
||||
private readonly readLatch: Latch.Latch
|
||||
private fiber: Fiber.Fiber<void, E> | undefined = undefined
|
||||
|
||||
constructor(
|
||||
context: Context.Context<R>,
|
||||
stream: Stream.Stream<Uint8Array | string, E, R>
|
||||
) {
|
||||
super({})
|
||||
this.readLatch = Latch.makeUnsafe(false)
|
||||
this.fiber = Stream.runForEachArray(stream, (chunk) =>
|
||||
this.readLatch.whenOpen(Effect.sync(() => {
|
||||
this.readLatch.closeUnsafe()
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
const item = chunk[i]
|
||||
if (typeof item === "string") {
|
||||
this.push(item, "utf8")
|
||||
} else {
|
||||
this.push(item)
|
||||
}
|
||||
}
|
||||
}))).pipe(
|
||||
this.readLatch.whenOpen,
|
||||
Effect.provideContext(context),
|
||||
Effect.runFork
|
||||
)
|
||||
this.fiber.addObserver((exit) => {
|
||||
this.fiber = undefined
|
||||
if (Exit.isSuccess(exit)) {
|
||||
this.push(null)
|
||||
} else {
|
||||
this.destroy(Cause.squash(exit.cause) as any)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override _read(_size: number): void {
|
||||
this.readLatch.openUnsafe()
|
||||
}
|
||||
|
||||
override _destroy(error: Error | null, callback: (error?: Error | null | undefined) => void): void {
|
||||
if (!this.fiber) {
|
||||
return callback(error)
|
||||
}
|
||||
Effect.runFork(Fiber.interrupt(this.fiber)).addObserver((exit) => {
|
||||
callback(exit._tag === "Failure" ? Cause.squash(exit.cause) as any : error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const defaultOnError = (error: unknown): Cause.UnknownError => new Cause.UnknownError(error)
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Shared Node.js implementation of Effect's `Terminal` service.
|
||||
*
|
||||
* `NodeTerminal` adapts Node's `readline` APIs plus the current process
|
||||
* `stdin` and `stdout` streams into {@link Terminal.Terminal}. The service can
|
||||
* display output, read a line, stream key input, and read terminal dimensions.
|
||||
* `make` manages readline and TTY raw mode in a scope, while `layer` provides
|
||||
* the default service that ends key input on Ctrl+C or Ctrl+D.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
import type * as Cause from "effect/Cause"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Option from "effect/Option"
|
||||
import { badArgument, type PlatformError } from "effect/PlatformError"
|
||||
import * as Predicate from "effect/Predicate"
|
||||
import * as Queue from "effect/Queue"
|
||||
import * as RcRef from "effect/RcRef"
|
||||
import type * as Scope from "effect/Scope"
|
||||
import * as Terminal from "effect/Terminal"
|
||||
import * as readline from "node:readline"
|
||||
|
||||
/**
|
||||
* Creates a scoped process-backed `Terminal` using Node `readline`, enabling
|
||||
* TTY raw mode while in scope and using the supplied predicate to decide when
|
||||
* key input should end.
|
||||
*
|
||||
* @category constructors
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const make: (
|
||||
shouldQuit?: (input: Terminal.UserInput) => boolean
|
||||
) => Effect.Effect<Terminal.Terminal, never, Scope.Scope> = Effect.fnUntraced(
|
||||
function*(shouldQuit: (input: Terminal.UserInput) => boolean = defaultShouldQuit) {
|
||||
const stdin = process.stdin
|
||||
const stdout = process.stdout
|
||||
|
||||
// Acquire readline interface with TTY setup/cleanup inside the scope
|
||||
const rlRef = yield* RcRef.make({
|
||||
acquire: Effect.acquireRelease(
|
||||
Effect.sync(() => {
|
||||
const rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 })
|
||||
readline.emitKeypressEvents(stdin, rl)
|
||||
|
||||
if (stdin.isTTY) {
|
||||
stdin.setRawMode(true)
|
||||
}
|
||||
return rl
|
||||
}),
|
||||
(rl) =>
|
||||
Effect.sync(() => {
|
||||
if (stdin.isTTY) {
|
||||
stdin.setRawMode(false)
|
||||
}
|
||||
rl.close()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const columns = Effect.sync(() => stdout.columns ?? 0)
|
||||
const rows = Effect.sync(() => stdout.rows ?? 0)
|
||||
|
||||
const readInput = Effect.gen(function*() {
|
||||
yield* RcRef.get(rlRef)
|
||||
const queue = yield* Queue.make<Terminal.UserInput, Cause.Done>()
|
||||
const handleKeypress = (s: string | undefined, k: readline.Key) => {
|
||||
const userInput = {
|
||||
input: Option.fromUndefinedOr(s),
|
||||
key: { name: k.name ?? "", ctrl: !!k.ctrl, meta: !!k.meta, shift: !!k.shift }
|
||||
}
|
||||
Queue.offerUnsafe(queue, userInput)
|
||||
if (shouldQuit(userInput)) {
|
||||
Queue.endUnsafe(queue)
|
||||
}
|
||||
}
|
||||
yield* Effect.addFinalizer(() => Effect.sync(() => stdin.off("keypress", handleKeypress)))
|
||||
stdin.on("keypress", handleKeypress)
|
||||
return queue as Queue.Dequeue<Terminal.UserInput, Cause.Done>
|
||||
})
|
||||
|
||||
const readLine = Effect.scoped(
|
||||
Effect.flatMap(RcRef.get(rlRef), (readlineInterface) =>
|
||||
Effect.callback<string, Terminal.QuitError>((resume) => {
|
||||
const onLine = (line: string) => resume(Effect.succeed(line))
|
||||
readlineInterface.once("line", onLine)
|
||||
return Effect.sync(() => readlineInterface.off("line", onLine))
|
||||
}))
|
||||
)
|
||||
|
||||
const display = (prompt: string) =>
|
||||
Effect.uninterruptible(
|
||||
Effect.callback<void, PlatformError>((resume) => {
|
||||
stdout.write(prompt, (err) =>
|
||||
Predicate.isNullish(err)
|
||||
? resume(Effect.void)
|
||||
: resume(Effect.fail(
|
||||
badArgument({
|
||||
module: "Terminal",
|
||||
method: "display",
|
||||
description: "Failed to write prompt to stdout",
|
||||
cause: err
|
||||
})
|
||||
)))
|
||||
})
|
||||
)
|
||||
|
||||
return Terminal.make({
|
||||
columns,
|
||||
rows,
|
||||
readInput,
|
||||
readLine,
|
||||
display
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Provides the default process-backed `Terminal` service, ending key input on
|
||||
* Ctrl+C or Ctrl+D.
|
||||
*
|
||||
* @category layers
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export const layer: Layer.Layer<Terminal.Terminal> = Layer.effect(Terminal.Terminal, make(defaultShouldQuit))
|
||||
|
||||
function defaultShouldQuit(input: Terminal.UserInput) {
|
||||
return input.key.ctrl && (input.key.name === "c" || input.key.name === "d")
|
||||
}
|
||||
65
repos/effect-smol/packages/platform-node-shared/src/index.ts
Normal file
65
repos/effect-smol/packages/platform-node-shared/src/index.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
|
||||
// @barrel: Auto-generated exports. Do not edit manually.
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeChildProcessSpawner from "./NodeChildProcessSpawner.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeClusterSocket from "./NodeClusterSocket.ts"
|
||||
|
||||
/**
|
||||
* @since 1.0.0
|
||||
*/
|
||||
export * as NodeCrypto from "./NodeCrypto.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeFileSystem from "./NodeFileSystem.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodePath from "./NodePath.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeRuntime from "./NodeRuntime.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeSink from "./NodeSink.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeSocket from "./NodeSocket.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeSocketServer from "./NodeSocketServer.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeStdio from "./NodeStdio.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeStream from "./NodeStream.ts"
|
||||
|
||||
/**
|
||||
* @since 4.0.0
|
||||
*/
|
||||
export * as NodeTerminal from "./NodeTerminal.ts"
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { SystemError, SystemErrorTag } from "effect/PlatformError"
|
||||
import * as PlatformError from "effect/PlatformError"
|
||||
import type { PathLike } from "node:fs"
|
||||
|
||||
/** @internal */
|
||||
export const handleErrnoException = (module: SystemError["module"], method: string) =>
|
||||
(
|
||||
err: NodeJS.ErrnoException,
|
||||
[path]: [path: PathLike | number | string | readonly string[], ...args: Array<any>]
|
||||
): PlatformError.PlatformError => {
|
||||
let reason: SystemErrorTag = "Unknown"
|
||||
|
||||
switch (err.code) {
|
||||
case "ENOENT":
|
||||
reason = "NotFound"
|
||||
break
|
||||
|
||||
case "EACCES":
|
||||
reason = "PermissionDenied"
|
||||
break
|
||||
|
||||
case "EEXIST":
|
||||
reason = "AlreadyExists"
|
||||
break
|
||||
|
||||
case "EISDIR":
|
||||
reason = "BadResource"
|
||||
break
|
||||
|
||||
case "ENOTDIR":
|
||||
reason = "BadResource"
|
||||
break
|
||||
|
||||
case "EBUSY":
|
||||
reason = "Busy"
|
||||
break
|
||||
|
||||
case "ELOOP":
|
||||
reason = "BadResource"
|
||||
break
|
||||
}
|
||||
|
||||
return PlatformError.systemError({
|
||||
_tag: reason,
|
||||
module,
|
||||
method,
|
||||
pathOrDescriptor: path as string | number,
|
||||
syscall: err.syscall,
|
||||
cause: err
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem"
|
||||
import { assert, describe, expect, it } from "@effect/vitest"
|
||||
import { Array } from "effect"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Fs from "effect/FileSystem"
|
||||
import * as Stream from "effect/Stream"
|
||||
|
||||
const runPromise = <E, A>(self: Effect.Effect<A, E, Fs.FileSystem>) =>
|
||||
Effect.runPromise(
|
||||
Effect.provide(self, NodeFileSystem.layer)
|
||||
)
|
||||
|
||||
describe("FileSystem", () => {
|
||||
it("readFile", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
const data = yield* fs.readFile(`${__dirname}/fixtures/text.txt`)
|
||||
const text = new TextDecoder().decode(data)
|
||||
expect(text.trim()).toEqual("lorem ipsum dolar sit amet")
|
||||
})))
|
||||
|
||||
it("makeTempDirectory", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
let dir = ""
|
||||
yield* Effect.scoped(Effect.gen(function*() {
|
||||
dir = yield* fs.makeTempDirectory()
|
||||
const stat = yield* fs.stat(dir)
|
||||
expect(stat.type).toEqual("Directory")
|
||||
}))
|
||||
const stat = yield* fs.stat(dir)
|
||||
expect(stat.type).toEqual("Directory")
|
||||
})))
|
||||
|
||||
it("makeTempDirectoryScoped", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
let dir = ""
|
||||
yield* Effect.scoped(
|
||||
Effect.gen(function*() {
|
||||
dir = yield* fs.makeTempDirectoryScoped()
|
||||
const stat = yield* fs.stat(dir)
|
||||
expect(stat.type).toEqual("Directory")
|
||||
})
|
||||
)
|
||||
const error = yield* Effect.flip(fs.stat(dir))
|
||||
assert(error.reason._tag === "NotFound")
|
||||
})))
|
||||
|
||||
it("truncate", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
const file = yield* fs.makeTempFile()
|
||||
|
||||
const text = "hello world"
|
||||
yield* fs.writeFile(file, new TextEncoder().encode(text))
|
||||
|
||||
const before = yield* Effect.map(fs.readFile(file), (_) => new TextDecoder().decode(_))
|
||||
expect(before).toEqual(text)
|
||||
|
||||
yield* fs.truncate(file)
|
||||
|
||||
const after = yield* Effect.map(fs.readFile(file), (_) => new TextDecoder().decode(_))
|
||||
expect(after).toEqual("")
|
||||
})))
|
||||
|
||||
it("should track the cursor position when reading", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
|
||||
yield* Effect.gen(function*() {
|
||||
let text: string
|
||||
const file = yield* fs.open(`${__dirname}/fixtures/text.txt`)
|
||||
|
||||
text = yield* file.readAlloc(Fs.Size(5)).pipe(
|
||||
Effect.flatMap(Effect.fromOption),
|
||||
Effect.map((_) => new TextDecoder().decode(_))
|
||||
)
|
||||
expect(text).toBe("lorem")
|
||||
|
||||
yield* file.seek(Fs.Size(7), "current")
|
||||
text = yield* file.readAlloc(Fs.Size(5)).pipe(
|
||||
Effect.flatMap(Effect.fromOption),
|
||||
Effect.map((_) => new TextDecoder().decode(_))
|
||||
)
|
||||
expect(text).toBe("dolar")
|
||||
|
||||
yield* file.seek(Fs.Size(1), "current")
|
||||
text = yield* file.readAlloc(Fs.Size(8)).pipe(
|
||||
Effect.flatMap(Effect.fromOption),
|
||||
Effect.map((_) => new TextDecoder().decode(_))
|
||||
)
|
||||
expect(text).toBe("sit amet")
|
||||
|
||||
yield* file.seek(Fs.Size(0), "start")
|
||||
text = yield* file.readAlloc(Fs.Size(11)).pipe(
|
||||
Effect.flatMap(Effect.fromOption),
|
||||
Effect.map((_) => new TextDecoder().decode(_))
|
||||
)
|
||||
expect(text).toBe("lorem ipsum")
|
||||
|
||||
text = yield* fs.stream(`${__dirname}/fixtures/text.txt`, { offset: Fs.Size(6), bytesToRead: Fs.Size(5) }).pipe(
|
||||
Stream.map((_) => new TextDecoder().decode(_)),
|
||||
Stream.runCollect,
|
||||
Effect.map(Array.join(""))
|
||||
)
|
||||
expect(text).toBe("ipsum")
|
||||
}).pipe(
|
||||
Effect.scoped
|
||||
)
|
||||
})))
|
||||
|
||||
it("should track the cursor position when writing", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
|
||||
yield* Effect.gen(function*() {
|
||||
let text: string
|
||||
const path = yield* fs.makeTempFileScoped()
|
||||
const file = yield* fs.open(path, { flag: "w+" })
|
||||
|
||||
yield* file.write(new TextEncoder().encode("lorem ipsum"))
|
||||
yield* file.write(new TextEncoder().encode(" "))
|
||||
yield* file.write(new TextEncoder().encode("dolor sit amet"))
|
||||
text = yield* fs.readFileString(path)
|
||||
expect(text).toBe("lorem ipsum dolor sit amet")
|
||||
|
||||
yield* file.seek(Fs.Size(-4), "current")
|
||||
yield* file.write(new TextEncoder().encode("hello world"))
|
||||
text = yield* fs.readFileString(path)
|
||||
expect(text).toBe("lorem ipsum dolor sit hello world")
|
||||
|
||||
yield* file.seek(Fs.Size(6), "start")
|
||||
yield* file.write(new TextEncoder().encode("blabl"))
|
||||
text = yield* fs.readFileString(path)
|
||||
expect(text).toBe("lorem blabl dolor sit hello world")
|
||||
}).pipe(
|
||||
Effect.scoped
|
||||
)
|
||||
})))
|
||||
|
||||
it("should maintain a read cursor in append mode", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
|
||||
yield* Effect.gen(function*() {
|
||||
let text: string
|
||||
const path = yield* fs.makeTempFileScoped()
|
||||
const file = yield* fs.open(path, { flag: "a+" })
|
||||
|
||||
yield* file.write(new TextEncoder().encode("foo"))
|
||||
yield* file.seek(Fs.Size(0), "start")
|
||||
|
||||
yield* file.write(new TextEncoder().encode("bar"))
|
||||
text = yield* fs.readFileString(path)
|
||||
expect(text).toBe("foobar")
|
||||
|
||||
text = yield* file.readAlloc(Fs.Size(3)).pipe(
|
||||
Effect.flatMap(Effect.fromOption),
|
||||
Effect.map((_) => new TextDecoder().decode(_))
|
||||
)
|
||||
expect(text).toBe("foo")
|
||||
|
||||
yield* file.write(new TextEncoder().encode("baz"))
|
||||
text = yield* fs.readFileString(path)
|
||||
expect(text).toBe("foobarbaz")
|
||||
|
||||
text = yield* file.readAlloc(Fs.Size(6)).pipe(
|
||||
Effect.flatMap(Effect.fromOption),
|
||||
Effect.map((_) => new TextDecoder().decode(_))
|
||||
)
|
||||
expect(text).toBe("barbaz")
|
||||
}).pipe(
|
||||
Effect.scoped
|
||||
)
|
||||
})))
|
||||
|
||||
it("should keep the current cursor if truncating doesn't affect it", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
|
||||
yield* Effect.gen(function*() {
|
||||
const path = yield* fs.makeTempFileScoped()
|
||||
const file = yield* fs.open(path, { flag: "w+" })
|
||||
|
||||
yield* file.write(new TextEncoder().encode("lorem ipsum dolor sit amet"))
|
||||
yield* file.seek(Fs.Size(6), "start")
|
||||
yield* file.truncate(Fs.Size(11))
|
||||
|
||||
const cursor = yield* file.seek(Fs.Size(0), "current")
|
||||
expect(cursor).toBe(Fs.Size(6))
|
||||
}).pipe(
|
||||
Effect.scoped
|
||||
)
|
||||
})))
|
||||
|
||||
it("should update the current cursor if truncating affects it", () =>
|
||||
runPromise(Effect.gen(function*() {
|
||||
const fs = yield* Fs.FileSystem
|
||||
|
||||
yield* Effect.gen(function*() {
|
||||
const path = yield* fs.makeTempFileScoped()
|
||||
const file = yield* fs.open(path, { flag: "w+" })
|
||||
|
||||
yield* file.write(new TextEncoder().encode("lorem ipsum dolor sit amet"))
|
||||
yield* file.truncate(Fs.Size(11))
|
||||
|
||||
const cursor = yield* file.seek(Fs.Size(0), "current")
|
||||
expect(cursor).toBe(Fs.Size(11))
|
||||
}).pipe(
|
||||
Effect.scoped
|
||||
)
|
||||
})))
|
||||
})
|
||||
@@ -0,0 +1,153 @@
|
||||
import * as NodeSink from "@effect/platform-node-shared/NodeSink"
|
||||
import * as NodeStream from "@effect/platform-node-shared/NodeStream"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Effect } from "effect"
|
||||
import * as Data from "effect/Data"
|
||||
import * as Latch from "effect/Latch"
|
||||
import * as Queue from "effect/Queue"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { createReadStream } from "fs"
|
||||
import { join } from "path"
|
||||
import { Writable } from "stream"
|
||||
import * as Tar from "tar"
|
||||
|
||||
const TEST_TARBALL = join(__dirname, "fixtures", "helloworld.tar.gz")
|
||||
|
||||
describe("Sink", () => {
|
||||
it.effect("should write to a stream", () =>
|
||||
Effect.gen(function*() {
|
||||
const items: Array<string> = []
|
||||
const destroyLatch = yield* Latch.make()
|
||||
yield* Stream.make("a", "b", "c").pipe(
|
||||
Stream.run(NodeSink.fromWritable({
|
||||
evaluate: () =>
|
||||
new Writable({
|
||||
construct(callback) {
|
||||
callback()
|
||||
},
|
||||
write(chunk, _encoding, callback) {
|
||||
items.push(chunk.toString())
|
||||
callback()
|
||||
},
|
||||
destroy(_error, callback) {
|
||||
destroyLatch.openUnsafe()
|
||||
callback(null)
|
||||
}
|
||||
}),
|
||||
onError: () => "error"
|
||||
}))
|
||||
)
|
||||
assert.deepEqual(items, ["a", "b", "c"])
|
||||
yield* destroyLatch.await
|
||||
}))
|
||||
|
||||
it.effect("write error", () =>
|
||||
Effect.gen(function*() {
|
||||
const items: Array<string> = []
|
||||
const sink = NodeSink.fromWritable({
|
||||
evaluate: () =>
|
||||
new Writable({
|
||||
construct(callback) {
|
||||
callback()
|
||||
},
|
||||
write(chunk, _encoding, callback) {
|
||||
items.push(chunk.toString())
|
||||
callback()
|
||||
},
|
||||
destroy(_error, callback) {
|
||||
callback(null)
|
||||
}
|
||||
}),
|
||||
onError: () => "error"
|
||||
})
|
||||
const result = yield* Stream.fail("a").pipe(Stream.run(sink), Effect.flip)
|
||||
assert.deepEqual(items, [])
|
||||
assert.strictEqual(result, "a")
|
||||
}))
|
||||
|
||||
it.live("endOnClose false", () =>
|
||||
Effect.gen(function*() {
|
||||
const items: Array<string> = []
|
||||
let destroyed = false
|
||||
const sink = NodeSink.fromWritable({
|
||||
evaluate: () =>
|
||||
new Writable({
|
||||
construct(callback) {
|
||||
callback()
|
||||
},
|
||||
write(chunk, _encoding, callback) {
|
||||
items.push(chunk.toString())
|
||||
callback()
|
||||
},
|
||||
destroy(_error, callback) {
|
||||
destroyed = true
|
||||
callback(null)
|
||||
}
|
||||
}),
|
||||
onError: () => "error",
|
||||
endOnDone: false
|
||||
})
|
||||
yield* Stream.run(Stream.make("a", "b", "c"), sink)
|
||||
yield* Effect.sleep(10)
|
||||
assert.deepEqual(items, ["a", "b", "c"])
|
||||
assert.strictEqual(destroyed, false)
|
||||
}))
|
||||
|
||||
it.effect("should handle non-compliant node streams", () =>
|
||||
Effect.gen(function*() {
|
||||
const stream = NodeStream.fromReadable<Uint8Array, "error">({
|
||||
evaluate: () => createReadStream(TEST_TARBALL),
|
||||
onError: () => "error"
|
||||
})
|
||||
const items = yield* entries(stream).pipe(
|
||||
Stream.flatMap((entry) =>
|
||||
NodeStream.fromReadable({
|
||||
evaluate: () => (entry as any),
|
||||
onError: (error) => new TarError({ error })
|
||||
}).pipe(
|
||||
Stream.map((content) => ({
|
||||
path: entry.path,
|
||||
content: Buffer.from(content).toString("utf-8")
|
||||
}))
|
||||
)
|
||||
),
|
||||
Stream.runCollect
|
||||
)
|
||||
assert.deepEqual(items, [
|
||||
{ path: "./tar/world.txt", content: "world\n" },
|
||||
{ path: "./tar/hello.txt", content: "hello\n" }
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
class TarError extends Data.TaggedError("TarError")<{
|
||||
readonly error: unknown
|
||||
}> {}
|
||||
|
||||
const entries = <R, E>(
|
||||
input: Stream.Stream<Uint8Array, E, R>
|
||||
): Stream.Stream<Tar.ReadEntry, TarError | E, R> =>
|
||||
Effect.gen(function*() {
|
||||
const parser = new Tar.Parser()
|
||||
|
||||
yield* input.pipe(
|
||||
Stream.run(
|
||||
NodeSink.fromWritable({
|
||||
evaluate: () => parser,
|
||||
onError: (error) => new TarError({ error })
|
||||
})
|
||||
),
|
||||
Effect.forkScoped
|
||||
)
|
||||
|
||||
return Stream.callback<Tar.ReadEntry, TarError>((queue) =>
|
||||
Effect.sync(() => {
|
||||
parser.on("entry", (entry) => {
|
||||
Queue.offerUnsafe(queue, entry)
|
||||
})
|
||||
parser.on("close", () => {
|
||||
Queue.endUnsafe(queue)
|
||||
})
|
||||
})
|
||||
)
|
||||
}).pipe(Stream.unwrap)
|
||||
@@ -0,0 +1,173 @@
|
||||
import * as NodeStream from "@effect/platform-node-shared/NodeStream"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Effect } from "effect"
|
||||
import * as Array from "effect/Array"
|
||||
import * as Channel from "effect/Channel"
|
||||
import * as Console from "effect/Console"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { Duplex, Readable, Transform } from "node:stream"
|
||||
import * as Zlib from "node:zlib"
|
||||
|
||||
describe("Stream", () => {
|
||||
it.effect("should read a stream", () =>
|
||||
Effect.gen(function*() {
|
||||
const stream = NodeStream.fromReadable<"error", string>({
|
||||
evaluate: () => Readable.from(["a", "b", "c"]),
|
||||
onError: () => "error"
|
||||
})
|
||||
const items = yield* Stream.runCollect(stream)
|
||||
assert.deepEqual(items, ["a", "b", "c"])
|
||||
}))
|
||||
|
||||
it.effect("fromDuplex", () =>
|
||||
Effect.gen(function*() {
|
||||
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
|
||||
Stream.pipeThroughChannelOrFail(NodeStream.fromDuplex({
|
||||
evaluate: () =>
|
||||
new Transform({
|
||||
transform(chunk, _encoding, callback) {
|
||||
callback(null, chunk.toString().toUpperCase())
|
||||
}
|
||||
}),
|
||||
onError: () => "error" as const
|
||||
})),
|
||||
Stream.decodeText(),
|
||||
Stream.mkString
|
||||
)
|
||||
|
||||
assert.strictEqual(result, "ABC")
|
||||
}))
|
||||
|
||||
it.effect("fromDuplex failure", () =>
|
||||
Effect.gen(function*() {
|
||||
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
|
||||
Stream.pipeThroughChannelOrFail(NodeStream.fromDuplex({
|
||||
evaluate: () =>
|
||||
new Transform({
|
||||
transform(_chunk, _encoding, callback) {
|
||||
callback(new Error())
|
||||
}
|
||||
}),
|
||||
onError: () => "error" as const
|
||||
})),
|
||||
Stream.runDrain,
|
||||
Effect.flip
|
||||
)
|
||||
|
||||
assert.strictEqual(result, "error")
|
||||
}))
|
||||
|
||||
it.effect("pipeThroughDuplex", () =>
|
||||
Effect.gen(function*() {
|
||||
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
|
||||
NodeStream.pipeThroughDuplex({
|
||||
evaluate: () =>
|
||||
new Transform({
|
||||
transform(chunk, _encoding, callback) {
|
||||
callback(null, chunk.toString().toUpperCase())
|
||||
}
|
||||
}),
|
||||
onError: () => "error" as const
|
||||
}),
|
||||
Stream.decodeText(),
|
||||
Stream.mkString
|
||||
)
|
||||
|
||||
assert.strictEqual(result, "ABC")
|
||||
}))
|
||||
|
||||
it.effect("pipeThroughDuplex write error", () =>
|
||||
Effect.gen(function*() {
|
||||
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
|
||||
NodeStream.pipeThroughDuplex({
|
||||
evaluate: () =>
|
||||
new Duplex({
|
||||
read() {},
|
||||
write(_chunk, _encoding, callback) {
|
||||
callback(new Error())
|
||||
}
|
||||
}),
|
||||
onError: () => "error" as const
|
||||
}),
|
||||
Stream.runDrain,
|
||||
Effect.flip
|
||||
)
|
||||
assert.strictEqual(result, "error")
|
||||
}))
|
||||
|
||||
it.effect("pipeThroughSimple", () =>
|
||||
Effect.gen(function*() {
|
||||
const result = yield* Stream.fromArray(["a", Buffer.from("b"), "c"]).pipe(
|
||||
NodeStream.pipeThroughSimple(
|
||||
() =>
|
||||
new Transform({
|
||||
transform(chunk, _encoding, callback) {
|
||||
callback(null, chunk.toString().toUpperCase())
|
||||
}
|
||||
})
|
||||
),
|
||||
Stream.decodeText(),
|
||||
Stream.mkString
|
||||
)
|
||||
|
||||
assert.strictEqual(result, "ABC")
|
||||
}))
|
||||
|
||||
it.effect("fromDuplex should work with node:zlib", () =>
|
||||
Effect.gen(function*() {
|
||||
const text = "abcdefg1234567890"
|
||||
const encoder = new TextEncoder()
|
||||
const input = encoder.encode(text)
|
||||
const stream = NodeStream.fromReadable<Uint8Array, "error">({
|
||||
evaluate: () => Readable.from([input]),
|
||||
onError: () => "error"
|
||||
})
|
||||
const deflate = NodeStream.fromDuplex({
|
||||
evaluate: () => Zlib.createGzip(),
|
||||
onError: () => "error" as const
|
||||
})
|
||||
const inflate = NodeStream.fromDuplex<never, Uint8Array, Uint8Array, "error">({
|
||||
evaluate: () => Zlib.createUnzip(),
|
||||
onError: () => "error" as const
|
||||
})
|
||||
const channel = Channel.pipeToOrFail(deflate, inflate)
|
||||
const result = yield* stream.pipe(
|
||||
Stream.pipeThroughChannelOrFail(channel),
|
||||
Stream.decodeText(),
|
||||
Stream.mkString
|
||||
)
|
||||
assert.strictEqual(result, text)
|
||||
}))
|
||||
|
||||
it.effect("toReadable roundtrip", () =>
|
||||
Effect.gen(function*() {
|
||||
const stream = Stream.range(0, 10000).pipe(
|
||||
Stream.map((n) => String(n))
|
||||
)
|
||||
const readable = yield* NodeStream.toReadable(stream)
|
||||
const outStream = NodeStream.fromReadable({
|
||||
evaluate: () => readable,
|
||||
onError: () => "error" as const
|
||||
})
|
||||
const items = yield* outStream.pipe(
|
||||
Stream.decodeText(),
|
||||
Stream.runCollect
|
||||
)
|
||||
assert.strictEqual(items.join(""), Array.range(0, 10000).join(""))
|
||||
}))
|
||||
|
||||
it.effect("toReadable with error", () =>
|
||||
Effect.gen(function*() {
|
||||
const stream = Stream.fail("error")
|
||||
const readable = yield* NodeStream.toReadable(stream)
|
||||
const outStream = NodeStream.fromReadable({
|
||||
evaluate: () => readable
|
||||
})
|
||||
const error = yield* outStream.pipe(
|
||||
Stream.runCollect,
|
||||
Effect.tapError((_) => Console.log(_)),
|
||||
Effect.flip
|
||||
)
|
||||
assert.deepEqual(error.cause, "error")
|
||||
}))
|
||||
})
|
||||
3
repos/effect-smol/packages/platform-node-shared/test/fixtures/bash/no-permissions.sh
vendored
Normal file
3
repos/effect-smol/packages/platform-node-shared/test/fixtures/bash/no-permissions.sh
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "this should not run because it doesn't have execute permissions"
|
||||
31
repos/effect-smol/packages/platform-node-shared/test/fixtures/bash/parent-exits-early.sh
vendored
Executable file
31
repos/effect-smol/packages/platform-node-shared/test/fixtures/bash/parent-exits-early.sh
vendored
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script spawns child processes and then exits early
|
||||
echo "Parent process started with PID $$"
|
||||
|
||||
# Spawn multiple child processes that will outlive the parent
|
||||
for i in {1..3}; do
|
||||
(
|
||||
# Child process
|
||||
child_pid=$BASHPID
|
||||
echo "Child $i started with PID $child_pid"
|
||||
|
||||
# Spawn a grandchild that runs for a long time
|
||||
(
|
||||
grandchild_pid=$BASHPID
|
||||
echo "Grandchild of child $i started with PID $grandchild_pid"
|
||||
# Keep running for 30 seconds
|
||||
sleep 30
|
||||
) &
|
||||
|
||||
# Keep the child running
|
||||
sleep 30
|
||||
) &
|
||||
done
|
||||
|
||||
# Give children time to start
|
||||
sleep 0.5
|
||||
|
||||
# Exit early (simulating a crash or early termination)
|
||||
echo "Parent exiting early with status 1..."
|
||||
exit 1
|
||||
32
repos/effect-smol/packages/platform-node-shared/test/fixtures/bash/spawn-children.sh
vendored
Executable file
32
repos/effect-smol/packages/platform-node-shared/test/fixtures/bash/spawn-children.sh
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script spawns child processes to test process group cleanup
|
||||
echo "Parent process started with PID $$"
|
||||
|
||||
# Spawn multiple child processes
|
||||
for i in {1..3}; do
|
||||
(
|
||||
# Child process
|
||||
child_pid=$BASHPID
|
||||
echo "Child $i started with PID $child_pid"
|
||||
|
||||
# Spawn a grandchild that runs for a long time
|
||||
(
|
||||
grandchild_pid=$BASHPID
|
||||
echo "Grandchild of child $i started with PID $grandchild_pid"
|
||||
# Keep running for 60 seconds
|
||||
for j in {1..60}; do
|
||||
sleep 1
|
||||
done
|
||||
) &
|
||||
|
||||
# Keep the child running
|
||||
for j in {1..60}; do
|
||||
sleep 1
|
||||
done
|
||||
) &
|
||||
done
|
||||
|
||||
# Keep the parent running
|
||||
echo "Parent process waiting..."
|
||||
wait
|
||||
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/SHOUTING
vendored
Normal file
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/SHOUTING
vendored
Normal file
@@ -0,0 +1 @@
|
||||
value
|
||||
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/integer
vendored
Normal file
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/integer
vendored
Normal file
@@ -0,0 +1 @@
|
||||
123
|
||||
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/nested/config
vendored
Normal file
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/nested/config
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello
|
||||
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/secret
vendored
Normal file
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/config/secret
vendored
Normal file
@@ -0,0 +1 @@
|
||||
keepitsafe
|
||||
BIN
repos/effect-smol/packages/platform-node-shared/test/fixtures/helloworld.tar.gz
vendored
Normal file
BIN
repos/effect-smol/packages/platform-node-shared/test/fixtures/helloworld.tar.gz
vendored
Normal file
Binary file not shown.
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/text.txt
vendored
Normal file
1
repos/effect-smol/packages/platform-node-shared/test/fixtures/text.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
lorem ipsum dolar sit amet
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{ "path": "../effect" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { mergeConfig, type ViteUserConfig } from "vitest/config"
|
||||
import shared from "../../vitest.shared.ts"
|
||||
|
||||
const config: ViteUserConfig = {}
|
||||
|
||||
export default mergeConfig(shared, config)
|
||||
Reference in New Issue
Block a user