0.2.0 (#5)
Publish / publish (push) Successful in 15s
Lint / lint (push) Successful in 11s

Co-authored-by: Julien Valverdé <julien.valverde@mailo.com>
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
2026-05-30 06:10:53 +02:00
parent 4c45f49092
commit 3fa879a66d
12 changed files with 969 additions and 223 deletions
+90 -33
View File
@@ -5,18 +5,18 @@
"": { "": {
"name": "@effect-lens/monorepo", "name": "@effect-lens/monorepo",
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.11", "@biomejs/biome": "^2.4.16",
"@effect/language-service": "^0.80.0", "@effect/language-service": "^0.86.2",
"@types/bun": "^1.3.6", "@types/bun": "^1.3.14",
"npm-check-updates": "^19.3.1", "npm-check-updates": "^22.2.1",
"npm-sort": "^0.0.4", "npm-sort": "^0.0.4",
"turbo": "^2.7.5", "turbo": "^2.9.16",
"typescript": "^5.9.3", "typescript": "^6.0.3",
}, },
}, },
"packages/effect-lens": { "packages/effect-lens": {
"name": "effect-lens", "name": "effect-lens",
"version": "0.1.0", "version": "0.2.0",
"peerDependencies": { "peerDependencies": {
"effect": "^3.21.0", "effect": "^3.21.0",
}, },
@@ -25,40 +25,55 @@
"name": "@effect-lens/example", "name": "@effect-lens/example",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@effect/platform": "^0.96.0", "@effect/platform": "^0.96.1",
"@effect/platform-browser": "^0.76.0", "@effect/platform-browser": "^0.76.0",
"effect": "^3.21.0", "@effect/platform-bun": "^0.89.0",
"effect": "^3.21.2",
"effect-lens": "workspace:*", "effect-lens": "workspace:*",
}, },
}, },
}, },
"packages": { "packages": {
"@biomejs/biome": ["@biomejs/biome@2.4.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.8", "@biomejs/cli-darwin-x64": "2.4.8", "@biomejs/cli-linux-arm64": "2.4.8", "@biomejs/cli-linux-arm64-musl": "2.4.8", "@biomejs/cli-linux-x64": "2.4.8", "@biomejs/cli-linux-x64-musl": "2.4.8", "@biomejs/cli-win32-arm64": "2.4.8", "@biomejs/cli-win32-x64": "2.4.8" }, "bin": { "biome": "bin/biome" } }, "sha512-ponn0oKOky1oRXBV+rlSaUlixUxf1aZvWC19Z41zBfUOUesthrQqL3OtiAlSB1EjFjyWpn98Q64DHelhA6jNlA=="], "@biomejs/biome": ["@biomejs/biome@2.4.16", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.16", "@biomejs/cli-darwin-x64": "2.4.16", "@biomejs/cli-linux-arm64": "2.4.16", "@biomejs/cli-linux-arm64-musl": "2.4.16", "@biomejs/cli-linux-x64": "2.4.16", "@biomejs/cli-linux-x64-musl": "2.4.16", "@biomejs/cli-win32-arm64": "2.4.16", "@biomejs/cli-win32-x64": "2.4.16" }, "bin": { "biome": "bin/biome" } }, "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ARx0tECE8I7S2C2yjnWYLNbBdDoPdq3oyNLhMglmuctThwUsuzFWRKrHmIGwIRWKz0Mat9DuzLEDp52hGnrxGQ=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Jg9/PsB9vDCJlANE8uhG7qDhb5w0Ix69D7XIIc8IfZPUoiPrbLm33k2Ig3NOJ/7nb3UbesFz3D1aDKm9DvzjhQ=="], "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-5CdrsJct76XG2hpKFwXnEtlT1p+4g4yV+XvvwBpzKsTNLO9c6iLlAxwcae2BJ7ekPGWjNGw9j09T5KGPKKxQig=="], "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-Zo9OhBQDJ3IBGPlqHiTISloo5H0+FBIpemqIJdW/0edJ+gEcLR+MZeZozcUyz3o1nXkVA7++DdRKQT0599j9jA=="], "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-PdKXspVEaMCQLjtZCn6vfSck/li4KX9KGwSDbZdgIqlrizJ2MnMcE3TvHa2tVfXNmbjMikzcfJpuPWH695yJrw=="], "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-Gi8quv8MEuDdKaPFtS2XjEnMqODPsRg6POT6KhoP+VrkNb+T2ywunVB+TvOU0LX1jAZzfBr+3V1mIbBhzAMKvw=="], "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-LoFatS0tnHv6KkCVpIy3qZCih+MxUMvdYiPWLHRri7mhi2vyOOs8OrbZBcLTUEWCS+ktO72nZMy4F96oMhkOHQ=="], "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.8", "", { "os": "win32", "cpu": "x64" }, "sha512-vAn7iXDoUbqFXqVocuq1sMYAd33p8+mmurqJkWl6CtIhobd/O6moe4rY5AJvzbunn/qZCdiDVcveqtkFh1e7Hg=="], "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="],
"@effect-lens/example": ["@effect-lens/example@workspace:packages/example"], "@effect-lens/example": ["@effect-lens/example@workspace:packages/example"],
"@effect/language-service": ["@effect/language-service@0.80.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-dKMATT1fDzaCpNrICpXga7sjJBtFLpKCAoE/1MiGXI8UwcHA9rmAZ2t52JO9g/kJpERWyomkJ+rl+VFlwNIofg=="], "@effect/cluster": ["@effect/cluster@0.58.2", "", { "dependencies": { "kubernetes-types": "^1.30.0" }, "peerDependencies": { "@effect/platform": "^0.96.1", "@effect/rpc": "^0.75.1", "@effect/sql": "^0.51.1", "@effect/workflow": "^0.18.0", "effect": "^3.21.2" } }, "sha512-oxQ3zUhXq0mJA7Y4TliALMP39Bx0LtAIxcqOW1Bdjh6uk+nG7kul/Puw80SwlcYGv3ul50SG+gvSRUTXB8d3JQ=="],
"@effect/platform": ["@effect/platform@0.96.0", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.21.0" } }, "sha512-U7PLhkVzg7zzrgFvyWATOzD6reL87KG/fcdOxgLWBQ/J5CCU6qdPAVG+0o6o+IxcsLoqGwxs+rFxaFzrdtDV1A=="], "@effect/experimental": ["@effect/experimental@0.60.0", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.96.0", "effect": "^3.21.0", "ioredis": "^5", "lmdb": "^3" }, "optionalPeers": ["ioredis", "lmdb"] }, "sha512-i5zIg7Xup2KgHyqHlYtkgqSE1bNzCL0GbbTQxrpIzKF0q/ebknOk/ox8B/gIq2vImjoEE81h/oxU+6i1NH210g=="],
"@effect/language-service": ["@effect/language-service@0.86.2", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-SaPln+8srOqDJDUwNTDmP5e+IYpEDr9+1epGznnsLqu8xvo6VnxyWARdeLpqvZJlb0Pgy9ca7ppqvvdWbHPXAg=="],
"@effect/platform": ["@effect/platform@0.96.1", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.10", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.21.2" } }, "sha512-cjB1QZZYEP8JXCFNGvBLVi0T6YUBQTmOVEUA3SDbiQ6RUO+p6CE3eyD2vMWmrz5nE8yY5QSAuOV9v0boEcUv+A=="],
"@effect/platform-browser": ["@effect/platform-browser@0.76.0", "", { "dependencies": { "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/platform": "^0.96.0", "effect": "^3.21.0" } }, "sha512-cUyBpcLstrP/HiNsIePMBAI6R1+u6aRFlAUZb4wf08y1d1Vqf/Dmxsq14ZjBfnSYiqBPrCeYf1ZI+qMGQQL0RA=="], "@effect/platform-browser": ["@effect/platform-browser@0.76.0", "", { "dependencies": { "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/platform": "^0.96.0", "effect": "^3.21.0" } }, "sha512-cUyBpcLstrP/HiNsIePMBAI6R1+u6aRFlAUZb4wf08y1d1Vqf/Dmxsq14ZjBfnSYiqBPrCeYf1ZI+qMGQQL0RA=="],
"@effect/platform-bun": ["@effect/platform-bun@0.89.0", "", { "dependencies": { "@effect/platform-node-shared": "^0.59.0", "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/cluster": "^0.58.0", "@effect/platform": "^0.96.0", "@effect/rpc": "^0.75.0", "@effect/sql": "^0.51.0", "effect": "^3.21.0" } }, "sha512-ReT5f2vujJfffMOBexrgwJd2RLxgfr2G0c1FyCsoflcjdQJ7RZE3cwHDp1M3hAzmG67wWAssMHqLsX6H/n27sQ=="],
"@effect/platform-node-shared": ["@effect/platform-node-shared@0.59.0", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "multipasta": "^0.2.7", "ws": "^8.18.2" }, "peerDependencies": { "@effect/cluster": "^0.58.0", "@effect/platform": "^0.96.0", "@effect/rpc": "^0.75.0", "@effect/sql": "^0.51.0", "effect": "^3.21.0" } }, "sha512-3bq2YKKfLY7UFauZSxqZUneCXoA3SMSls82V+0RKunvRlfPuPQW0hVn6t1RkvEdh0PDoygWG2mZXYQa6Iqgp9A=="],
"@effect/rpc": ["@effect/rpc@0.75.1", "", { "dependencies": { "msgpackr": "^1.11.10" }, "peerDependencies": { "@effect/platform": "^0.96.1", "effect": "^3.21.2" } }, "sha512-8yxF8+mMGGEbF8BUCp34HjdJj7CvTpGeZxBcpsDF6v7zPiGbJL1UDLzA8ZqYjmcngBHhPecbmeONTk/LiLAaEg=="],
"@effect/sql": ["@effect/sql@0.51.1", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/experimental": "^0.60.0", "@effect/platform": "^0.96.1", "effect": "^3.21.2" } }, "sha512-iPDAefrJcI0HcTk9keP9Gq8Pg08K1HmpnmZZt85AqyTcvorhoNsXDFiKBbPldfV2CortwVkacX8KjO9GPpSYCA=="],
"@effect/workflow": ["@effect/workflow@0.18.1", "", { "peerDependencies": { "@effect/experimental": "^0.60.0", "@effect/platform": "^0.96.1", "@effect/rpc": "^0.75.1", "effect": "^3.21.2" } }, "sha512-FxsUxkyvd7CyN7tw4bQgmAJv8tf8hUwy72bwGYzKGpeuiEObiUKgO1pg8xM49gB6EtwOdVRJhytwcFc8eM/6ow=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
@@ -71,29 +86,57 @@
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
"@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="],
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="],
"@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA=="],
"@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg=="],
"@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng=="],
"@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ=="],
"@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg=="],
"@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA=="],
"@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA=="],
"@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ=="],
"@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg=="],
"@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q=="],
"@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g=="],
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@turbo/darwin-64": ["@turbo/darwin-64@2.8.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ=="], "@turbo/darwin-64": ["@turbo/darwin-64@2.9.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-jLjApWTSNd7JZ5JaLYfelW1ytnGQOvB7ivl+2RD1xQvJTbi8I9gBjzcga7tDZVPyaxpl10YTfJt3BrYXR18KDw=="],
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.8.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw=="], "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YPgrn+5HIGzrx0O2a631SV4MBQUe4W/DafMFUuBVgaU32PW9/OTT0ehviF0QSxTXuRJlHvW2eUTemddF5/spmw=="],
"@turbo/linux-64": ["@turbo/linux-64@2.8.20", "", { "os": "linux", "cpu": "x64" }, "sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA=="], "@turbo/linux-64": ["@turbo/linux-64@2.9.16", "", { "os": "linux", "cpu": "x64" }, "sha512-vAEf1H6l26lTpl9FJ/peQo1NUB8RC0sbEJJz5mPcUhHA2bPDup2x3CZPgo/bH8S4cUcBLm4FN3UHd5iUO2RAew=="],
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.8.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA=="], "@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-xDBLR2PZg4BrQOchfG6svgpv5FCNJ2TOtT2psLdEJcdKo1BH+pnPs9Xj6pvUjgfkHbuvBOfeE4R6tvxMoQKDHQ=="],
"@turbo/windows-64": ["@turbo/windows-64@2.8.20", "", { "os": "win32", "cpu": "x64" }, "sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw=="], "@turbo/windows-64": ["@turbo/windows-64@2.9.16", "", { "os": "win32", "cpu": "x64" }, "sha512-NBAJnaUiGdgkSzQwUIdOvkCkcpTSu58G/sBGa0mvBtzfvFOOgrQwepKOOQ8cp6sWM6OcKDNFj2p1dsZA1OWjPg=="],
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.8.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw=="], "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-Y7SJppD0Z8wjO3Ec0ZGd9KQ4Yv0BMnA8CIowj5Vp+OEVsosXDG2weK6/t1RRLfJmc2Ozrnd6y4DOgQys+mn3WQ=="],
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"effect": ["effect@3.21.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ=="], "effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="],
"effect-lens": ["effect-lens@workspace:packages/effect-lens"], "effect-lens": ["effect-lens@workspace:packages/effect-lens"],
@@ -101,24 +144,38 @@
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
"msgpackr": ["msgpackr@1.11.9", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
"msgpackr": ["msgpackr@1.11.12", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg=="],
"msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"npm-check-updates": ["npm-check-updates@19.6.5", "", { "bin": { "npm-check-updates": "build/cli.js", "ncu": "build/cli.js" } }, "sha512-XlBUMC30relXfEerrnX239W9iB30U6Woz0Hj42Sv6iSF4EGOvj2mS2r45sZ3RglH0VPBxXOWooMxObZ/SMZhrw=="], "npm-check-updates": ["npm-check-updates@22.2.1", "", { "bin": { "npm-check-updates": "build/cli.js", "ncu": "build/cli.js" } }, "sha512-mGdIJfhtg+q0BzhbOpbOL73zhMZlgBQMG4wnBwPMMD5k96028UCuV0753YeSYk9odoh7HWK6/cY69bWxT7o+yg=="],
"npm-sort": ["npm-sort@0.0.4", "", { "bin": { "npm-sort": "./index.js" } }, "sha512-S5Id/3Jvr7Cf/QnWjRteprngERCBhhEFOM+wMhUrAYP060/HUBC1aL5GoXS3xITlgacJCWaSmP4HQaAt91nNYQ=="], "npm-sort": ["npm-sort@0.0.4", "", { "bin": { "npm-sort": "./index.js" } }, "sha512-S5Id/3Jvr7Cf/QnWjRteprngERCBhhEFOM+wMhUrAYP060/HUBC1aL5GoXS3xITlgacJCWaSmP4HQaAt91nNYQ=="],
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
"turbo": ["turbo@2.8.20", "", { "optionalDependencies": { "@turbo/darwin-64": "2.8.20", "@turbo/darwin-arm64": "2.8.20", "@turbo/linux-64": "2.8.20", "@turbo/linux-arm64": "2.8.20", "@turbo/windows-64": "2.8.20", "@turbo/windows-arm64": "2.8.20" }, "bin": { "turbo": "bin/turbo" } }, "sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ=="], "turbo": ["turbo@2.9.16", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.16", "@turbo/darwin-arm64": "2.9.16", "@turbo/linux-64": "2.9.16", "@turbo/linux-arm64": "2.9.16", "@turbo/windows-64": "2.9.16", "@turbo/windows-arm64": "2.9.16" }, "bin": { "turbo": "bin/turbo" } }, "sha512-NqgRQy6j6dPYcdSdv0q1g9QsZg7SWg87RERM8otw/1AtKU2yTFVClOM7cbwKzOonZr/Ek1blTBucw64L9H0Bwg=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="],
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
} }
} }
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@effect-lens/monorepo", "name": "@effect-lens/monorepo",
"packageManager": "bun@1.3.6", "packageManager": "bun@1.3.14",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"./packages/*" "./packages/*"
@@ -15,12 +15,12 @@
"clean:modules": "turbo clean:modules && rm -rf node_modules" "clean:modules": "turbo clean:modules && rm -rf node_modules"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.11", "@biomejs/biome": "^2.4.16",
"@effect/language-service": "^0.80.0", "@effect/language-service": "^0.86.2",
"@types/bun": "^1.3.6", "@types/bun": "^1.3.14",
"npm-check-updates": "^19.3.1", "npm-check-updates": "^22.2.1",
"npm-sort": "^0.0.4", "npm-sort": "^0.0.4",
"turbo": "^2.7.5", "turbo": "^2.9.16",
"typescript": "^5.9.3" "typescript": "^6.0.3"
} }
} }
+51 -22
View File
@@ -28,7 +28,7 @@ Lens<
A, // Type of the value the lens is focused on A, // Type of the value the lens is focused on
ER, // Errors that can happen when reading ER, // Errors that can happen when reading
EW, // Errors that can happen when writing EW, // Errors that can happen when writing
RE, // Requirements for reading RR, // Requirements for reading
RW // Requirements for writing RW // Requirements for writing
> >
``` ```
@@ -55,11 +55,14 @@ yield* Lens.update(lens, Array.replace(1, 1664))
Currently available: Currently available:
- `fromSubscriptionRef` - `fromSubscriptionRef`
- `fromSynchronizedRef` (note: since `SynchronizedRef` is not reactive (does not produce a stream of value changes), the resulting Lens' `changes` stream will only emit the current value of the lens when evaluated, and nothing else) - `fromSynchronizedRef` (note: since `SynchronizedRef` is not reactive (does not produce a stream of value changes), the resulting Lens' `changes` stream will only emit the current value of the lens when evaluated, and nothing else)
- `fromRef` (returns an effect because it creates an internal lock)
More to come!
#### Manually #### Manually
You can also create Lenses manually using `make` by providing a getter, a stream of changes and either a `set` or `modify` function depending on your needs. You can also create Lenses manually using `make` by providing:
- `get`: an effect that reads the current value,
- `changes`: a stream of value changes,
- `commit`: an effectful write primitive,
- `lock`: an effect that produces the lock used to serialize writes.
You can get pretty creative! Here's an example of a Lens that points to a specific key of the browser `LocalStorage`: You can get pretty creative! Here's an example of a Lens that points to a specific key of the browser `LocalStorage`:
```typescript ```typescript
@@ -67,8 +70,9 @@ You can get pretty creative! Here's an example of a Lens that points to a specif
const lens = Effect.all([ const lens = Effect.all([
KeyValueStore.KeyValueStore, KeyValueStore.KeyValueStore,
Effect.succeed("someKey"), Effect.succeed("someKey"),
Effect.makeSemaphore(1),
]).pipe( ]).pipe(
Effect.map(([kv, key]) => Lens.make({ Effect.map(([kv, key, semaphore]) => Lens.make({
get: kv.get(key), get: kv.get(key),
changes: kv.get(key).pipe( changes: kv.get(key).pipe(
@@ -83,9 +87,11 @@ const lens = Effect.all([
Stream.unwrap, Stream.unwrap,
), ),
set: a => Option.isSome(a) commit: a => Option.isSome(a)
? kv.set(key, a.value) ? kv.set(key, a.value)
: kv.remove(key), : kv.remove(key),
lock: Effect.succeed(semaphore.withPermits(1)),
})), })),
Effect.provide(BrowserKeyValueStore.layerLocalStorage), Effect.provide(BrowserKeyValueStore.layerLocalStorage),
@@ -95,20 +101,6 @@ const lens = Effect.all([
Note: while Lens supports asynchronous effects for the proxy logic, we would recommend keeping them synchronous to preserve atomicity. Note: while Lens supports asynchronous effects for the proxy logic, we would recommend keeping them synchronous to preserve atomicity.
If a `Lens` depends on a service in its environment, you can provide that service directly to the lens:
```typescript
class Offset extends Context.Tag("Offset")<Offset, { readonly value: number }>() {}
const root = Lens.fromSubscriptionRef(ref)
const offsetLens = Lens.mapEffect(
root,
n => Effect.map(Offset, ({ value }) => n + value),
(_n, next) => Effect.map(Offset, ({ value }) => next - value),
)
const runnableLens = Lens.provide(offsetLens, Offset, { value: 5 })
```
### Focusing ### Focusing
@@ -181,8 +173,6 @@ Currently available:
| `focusChunkAt` | Focuses to an indexed entry of a `Chunk`. Replaces the parent `Chunk` immutably when writing to the focused element | Immutable | | | `focusChunkAt` | Focuses to an indexed entry of a `Chunk`. Replaces the parent `Chunk` immutably when writing to the focused element | Immutable | |
| `focusOption` | Focuses to the value inside an `Option`. Wraps writes back into `Option.some` | Immutable | Reading or writing fails with `NoSuchElementException` when the parent option is `None` | | `focusOption` | Focuses to the value inside an `Option`. Wraps writes back into `Option.some` | Immutable | Reading or writing fails with `NoSuchElementException` when the parent option is `None` |
Also more to come!
#### Manually #### Manually
You can create focused Lenses by composing them manually using `map`, `mapEffect` and `unwrap`: You can create focused Lenses by composing them manually using `map`, `mapEffect` and `unwrap`:
```typescript ```typescript
@@ -211,6 +201,42 @@ const benzemonstreLens = ref.pipe(
// As you can see, this is automatically tracked by the Lens type // As you can see, this is automatically tracked by the Lens type
``` ```
#### Low-level derived lenses
For advanced cases, you can derive a Lens manually using `derive`. This is the primitive used by the built-in transforms.
A derived Lens describes how to transform three parent channels:
- `resolve`: reads the parent and returns the focused value plus a `commit` function to rebuild the parent,
- `mapStream`: transforms the parent `changes` stream,
- `mapLock`: transforms the parent write lock.
Most custom focusing logic should use `map` or `mapEffect`, but `derive` is useful when you need full control over read, stream, lock, and write-back behavior.
```typescript
declare const lens: Lens.Lens<User, never, never, never, never>
const nameLens = lens.pipe(
Lens.derive({
resolve: parent => Effect.map(
parent,
resolved => ({
value: resolved.value.name,
commit: next => resolved.commit(
Effect.map(next, name => ({
...resolved.value,
name,
})),
),
}),
),
mapStream: Stream.map(user => user.name),
// This derived Lens does not add lock behavior, so it reuses the parent lock.
mapLock: identity,
}),
)
```
### Subscribable ### Subscribable
@@ -255,8 +281,11 @@ Currently available:
| - | - | | - | - |
| `focusObjectOn` | Focuses to the field of an object | | `focusObjectOn` | Focuses to the field of an object |
| `focusArrayAt` | Focuses to an indexed entry of an array | | `focusArrayAt` | Focuses to an indexed entry of an array |
| `focusArrayLength` | Focuses to the length of an array |
| `focusTupleAt` | Focuses to an indexed entry of a tuple | | `focusTupleAt` | Focuses to an indexed entry of a tuple |
| `focusChunkAt` | Focuses to an indexed entry of a `Chunk` | | `focusChunkAt` | Focuses to an indexed entry of a `Chunk` |
| `focusChunkSize` | Focuses to the size of a `Chunk` |
| `focusIterableSize` | Focuses to the size of an iterable |
## Todo ## Todo
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "effect-lens", "name": "effect-lens",
"description": "An effectful Lens type to easily manage nested state", "description": "An effectful Lens type to easily manage nested state",
"version": "0.1.5", "version": "0.2.0",
"type": "module", "type": "module",
"files": [ "files": [
"./README.md", "./README.md",
+156 -5
View File
@@ -1,11 +1,113 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { Chunk, Context, Effect, Option, SubscriptionRef } from "effect" import { Chunk, Context, Effect, Either, identity, Option, Stream, SubscriptionRef } from "effect"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
describe("Lens", () => { describe("Lens", () => {
class Offset extends Context.Tag("Offset")<Offset, { readonly value: number }>() {} class Offset extends Context.Tag("Offset")<Offset, { readonly value: number }>() {}
test("mapErrorRead transforms read errors", async () => {
const lens = Lens.mapErrorRead(
Lens.make<number, "read", never, never, never>({
get: Effect.fail("read" as const),
changes: Stream.fail("read" as const),
commit: () => Effect.void,
lock: Effect.succeed(identity),
}),
error => `mapped:${ error }`,
)
const result = await Effect.runPromise(Effect.either(Lens.get(lens)))
expect(result).toEqual(Either.left("mapped:read"))
})
test("mapErrorWrite transforms modify errors", async () => {
const lens = Lens.mapErrorWrite(
Lens.make<number, never, "write", never, never>({
get: Effect.succeed(1),
changes: Stream.make(1),
commit: () => Effect.fail("write" as const),
lock: Effect.succeed(identity),
}),
() => "mapped-write",
)
const result = await Effect.runPromise(Effect.either(Lens.set(lens, 2)))
expect(result).toEqual(Either.left("mapped-write"))
})
test("mapError transforms read and modify errors", async () => {
const lens = Lens.mapError(
Lens.make<number, "read", "write", never, never>({
get: Effect.fail("read" as const),
changes: Stream.fail("read" as const),
commit: () => Effect.fail("write" as const),
lock: Effect.succeed(identity),
}),
() => "mapped",
)
const result = await Effect.runPromise(Effect.all([
Effect.either(Lens.get(lens)),
Effect.either(Lens.set(lens, 1)),
] as const))
expect(result[0]).toEqual(Either.left("mapped"))
expect(result[1]).toEqual(Either.left("mapped"))
})
test("tapErrorRead runs an effect on read failures", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make(0),
counter => {
const lens = Lens.tapErrorRead(
Lens.make<number, "read", never, never, never>({
get: Effect.fail("read" as const),
changes: Stream.fail("read" as const),
commit: () => Effect.void,
lock: Effect.succeed(identity),
}),
() => SubscriptionRef.modify(counter, n => [void 0, n + 1] as const),
)
return Effect.flatMap(
Effect.either(Lens.get(lens)),
() => counter.get,
)
},
),
)
expect(result).toBe(1)
})
test("tapErrorWrite runs an effect on modify failures", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make(0),
counter => {
const lens = Lens.tapErrorWrite(
Lens.make<number, never, "write", never, never>({
get: Effect.succeed(1),
changes: Stream.make(1),
commit: () => Effect.fail("write" as const),
lock: Effect.succeed(identity),
}),
() => SubscriptionRef.modify(counter, n => [void 0, n + 1] as const),
)
return Effect.flatMap(
Effect.either(Lens.set(lens, 2)),
() => counter.get,
)
},
),
)
expect(result).toBe(1)
})
test("mapOption transforms Some values and preserves None", async () => { test("mapOption transforms Some values and preserves None", async () => {
const result = await Effect.runPromise( const result = await Effect.runPromise(
Effect.flatMap( Effect.flatMap(
@@ -56,19 +158,18 @@ describe("Lens", () => {
expect(result[1]).toEqual(Option.some(50)) // 100 / 2 expect(result[1]).toEqual(Option.some(50)) // 100 / 2
}) })
test("provide supplies a service to get and modify", async () => { test("provideContext supplies a service to get and modify", async () => {
const result = await Effect.runPromise( const result = await Effect.runPromise(
Effect.flatMap( Effect.flatMap(
SubscriptionRef.make(10), SubscriptionRef.make(10),
parent => { parent => {
const lens = Lens.provide( const lens = Lens.provideContext(
Lens.mapEffect( Lens.mapEffect(
Lens.fromSubscriptionRef(parent), Lens.fromSubscriptionRef(parent),
n => Effect.map(Offset, ({ value }) => n + value), n => Effect.map(Offset, ({ value }) => n + value),
(_n, next) => Effect.map(Offset, ({ value }) => next - value), (_n, next) => Effect.map(Offset, ({ value }) => next - value),
), ),
Offset, Context.make(Offset, { value: 5 }),
{ value: 5 },
) )
return Effect.flatMap( return Effect.flatMap(
@@ -86,6 +187,56 @@ describe("Lens", () => {
expect(result[1]).toBe(25) expect(result[1]).toBe(25)
}) })
test("modifyEffect updates are atomic under concurrency", async () => {
const iterations = 100
const result = await Effect.runPromise(Effect.flatMap(
SubscriptionRef.make({ count: 0 }),
parent => {
const countLens = Lens.focusObjectOn(Lens.fromSubscriptionRef(parent), "count")
return Effect.flatMap(
Effect.forEach(
Array.from({ length: iterations }),
() => Lens.updateEffect(
countLens,
count => Effect.as(Effect.yieldNow(), count + 1),
),
{ concurrency: "unbounded", discard: true },
),
() => parent.get,
)
},
))
expect(result.count).toBe(iterations)
})
test("unwrap delegates reads, writes, and locking to the inner lens", async () => {
const iterations = 100
const result = await Effect.runPromise(Effect.flatMap(
SubscriptionRef.make(0),
parent => {
const lens = Lens.unwrap(Effect.succeed(Lens.fromSubscriptionRef(parent)))
return Effect.flatMap(
Effect.forEach(
Array.from({ length: iterations }),
() => Lens.updateEffect(
lens,
count => Effect.as(Effect.yieldNow(), count + 1),
),
{ concurrency: "unbounded", discard: true },
),
() => Effect.all([Lens.get(lens), parent.get] as const),
)
},
))
expect(result).toEqual([iterations, iterations])
})
test("focusObjectOn focuses a nested property without touching other fields", async () => { test("focusObjectOn focuses a nested property without touching other fields", async () => {
const [initialCount, updatedState] = await Effect.runPromise( const [initialCount, updatedState] = await Effect.runPromise(
Effect.flatMap( Effect.flatMap(
+534 -118
View File
@@ -1,4 +1,4 @@
import { Array, Chunk, type Context, Effect, Function, identity, Option, Pipeable, Predicate, Readable, Stream, type SubscriptionRef, type SynchronizedRef } from "effect" import { Array, Chunk, type Context, Effect, Function, identity, Option, Pipeable, Predicate, PubSub, Readable, Ref, Stream, type SubscriptionRef, type SynchronizedRef } from "effect"
import type { NoSuchElementException } from "effect/Cause" import type { NoSuchElementException } from "effect/Cause"
import * as Subscribable from "./Subscribable.js" import * as Subscribable from "./Subscribable.js"
@@ -17,81 +17,210 @@ export interface Lens<in out A, in out ER = never, in out EW = never, in out RR
extends Subscribable.Subscribable<A, ER, RR> { extends Subscribable.Subscribable<A, ER, RR> {
readonly [LensTypeId]: LensTypeId readonly [LensTypeId]: LensTypeId
readonly modify: <B, E1 = never, R1 = never>( readonly modifyEffect: <B, E1 = never, R1 = never>(
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1> f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.Effect<B, ER | EW | E1, RR | RW | R1> ) => Effect.Effect<B, ER | EW | E1, RR | RW | R1>
} }
/**
* Internal `Lens` implementation.
*/
export class LensImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
extends Pipeable.Class() implements Lens<A, ER, EW, RR, RW> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [LensTypeId]: LensTypeId = LensTypeId
constructor(
readonly get: Effect.Effect<A, ER, RR>,
readonly changes: Stream.Stream<A, ER, RR>,
readonly modify: <B, E1 = never, R1 = never>(
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.Effect<B, ER | EW | E1, RR | RW | R1>,
) {
super()
}
}
/** /**
* Checks whether a value is a `Lens`. * Checks whether a value is a `Lens`.
*/ */
export const isLens = (u: unknown): u is Lens<unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, LensTypeId) export const isLens = (u: unknown): u is Lens<unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, LensTypeId)
/** export const LensImplTypeId: unique symbol = Symbol.for("@effect-fc/Lens/LensImpl")
* Creates a `Lens` by supplying how to read the current value, observe changes, and apply transformations. export type LensImplTypeId = typeof LensImplTypeId
*
* Either `modify` or `set` needs to be supplied. export declare namespace LensImpl {
*/ export interface Resolved<in out A, in out EW = never, in out RW = never> {
export const make = <A, ER, EW, RR, RW>( readonly value: A
options: { readonly commit: <E = never, R = never>(
next: Effect.Effect<A, E, R>
) => Effect.Effect<void, EW | E, RW | R>
}
export interface Lock {
<A1, E1, R1>(self: Effect.Effect<A1, E1, R1>): Effect.Effect<A1, E1, R1>
}
}
export abstract class LensImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
extends Pipeable.Class() implements Lens<A, ER, EW, RR, RW> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [LensTypeId]: LensTypeId = LensTypeId
readonly [LensImplTypeId]: LensImplTypeId = LensImplTypeId
abstract readonly resolve: Effect.Effect<LensImpl.Resolved<A, EW, RW>, ER, RR>
abstract readonly changes: Stream.Stream<A, ER, RR>
abstract readonly lock: Effect.Effect<LensImpl.Lock, EW, RW>
get get() { return Effect.map(this.resolve, resolved => resolved.value) }
modifyEffect<B, E1 = never, R1 = never>(
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>,
): Effect.Effect<B, ER | EW | E1, RR | RW | R1> {
return Effect.flatMap(
this.lock,
lock => lock(Effect.flatMap(
this.resolve,
resolved => Effect.flatMap(
f(resolved.value),
([c, next]) => Effect.as(resolved.commit(Effect.succeed(next)), c),
),
)),
)
}
}
export const isLensImpl = (u: unknown): u is LensImpl<unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, LensImplTypeId)
export const asLensImpl = <A, ER, EW, RR, RW>(
lens: Lens<A, ER, EW, RR, RW>
): LensImpl<A, ER, EW, RR, RW> => {
if (!isLensImpl(lens))
throw new Error("Not a 'LensImpl'")
return lens as LensImpl<A, ER, EW, RR, RW>
}
export declare namespace LensLazyImpl {
export interface Source<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never> {
readonly get: Effect.Effect<A, ER, RR> readonly get: Effect.Effect<A, ER, RR>
readonly changes: Stream.Stream<A, ER, RR> readonly changes: Stream.Stream<A, ER, RR>
} & ( readonly commit: (a: A) => Effect.Effect<void, EW, RW>
| { readonly lock: Effect.Effect<LensImpl.Lock, EW, RW>
readonly modify: <B, E1 = never, R1 = never>( }
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1> }
) => Effect.Effect<B, ER | EW | E1, RR | RW | R1>
} export class LensLazyImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
| { readonly set: (a: A) => Effect.Effect<void, EW, RW> } extends LensImpl<A, ER, EW, RR, RW> {
) constructor(
): Lens<A, ER, EW, RR, RW> => new LensImpl<A, ER, EW, RR, RW>( readonly source: LensLazyImpl.Source<A, ER, EW, RR, RW>,
options.get, ) {
options.changes, super()
Predicate.hasProperty(options, "modify") }
? options.modify
: <B, E1 = never, R1 = never>( get resolve(): Effect.Effect<LensImpl.Resolved<A, EW, RW>, ER, RR> {
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1> return Effect.map(
) => Effect.flatMap( this.source.get,
options.get, value => ({
a => Effect.flatMap(f(a), ([b, next]) => Effect.as(options.set(next), b) value,
)), commit: next => Effect.flatMap(next, value => this.source.commit(value)),
) }),
)
}
get changes() { return this.source.changes }
get lock() { return this.source.lock }
}
/** /**
* Creates a `Lens` that proxies a `SubscriptionRef`. * Creates a `Lens` by supplying how to read the current value, observe changes, and apply transformations.
*/ */
export const fromSubscriptionRef = <A>( export const make = <A, ER, EW, RR, RW>(
ref: SubscriptionRef.SubscriptionRef<A> source: LensLazyImpl.Source<A, ER, EW, RR, RW>
): Lens<A, never, never, never, never> => make({ ): Lens<A, ER, EW, RR, RW> => new LensLazyImpl(source)
get get() { return ref.get },
get changes() { return ref.changes },
modify: <B, E1 = never, R1 = never>( export class UnwrappedLensImpl<in out A, in out ER, in out EW, in out RR, in out RW, in out E1, in out R1>
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1> extends LensImpl<A, ER | E1, EW | E1, RR | R1, RW | R1> {
) => ref.modifyEffect(f), constructor(
readonly effect: Effect.Effect<Lens<A, ER, EW, RR, RW>, E1, R1>
) {
super()
}
get resolve(): Effect.Effect<LensImpl.Resolved<A, EW | E1, RW | R1>, ER | E1, RR | R1> {
return Effect.map(
Effect.flatMap(this.effect, l => asLensImpl(l).resolve),
resolved => ({
value: resolved.value,
commit: next => resolved.commit(next),
}),
)
}
get changes() { return Stream.unwrap(Effect.map(this.effect, l => l.changes)) }
get lock() { return Effect.flatMap(this.effect, l => asLensImpl(l).lock) }
}
/**
* Flattens an effectful `Lens`.
*/
export const unwrap = <A, ER, EW, RR, RW, E1, R1>(
effect: Effect.Effect<Lens<A, ER, EW, RR, RW>, E1, R1>
): Lens<A, ER | E1, EW | E1, RR | R1, RW | R1> => new UnwrappedLensImpl(effect)
export class RefLensImpl<in out A>
extends LensImpl<A, never, never, never, never> {
constructor(
readonly ref: Ref.Ref<A>,
readonly semaphore: Effect.Semaphore,
) {
super()
}
get resolve(): Effect.Effect<LensImpl.Resolved<A>, never, never> {
return Effect.map(
Ref.get(this.ref),
value => ({
value,
commit: next => Effect.flatMap(
next,
value => Ref.set(this.ref, value),
),
}),
)
}
get changes() { return Stream.unwrap(Effect.map(Ref.get(this.ref), Stream.make)) }
get lock() { return Effect.succeed(this.semaphore.withPermits(1)) }
}
/**
* Creates a `Lens` that proxies a `Ref`.
*
* Note: since `Ref` does not provide any kind of reactivity mechanism, the produced `Lens` will be non-reactive.
* This means its `changes` stream will only emit the current value once when evaluated and nothing else.
*/
export const fromRef = Effect.fnUntraced(function* <A>(
ref: Ref.Ref<A>
): Effect.fn.Return<Lens<A, never, never, never, never>, never, never> {
return new RefLensImpl(ref, yield* Effect.makeSemaphore(1))
}) })
export declare namespace SynchronizedRefLensImpl {
export interface SynchronizedRefWithInternals<in out A>
extends SynchronizedRef.SynchronizedRef<A> {
readonly ref: Ref.Ref<A>
readonly withLock: LensImpl.Lock
}
}
export class SynchronizedRefLensImpl<in out A>
extends LensImpl<A, never, never, never, never> {
constructor(
readonly ref: SynchronizedRefLensImpl.SynchronizedRefWithInternals<A>
) {
super()
}
get resolve(): Effect.Effect<LensImpl.Resolved<A>, never, never> {
return Effect.map(
Ref.get(this.ref.ref),
value => ({
value,
commit: next => Effect.flatMap(
next,
value => Ref.set(this.ref.ref, value),
),
}),
)
}
get changes() { return Stream.unwrap(Effect.map(Ref.get(this.ref.ref), Stream.make)) }
get lock() { return Effect.succeed(this.ref.withLock) }
}
/** /**
* Creates a `Lens` that proxies a `SynchronizedRef`. * Creates a `Lens` that proxies a `SynchronizedRef`.
* *
@@ -100,26 +229,112 @@ export const fromSubscriptionRef = <A>(
*/ */
export const fromSynchronizedRef = <A>( export const fromSynchronizedRef = <A>(
ref: SynchronizedRef.SynchronizedRef<A> ref: SynchronizedRef.SynchronizedRef<A>
): Lens<A, never, never, never, never> => make({ ): Lens<A, never, never, never, never> => new SynchronizedRefLensImpl(ref as SynchronizedRefLensImpl.SynchronizedRefWithInternals<A>)
get get() { return ref.get },
get changes() { return Stream.unwrap(Effect.map(ref.get, Stream.make)) },
modify: <B, E1 = never, R1 = never>( export declare namespace SubscriptionRefLensImpl {
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1> export interface SubscriptionRefWithInternals<in out A>
) => ref.modifyEffect(f), extends SubscriptionRef.SubscriptionRef<A> {
}) readonly ref: Ref.Ref<A>
readonly pubsub: PubSub.PubSub<A>
readonly semaphore: Effect.Semaphore
}
}
export class SubscriptionRefLensImpl<in out A>
extends LensImpl<A, never, never, never, never> {
constructor(
readonly ref: SubscriptionRefLensImpl.SubscriptionRefWithInternals<A>
) {
super()
}
get resolve(): Effect.Effect<LensImpl.Resolved<A>, never, never> {
return Effect.map(
this.ref.get,
value => ({
value,
commit: next => Effect.flatMap(
next,
value => Effect.zipLeft(
Ref.set(this.ref.ref, value),
PubSub.publish(this.ref.pubsub, value),
),
),
}),
)
}
get changes() { return this.ref.changes }
get lock() { return Effect.succeed(this.ref.semaphore.withPermits(1)) }
}
/** /**
* Flattens an effectful `Lens`. * Creates a `Lens` that proxies a `SubscriptionRef`.
*/ */
export const unwrap = <A, ER, EW, RR, RW, E1, R1>( export const fromSubscriptionRef = <A>(
effect: Effect.Effect<Lens<A, ER, EW, RR, RW>, E1, R1> ref: SubscriptionRef.SubscriptionRef<A>
): Lens<A, ER | E1, EW | E1, RR | R1, RW | R1> => make({ ): Lens<A, never, never, never, never> => new SubscriptionRefLensImpl(ref as SubscriptionRefLensImpl.SubscriptionRefWithInternals<A>)
get: Effect.flatMap(effect, l => l.get),
changes: Stream.unwrap(Effect.map(effect, l => l.changes)),
modify: <B, E2 = never, R2 = never>( export declare namespace DerivedLensImpl {
f: (a: A) => Effect.Effect<readonly [B, A], E2, R2> export interface Source<
) => Effect.flatMap(effect, l => l.modify(f)), in out A,
}) in out B,
in out ER = never,
in out ESR = never,
in out EW = never,
in out ESW = never,
in out RR = never,
in out RSR = never,
in out RW = never,
in out RSW = never,
> {
readonly resolve: (effect: Effect.Effect<LensImpl.Resolved<B, ESW, RSW>, ESR, RSR>) => Effect.Effect<LensImpl.Resolved<A, EW, RW>, ER, RR>
readonly mapStream: (stream: Stream.Stream<B, ESR, RSR>) => Stream.Stream<A, ER, RR>
readonly mapLock: (lock: Effect.Effect<LensImpl.Lock, ESW, RSW>) => Effect.Effect<LensImpl.Lock, EW, RW>
}
}
export class DerivedLensImpl<
in out A,
in out B,
in out ER = never,
in out PER = never,
in out EW = never,
in out PEW = never,
in out RR = never,
in out PRR = never,
in out RW = never,
in out PRW = never,
>
extends LensImpl<A, ER, EW, RR, RW> {
constructor(
readonly parent: LensImpl<B, PER, PEW, PRR, PRW>,
readonly source: DerivedLensImpl.Source<A, B, ER, PER, EW, PEW, RR, PRR, RW, PRW>,
) {
super()
}
get resolve() { return this.source.resolve(this.parent.resolve) }
get changes() { return this.source.mapStream(this.parent.changes) }
get lock() { return this.source.mapLock(this.parent.lock) }
}
/**
* Derives a new `Lens` by linking a step to an existing parent lens.
*/
export const derive: {
<A, B, ER, EW, RR, RW, ER2, EW2, RR2, RW2>(
self: Lens<B, ER, EW, RR, RW>,
source: DerivedLensImpl.Source<A, B, ER2, ER, EW2, EW, RR2, RR, RW2, RW>,
): Lens<A, ER2, EW2, RR2, RW2>
<A, B, ER, EW, RR, RW, ER2, EW2, RR2, RW2>(
source: DerivedLensImpl.Source<A, B, ER2, ER, EW2, EW, RR2, RR, RW2, RW>,
): (self: Lens<B, ER, EW, RR, RW>) => Lens<A, ER2, EW2, RR2, RW2>
} = Function.dual(2, <A, B, ER, EW, RR, RW, ER2, EW2, RR2, RW2>(
self: Lens<B, ER, EW, RR, RW>,
source: DerivedLensImpl.Source<A, B, ER2, ER, EW2, EW, RR2, RR, RW2, RW>,
): Lens<A, ER2, EW2, RR2, RW2> => new DerivedLensImpl(asLensImpl(self), source))
/** /**
@@ -139,14 +354,16 @@ export const map: {
self: Lens<A, ER, EW, RR, RW>, self: Lens<A, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => B, get: (a: NoInfer<A>) => B,
set: (a: NoInfer<A>, b: B) => NoInfer<A>, set: (a: NoInfer<A>, b: B) => NoInfer<A>,
): Lens<B, ER, EW, RR, RW> => make({ ): Lens<B, ER, EW, RR, RW> => derive(self, {
get get() { return Effect.map(self.get, get) }, resolve: parent => Effect.map(
get changes() { return Stream.map(self.changes, get) }, parent,
modify: <C, E1 = never, R1 = never>( resolved => ({
f: (b: B) => Effect.Effect<readonly [C, B], E1, R1> value: get(resolved.value),
) => self.modify(a => commit: next => resolved.commit(Effect.map(next, b => set(resolved.value, b))),
Effect.flatMap(f(get(a)), ([c, next]) => Effect.succeed([c, set(a, next)])) }),
), ),
mapStream: Stream.map(get),
mapLock: identity,
})) }))
/** /**
@@ -166,21 +383,19 @@ export const mapEffect: {
self: Lens<A, ER, EW, RR, RW>, self: Lens<A, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>, get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>,
set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>, set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>,
): Lens<B, ER | EGet, EW | ESet, RR | RGet, RW | RSet> => make({ ): Lens<B, ER | EGet, EW | ESet, RR | RGet, RW | RSet> => derive(self, {
get get() { return Effect.flatMap(self.get, get) }, resolve: parent => Effect.flatMap(
get changes() { return Stream.mapEffect(self.changes, get) }, parent,
modify: <C, E1 = never, R1 = never>( resolved => Effect.map(
f: (b: B) => Effect.Effect<readonly [C, B], E1, R1> get(resolved.value),
) => self.modify(a => Effect.flatMap( value => ({
get(a), value,
b => Effect.flatMap( commit: next => resolved.commit(Effect.flatMap(next, b => set(resolved.value, b))),
f(b), }),
([c, bNext]) => Effect.flatMap( ),
set(a, bNext), ),
nextA => Effect.succeed([c, nextA] as const), mapStream: Stream.mapEffect(get),
), mapLock: identity<Effect.Effect<LensImpl.Lock, EW | ESet, RW | RSet>>,
)
)),
})) }))
/** /**
@@ -263,16 +478,213 @@ export const mapStream: {
} = Function.dual(2, <A, ER, EW, RR, RW>( } = Function.dual(2, <A, ER, EW, RR, RW>(
self: Lens<A, ER, EW, RR, RW>, self: Lens<A, ER, EW, RR, RW>,
f: (changes: Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>) => Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>, f: (changes: Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>) => Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>,
): Lens<A, ER, EW, RR, RW> => make({ ): Lens<A, ER, EW, RR, RW> => derive(self, {
get get() { return self.get }, resolve: identity,
get changes() { return f(self.changes) }, mapStream: f,
get modify() { return self.modify }, mapLock: identity,
}))
/**
* Transforms read errors of a `Lens`.
*
* Applies to `get` and `changes` while leaving `modify` unchanged.
*/
export const mapErrorRead: {
<A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => E2,
): Lens<A, E2, EW, RR, RW>
<A, ER, EW, RR, RW, E2>(
f: (error: NoInfer<ER>) => E2,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, EW, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => E2,
): Lens<A, E2, EW, RR, RW> => derive(self, {
resolve: Effect.mapError(f),
mapStream: Stream.mapError(f),
mapLock: identity,
}))
/**
* Transforms modify errors of a `Lens`.
*
* Applies to the commit/rebuild portion of `modifyEffect` while leaving failures from the
* user-supplied callback unchanged.
*/
export const mapErrorWrite: {
<A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<EW>) => E2,
): Lens<A, ER, E2, RR, RW>
<A, ER, EW, RR, RW, E2>(
f: (error: NoInfer<EW>) => E2,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, E2, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<EW>) => E2,
): Lens<A, ER, E2, RR, RW> => derive(self, {
resolve: parent => Effect.map(parent, resolved => ({
value: resolved.value,
commit: next => Effect.flatMap(
next,
value => Effect.mapError(resolved.commit(Effect.succeed(value)), f),
),
})),
mapStream: identity,
mapLock: Effect.mapError(f),
}))
/**
* Transforms all errors of a `Lens`.
*
* Applies to `get`, `changes`, and the commit/rebuild portion of `modifyEffect` while leaving
* failures from the user-supplied callback unchanged.
*/
export const mapError: {
<A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER | EW>) => E2,
): Lens<A, E2, E2, RR, RW>
<A, ER, EW, RR, RW, E2>(
f: (error: NoInfer<ER | EW>) => E2,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, E2, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER | EW>) => E2,
): Lens<A, E2, E2, RR, RW> => derive(self, {
resolve: parent => Effect.map(
Effect.mapError(parent, f),
resolved => ({
value: resolved.value,
commit: next => Effect.flatMap(
next,
value => Effect.mapError(resolved.commit(Effect.succeed(value)), f),
),
}),
),
mapStream: Stream.mapError(f),
mapLock: Effect.mapError(f),
}))
/**
* Runs an effect when read failures occur.
*
* Applies to `get` and `changes` while leaving `modify` unchanged.
*/
export const tapErrorRead: {
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => Effect.Effect<unknown, E2, R2>,
): Lens<A, ER | E2, EW, RR | R2, RW>
<A, ER, EW, RR, RW, E2, R2>(
f: (error: NoInfer<ER>) => Effect.Effect<unknown, E2, R2>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER | E2, EW, RR | R2, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => Effect.Effect<unknown, E2, R2>,
): Lens<A, ER | E2, EW, RR | R2, RW> => derive(self, {
resolve: Effect.tapError(f),
mapStream: Stream.tapError(f),
mapLock: identity,
}))
/**
* Runs an effect when modify failures occur.
*
* Applies to the commit/rebuild portion of `modifyEffect` while leaving failures from the
* user-supplied callback unchanged.
*/
export const tapErrorWrite: {
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<EW>) => Effect.Effect<unknown, E2, R2>,
): Lens<A, ER, EW | E2, RR, RW | R2>
<A, ER, EW, RR, RW, E2, R2>(
f: (error: NoInfer<EW>) => Effect.Effect<unknown, E2, R2>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW | E2, RR, RW | R2>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<EW>) => Effect.Effect<unknown, E2, R2>,
): Lens<A, ER, EW | E2, RR, RW | R2> => derive(self, {
resolve: parent => Effect.map(parent, resolved => ({
value: resolved.value,
commit: next => Effect.flatMap(
next,
value => Effect.tapError(resolved.commit(Effect.succeed(value)), f),
),
})),
mapStream: identity,
mapLock: Effect.tapError(f),
}))
/**
* Runs an effect when any `Lens` failure occurs.
*
* Applies to `get`, `changes`, and the commit/rebuild portion of `modifyEffect` while leaving
* failures from the user-supplied callback unchanged.
*/
export const tapError: {
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER | EW>) => Effect.Effect<unknown, E2, R2>,
): Lens<A, ER | E2, EW | E2, RR | R2, RW | R2>
<A, ER, EW, RR, RW, E2, R2>(
f: (error: NoInfer<ER | EW>) => Effect.Effect<unknown, E2, R2>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER | E2, EW | E2, RR | R2, RW | R2>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER | EW>) => Effect.Effect<unknown, E2, R2>,
): Lens<A, ER | E2, EW | E2, RR | R2, RW | R2> => derive(self, {
resolve: parent => Effect.map(
Effect.tapError(parent, f),
resolved => ({
value: resolved.value,
commit: next => Effect.flatMap(
next,
value => Effect.tapError(resolved.commit(Effect.succeed(value)), f),
),
}),
),
mapStream: Stream.tapError(f),
mapLock: Effect.tapError(f),
}))
/**
* Provides a `Context` to a `Lens`, removing it from both the read and write environments.
*/
export const provideContext: {
<A, ER, EW, RR, RW, R2>(
self: Lens<A, ER, EW, RR, RW>,
context: Context.Context<R2>,
): Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>>
<R2>(
context: Context.Context<R2>,
): <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>>
} = Function.dual(2, <A, ER, EW, RR, RW, R2>(
self: Lens<A, ER, EW, RR, RW>,
context: Context.Context<R2>,
): Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>> => derive(self, {
resolve: parent => Effect.map(
Effect.provide(parent, context),
resolved => ({
value: resolved.value,
commit: next => Effect.provide(resolved.commit(next), context),
}),
),
mapStream: Stream.provideSomeContext(context),
mapLock: Effect.provide(context),
})) }))
/** /**
* Provides a single service to a `Lens`, removing it from both the read and write environments. * Provides a single service to a `Lens`, removing it from both the read and write environments.
*
* This is the `Lens` equivalent of `Effect.provideService`: use it when a lens requires one
* `Context.Tag` and you already have the concrete service value.
*/ */
export const provide: { export const provideService: {
<A, ER, EW, RR, RW, I, S>( <A, ER, EW, RR, RW, I, S>(
self: Lens<A, ER, EW, RR, RW>, self: Lens<A, ER, EW, RR, RW>,
tag: Context.Tag<I, S>, tag: Context.Tag<I, S>,
@@ -286,12 +698,16 @@ export const provide: {
self: Lens<A, ER, EW, RR, RW>, self: Lens<A, ER, EW, RR, RW>,
tag: Context.Tag<I, S>, tag: Context.Tag<I, S>,
service: NoInfer<S>, service: NoInfer<S>,
): Lens<A, ER, EW, Exclude<RR, I>, Exclude<RW, I>> => make({ ): Lens<A, ER, EW, Exclude<RR, I>, Exclude<RW, I>> => derive(self, {
get get() { return Effect.provideService(self.get, tag, service) }, resolve: parent => Effect.map(
get changes() { return Stream.provideService(self.changes, tag, service) }, Effect.provideService(parent, tag, service),
modify: <B, E1 = never, R1 = never>( resolved => ({
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1> value: resolved.value,
) => Effect.provideService(self.modify(f), tag, service), commit: next => Effect.provideService(resolved.commit(next), tag, service),
}),
),
mapStream: Stream.provideService(tag, service),
mapLock: Effect.provideService(tag, service),
})) }))
@@ -478,7 +894,7 @@ export const set: {
<A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW, RR | RW> <A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<void, ER | EW, RR | RW> <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<void, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) => } = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) =>
self.modify<void, never, never>(() => Effect.succeed([void 0, value] as const)), self.modifyEffect<void, never, never>(() => Effect.succeed([void 0, value] as const)),
) )
/** /**
@@ -488,7 +904,7 @@ export const getAndSet: {
<A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) => } = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) =>
self.modify<A, never, never>(a => Effect.succeed([a, value] as const)), self.modifyEffect<A, never, never>(a => Effect.succeed([a, value] as const)),
) )
/** /**
@@ -498,7 +914,7 @@ export const update: {
<A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW, RR | RW> <A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<void, ER | EW, RR | RW> <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<void, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) => } = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) =>
self.modify<void, never, never>(a => Effect.succeed([void 0, f(a)] as const)), self.modifyEffect<void, never, never>(a => Effect.succeed([void 0, f(a)] as const)),
) )
/** /**
@@ -508,7 +924,7 @@ export const updateEffect: {
<A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW | E, RR | RW | R> <A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW | E, RR | RW | R>
<A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<void, ER | EW | E, RR | RW | R> <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<void, ER | EW | E, RR | RW | R>
} = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) => } = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) =>
self.modify<void, E, R>(a => Effect.flatMap( self.modifyEffect<void, E, R>(a => Effect.flatMap(
f(a), f(a),
next => Effect.succeed([void 0, next] as const), next => Effect.succeed([void 0, next] as const),
)), )),
@@ -521,7 +937,7 @@ export const getAndUpdate: {
<A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) => } = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) =>
self.modify<A, never, never>(a => Effect.succeed([a, f(a)] as const)), self.modifyEffect<A, never, never>(a => Effect.succeed([a, f(a)] as const)),
) )
/** /**
@@ -531,7 +947,7 @@ export const getAndUpdateEffect: {
<A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW | E, RR | RW | R> <A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW | E, RR | RW | R>
<A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, ER | EW | E, RR | RW | R> <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, ER | EW | E, RR | RW | R>
} = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) => } = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) =>
self.modify<A, E, R>(a => Effect.flatMap( self.modifyEffect<A, E, R>(a => Effect.flatMap(
f(a), f(a),
next => Effect.succeed([a, next] as const) next => Effect.succeed([a, next] as const)
)), )),
@@ -544,7 +960,7 @@ export const setAndGet: {
<A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) => } = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) =>
self.modify<A, never, never>(() => Effect.succeed([value, value] as const)), self.modifyEffect<A, never, never>(() => Effect.succeed([value, value] as const)),
) )
/** /**
@@ -554,7 +970,7 @@ export const updateAndGet: {
<A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<A, ER | EW, RR | RW> <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) => } = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) =>
self.modify<A, never, never>(a => { self.modifyEffect<A, never, never>(a => {
const next = f(a) const next = f(a)
return Effect.succeed([next, next] as const) return Effect.succeed([next, next] as const)
}), }),
@@ -567,7 +983,7 @@ export const updateAndGetEffect: {
<A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW | E, RR | RW | R> <A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW | E, RR | RW | R>
<A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, ER | EW | E, RR | RW | R> <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, ER | EW | E, RR | RW | R>
} = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) => } = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) =>
self.modify<A, E, R>(a => Effect.flatMap( self.modifyEffect<A, E, R>(a => Effect.flatMap(
f(a), f(a),
next => Effect.succeed([next, next] as const), next => Effect.succeed([next, next] as const),
)), )),
@@ -0,0 +1,66 @@
import { describe, expect, test } from "bun:test"
import { Chunk, Effect, SubscriptionRef } from "effect"
import * as Subscribable from "./Subscribable.js"
describe("Subscribable", () => {
test("focusArrayLength reads the current array length and reflects updates", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([1, 2, 3]),
parent => {
const sizeSub = Subscribable.focusArrayLength(parent)
return Effect.flatMap(
sizeSub.get,
initial => Effect.flatMap(
SubscriptionRef.set(parent, [1, 2, 3, 4, 5]),
() => Effect.map(sizeSub.get, next => [initial, next] as const),
),
)
},
),
)
expect(result).toEqual([3, 5])
})
test("focusChunkSize reads the current chunk size and reflects updates", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make(Chunk.make(1, 2) as Chunk.Chunk<number>),
parent => {
const sizeSub = Subscribable.focusChunkSize(parent)
return Effect.flatMap(
sizeSub.get,
initial => Effect.flatMap(
SubscriptionRef.set(parent, Chunk.make(1, 2, 3, 4)),
() => Effect.map(sizeSub.get, next => [initial, next] as const),
),
)
},
),
)
expect(result).toEqual([2, 4])
})
test("focusIterableSize also works for array values", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([1, 2, 3]),
parent => {
const sizeSub = Subscribable.focusIterableSize(parent)
return Effect.flatMap(
sizeSub.get,
initial => Effect.flatMap(
SubscriptionRef.set(parent, [1, 2, 3, 4, 5]),
() => Effect.map(sizeSub.get, next => [initial, next] as const),
),
)
},
),
)
expect(result).toEqual([3, 5])
})
})
+22 -1
View File
@@ -1,4 +1,4 @@
import { Array, Chunk, Effect, Function, Option, Subscribable } from "effect" import { Array, Chunk, Effect, Function, Iterable, Option, Subscribable } from "effect"
import type { NoSuchElementException } from "effect/Cause" import type { NoSuchElementException } from "effect/Cause"
@@ -71,6 +71,13 @@ export const focusArrayAt: {
index: number, index: number,
): Subscribable.Subscribable<A[number], E | NoSuchElementException, R> => Subscribable.mapEffect(self, Array.get(index))) ): Subscribable.Subscribable<A[number], E | NoSuchElementException, R> => Subscribable.mapEffect(self, Array.get(index)))
/**
* Narrows the focus to the length of an array.
*/
export const focusArrayLength = <A extends readonly any[], E, R>(
self: Subscribable.Subscribable<A, E, R>,
): Subscribable.Subscribable<number, E, R> => Subscribable.map(self, Array.length)
/** /**
* Narrows the focus to an indexed element of a readonly tuple. * Narrows the focus to an indexed element of a readonly tuple.
*/ */
@@ -102,3 +109,17 @@ export const focusChunkAt: {
self: Subscribable.Subscribable<Chunk.Chunk<A>, E, R>, self: Subscribable.Subscribable<Chunk.Chunk<A>, E, R>,
index: number, index: number,
): Subscribable.Subscribable<A, E | NoSuchElementException, R> => Subscribable.mapEffect(self, Chunk.get(index))) ): Subscribable.Subscribable<A, E | NoSuchElementException, R> => Subscribable.mapEffect(self, Chunk.get(index)))
/**
* Narrows the focus to the size of a `Chunk`.
*/
export const focusChunkSize = <A, E, R>(
self: Subscribable.Subscribable<Chunk.Chunk<A>, E, R>,
): Subscribable.Subscribable<number, E, R> => Subscribable.map(self, Chunk.size)
/**
* Narrows the focus to the size of a `Iterable`.
*/
export const focusIterableSize = <A, E, R>(
self: Subscribable.Subscribable<Iterable<A>, E, R>,
): Subscribable.Subscribable<number, E, R> => Subscribable.map(self, Iterable.size)
+1 -1
View File
@@ -6,7 +6,6 @@
"module": "NodeNext", "module": "NodeNext",
"moduleDetection": "force", "moduleDetection": "force",
"jsx": "react-jsx", "jsx": "react-jsx",
// "allowJs": true,
// Bundler mode // Bundler mode
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
@@ -25,6 +24,7 @@
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false,
// Build // Build
"rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
+4 -3
View File
@@ -10,12 +10,13 @@
"clean:modules": "rm -rf node_modules" "clean:modules": "rm -rf node_modules"
}, },
"dependencies": { "dependencies": {
"@effect/platform": "^0.96.0", "@effect/platform": "^0.96.1",
"@effect/platform-browser": "^0.76.0", "@effect/platform-browser": "^0.76.0",
"effect": "^3.21.0", "@effect/platform-bun": "^0.89.0",
"effect": "^3.21.2",
"effect-lens": "workspace:*" "effect-lens": "workspace:*"
}, },
"overrides": { "overrides": {
"effect": "^3.21.0" "effect": "^3.21.2"
} }
} }
+36 -31
View File
@@ -1,36 +1,41 @@
import { KeyValueStore } from "@effect/platform" // import { KeyValueStore } from "@effect/platform"
import { BrowserKeyValueStore, BrowserStream } from "@effect/platform-browser" // import { BrowserKeyValueStore, BrowserStream } from "@effect/platform-browser"
import { Effect, Option, Stream } from "effect" // import { Effect, Option, Stream } from "effect"
import { Lens } from "effect-lens" // import { Lens } from "effect-lens"
Effect.gen(function*() { // Effect.gen(function*() {
// \/ Lens<Option.Option<string>, PlatformError, PlatformError, never, never> // // \/ Lens<Option.Option<string>, PlatformError, PlatformError, never, never>
Effect.all([ // const lens = Effect.all([
KeyValueStore.KeyValueStore, // KeyValueStore.KeyValueStore,
Effect.succeed("someKey"), // Effect.succeed("someKey"),
]).pipe( // Effect.makeSemaphore(1),
Effect.map(([kv, key]) => Lens.make({ // ]).pipe(
get: kv.get(key), // Effect.map(([kv, key, semaphore]) => Lens.make({
// get: kv.get(key),
changes: kv.get(key).pipe( // changes: kv.get(key).pipe(
Effect.map(Stream.make), // Effect.map(Stream.make),
Effect.map(a => Stream.concat( // Effect.map(a => Stream.concat(
a, // a,
BrowserStream.fromEventListenerWindow("storage").pipe( // BrowserStream.fromEventListenerWindow("storage").pipe(
Stream.filter(event => event.key === key), // Stream.filter(event => event.key === key),
Stream.map(event => Option.fromNullable(event.newValue)), // Stream.map(event => Option.fromNullable(event.newValue)),
), // ),
)), // )),
Stream.unwrap, // Stream.unwrap,
), // ),
set: a => Option.isSome(a) // commit: a => Option.isSome(a)
? kv.set(key, a.value) // ? kv.set(key, a.value)
: kv.remove(key), // : kv.remove(key),
})),
Effect.provide(BrowserKeyValueStore.layerLocalStorage), // lock: Effect.succeed(semaphore.withPermits(1)),
Lens.unwrap, // })),
)
}) // Effect.provide(BrowserKeyValueStore.layerLocalStorage),
// Lens.unwrap,
// )
// console.log(yield* Lens.get(lens))
// })
+1 -1
View File
@@ -6,7 +6,6 @@
"module": "NodeNext", "module": "NodeNext",
"moduleDetection": "force", "moduleDetection": "force",
"jsx": "react-jsx", "jsx": "react-jsx",
// "allowJs": true,
// Bundler mode // Bundler mode
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
@@ -25,6 +24,7 @@
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false,
// Build // Build
"rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,