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

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

View File

@@ -0,0 +1,733 @@
# @effect/platform-node-shared
## 4.0.0-beta.99
### Patch Changes
- [#6411](https://github.com/Effect-TS/effect/pull/6411) [`b330264`](https://github.com/Effect-TS/effect/commit/b3302645fc1c23f7d03a3693dabdcb88763d5cbd) Thanks @sbking! - Fix child process termination to escalate to `SIGKILL` when the initial signal does not stop the process within `forceKillAfter`.
- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]:
- effect@4.0.0-beta.99
## 4.0.0-beta.98
### Patch Changes
- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]:
- effect@4.0.0-beta.98
## 4.0.0-beta.97
### Patch Changes
- Updated dependencies []:
- effect@4.0.0-beta.97
## 4.0.0-beta.96
### Patch Changes
- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]:
- effect@4.0.0-beta.96
## 4.0.0-beta.95
### Patch Changes
- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]:
- effect@4.0.0-beta.95
## 4.0.0-beta.94
### Patch Changes
- [#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

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Effectful Technologies Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,7 @@
# `@effect/platform-node-shared`
Provides shared Node.js-compatible implementations used by the Effect Node.js and Bun platform packages.
## Documentation
- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/platform-node-shared).

View File

@@ -0,0 +1,23 @@
{
"$schema": "../../node_modules/@effect/docgen/schema.json",
"srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/platform-node-shared/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"]
},
"plugins": [
{ "name": "@effect/language-service", "includeSuggestionsInTsc": false }
]
}
}

View File

@@ -0,0 +1,75 @@
{
"name": "@effect/platform-node-shared",
"type": "module",
"version": "4.0.0-beta.99",
"license": "MIT",
"description": "Unified interfaces for common platform-specific services",
"homepage": "https://effect.website",
"repository": {
"type": "git",
"url": "https://github.com/Effect-TS/effect.git",
"directory": "packages/platform-node-shared"
},
"bugs": {
"url": "https://github.com/Effect-TS/effect/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"
}
}

View File

@@ -0,0 +1,722 @@
/**
* 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) =>
killProcessGroup(command, childProcess, signal).pipe(
Effect.catch(() => killProcess(command, childProcess, signal)),
Effect.andThen(Deferred.await(exitSignal))
)
).pipe(
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) =>
killProcessGroup(command, childProcess, signal).pipe(
Effect.catch(() => killProcess(command, childProcess, signal)),
Effect.andThen(Deferred.await(exitSignal))
)
).pipe(
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 }
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
echo "this should not run because it doesn't have execute permissions"

View 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

View 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

View File

@@ -0,0 +1 @@
value

View File

@@ -0,0 +1 @@
123

View File

@@ -0,0 +1 @@
hello

View File

@@ -0,0 +1 @@
keepitsafe

View File

@@ -0,0 +1 @@
lorem ipsum dolar sit amet

View File

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

View File

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