1 Commits

Author SHA1 Message Date
092737076f 0.2.3 (#33)
All checks were successful
Publish / publish (push) Successful in 19s
Lint / lint (push) Successful in 12s
Co-authored-by: Julien Valverdé <julien.valverde@mailo.com>
Co-authored-by: Renovate Bot <renovate-bot@valverde.cloud>
Reviewed-on: #33
2026-01-23 01:50:12 +01:00
6 changed files with 163 additions and 108 deletions

View File

@@ -5,12 +5,12 @@
"": {
"name": "@effect-fc/monorepo",
"devDependencies": {
"@biomejs/biome": "^2.3.8",
"@biomejs/biome": "^2.3.11",
"@effect/language-service": "^0.72.0",
"@types/bun": "^1.3.3",
"npm-check-updates": "^19.1.2",
"@types/bun": "^1.3.6",
"npm-check-updates": "^19.3.1",
"npm-sort": "^0.0.4",
"turbo": "^2.6.1",
"turbo": "^2.7.5",
"typescript": "^5.9.3",
},
},
@@ -30,26 +30,26 @@
"name": "@effect-fc/example",
"version": "0.0.0",
"dependencies": {
"@effect/platform": "^0.94.0",
"@effect/platform": "^0.94.2",
"@effect/platform-browser": "^0.74.0",
"@radix-ui/themes": "^3.2.1",
"@typed/id": "^0.17.2",
"effect": "^3.19.8",
"effect": "^3.19.15",
"effect-fc": "workspace:*",
"react-icons": "^5.5.0",
},
"devDependencies": {
"@tanstack/react-router": "^1.139.12",
"@tanstack/react-router-devtools": "^1.139.12",
"@tanstack/router-plugin": "^1.139.12",
"@types/react": "^19.2.7",
"@tanstack/react-router": "^1.154.12",
"@tanstack/react-router-devtools": "^1.154.12",
"@tanstack/router-plugin": "^1.154.12",
"@types/react": "^19.2.9",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"@vitejs/plugin-react": "^5.1.2",
"globals": "^17.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"type-fest": "^5.2.0",
"vite": "^7.2.6",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"type-fest": "^5.4.1",
"vite": "^7.3.1",
},
},
},
@@ -118,7 +118,7 @@
"@effect/language-service": ["@effect/language-service@0.72.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-MWkyTPCXSs5Q3OIBWR3q24SA+ipkdWW7EBJBt6EPUzlzZxjJLXtLBhXpMoCFheSEM0FTWOHT4BRLh5lufsmjVw=="],
"@effect/platform": ["@effect/platform@0.94.1", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.14" } }, "sha512-SlL8OMTogHmMNnFLnPAHHo3ua1yrB1LNQOVQMiZsqYu9g3216xjr0gn5WoDgCxUyOdZcseegMjWJ7dhm/2vnfg=="],
"@effect/platform": ["@effect/platform@0.94.2", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.15" } }, "sha512-85vdwpnK4oH/rJ3EuX/Gi2Hkt+K4HvXWr9bxCuqvty9hxyEcRxkJcqTesYrcVoQB6aULb1Za2B0MKoTbvffB3Q=="],
"@effect/platform-browser": ["@effect/platform-browser@0.74.0", "", { "dependencies": { "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/platform": "^0.94.0", "effect": "^3.19.13" } }, "sha512-PAgkg5L5cASQpScA0SZTSy543MVA4A9kmpVCjo2fCINLRpTeuCFAOQHgPmw8dKHnYS0yGs2TYn7AlrhhqQ5o3g=="],
@@ -382,27 +382,27 @@
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@tanstack/history": ["@tanstack/history@1.145.7", "", {}, "sha512-gMo/ReTUp0a3IOcZoI3hH6PLDC2R/5ELQ7P2yu9F6aEkA0wSQh+Q4qzMrtcKvF2ut0oE+16xWCGDo/TdYd6cEQ=="],
"@tanstack/history": ["@tanstack/history@1.154.7", "", {}, "sha512-YBgwS9qG4rs1ZY/ZrhQtjOH8BG9Qa2wf2AsxT/SnZ4HZJ1DcCEqkoiHH0yH6CYvdDit31X5HokOqQrRSsZEwGA=="],
"@tanstack/react-router": ["@tanstack/react-router@1.150.0", "", { "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.150.0", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-k/oycTCpBT2XoEk9dNd/nNYhF0X9fLSB10lT40+NVX1TjOtBq5whksk8MT6oRnSoQ8KWeb7La3G9kFaAeSULkA=="],
"@tanstack/react-router": ["@tanstack/react-router@1.154.12", "", { "dependencies": { "@tanstack/history": "1.154.7", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.154.12", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-WiYfC6IYC2HwjkATouJCQlAM5RJ8MViefslfUcZpsbCb+WGQpdpvUY7GPJLEeessSpqgiC2EabRYC2kYVNyMPg=="],
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.150.0", "", { "dependencies": { "@tanstack/router-devtools-core": "1.150.0" }, "peerDependencies": { "@tanstack/react-router": "^1.150.0", "@tanstack/router-core": "^1.150.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-TlvTE+XK5XVCfYjazoMWkjyyPKe4kMw2nCA7EuWoYUJKOqRW5oKvBY7auViGWxp51FKDEjV3bbok3wPKBYwZww=="],
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.154.12", "", { "dependencies": { "@tanstack/router-devtools-core": "1.154.12" }, "peerDependencies": { "@tanstack/react-router": "^1.154.12", "@tanstack/router-core": "^1.154.12", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-TcGe7pmeVjk1zD58eMR87GG9OXMx6LDGz5QopmJS4LafvK2hvuaht+eKBnZlCvKLPlXu5juwHT4u+2bYdn6sqQ=="],
"@tanstack/react-store": ["@tanstack/react-store@0.8.0", "", { "dependencies": { "@tanstack/store": "0.8.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow=="],
"@tanstack/router-core": ["@tanstack/router-core@1.150.0", "", { "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.4.1", "seroval-plugins": "^1.4.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-cAm44t/tUbfyzaDH+rE/WO4u3AgaZdpJp00xjQ4gNkC2O95ntVHq5fx+4fhtrkKpgdXoKldgk8OK66djiWpuGQ=="],
"@tanstack/router-core": ["@tanstack/router-core@1.154.12", "", { "dependencies": { "@tanstack/history": "1.154.7", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-p+TKxkXcLGtCwwW237D8pV4f6ea2K1pzc/e65ljugoTawsA/YR2/gmTSBDTUsSYy6Tmu4mMJmZ0Q4zNkcfCS3g=="],
"@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.150.0", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.150.0", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-61V+4fq2fOPru/48cuojKvWhQx2h/nuj4nVHwzu9E7O8h391h4Hks6axxRbY98/rIz96mn5TCoc0aYuoga53bg=="],
"@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.154.12", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.154.12", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-lvnP9cqknvSSkUjqQRVn61TcBhq72hCFFOzMwdFdFPTO8nMEXvYE6ZZJiXtivwcvsKmO6XVFLMXuJr/928gNkw=="],
"@tanstack/router-generator": ["@tanstack/router-generator@1.150.0", "", { "dependencies": { "@tanstack/router-core": "1.150.0", "@tanstack/router-utils": "1.143.11", "@tanstack/virtual-file-routes": "1.145.4", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-WsA1bN5/I+cxE6V1DkU5ABIPBQxZLlxszElYgnIhs884tzukv76rYMFOy6Xqd51YIFdYtjDrxZbp4/vfkrVCug=="],
"@tanstack/router-generator": ["@tanstack/router-generator@1.154.12", "", { "dependencies": { "@tanstack/router-core": "1.154.12", "@tanstack/router-utils": "1.154.7", "@tanstack/virtual-file-routes": "1.154.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-cjr3KS3Esnyh05CWl78KgK2Z9kTjeFasZXcSUrh//TzzU72eXQ+dzKppD3kMsjuyRfUxAfdufsR9GDNMMuLk9w=="],
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.150.0", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.150.0", "@tanstack/router-generator": "1.150.0", "@tanstack/router-utils": "1.143.11", "@tanstack/virtual-file-routes": "1.145.4", "babel-dead-code-elimination": "^1.0.11", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.150.0", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-k2NLysBXO4Wpt4Oo0xeBhNtFsMwHOU8ud48/cWNWbV89QAjlk0XU5CGNj2JEaFMT0zlF3H/aM5/h0+vYnDjFFA=="],
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.154.12", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.154.12", "@tanstack/router-generator": "1.154.12", "@tanstack/router-utils": "1.154.7", "@tanstack/virtual-file-routes": "1.154.7", "babel-dead-code-elimination": "^1.0.11", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.154.12", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-YlFjrL5j7RbYT/B3RZZedbXOHXfqRV7b/qIGyojBaHsrIgKFGo4AHg/FyS50HJaHGQ27vvgWNSy/4Orrozbm0Q=="],
"@tanstack/router-utils": ["@tanstack/router-utils@1.143.11", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-N24G4LpfyK8dOlnP8BvNdkuxg1xQljkyl6PcrdiPSA301pOjatRT1y8wuCCJZKVVD8gkd0MpCZ0VEjRMGILOtA=="],
"@tanstack/router-utils": ["@tanstack/router-utils@1.154.7", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-61bGx32tMKuEpVRseu2sh1KQe8CfB7793Mch/kyQt0EP3tD7X0sXmimCl3truRiDGUtI0CaSoQV1NPjAII1RBA=="],
"@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="],
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.145.4", "", {}, "sha512-CI75JrfqSluhdGwLssgVeQBaCphgfkMQpi8MCY3UJX1hoGzXa8kHYJcUuIFMOLs1q7zqHy++EVVtMK03osR5wQ=="],
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.154.7", "", {}, "sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg=="],
"@typed/id": ["@typed/id@0.17.2", "", { "peerDependencies": { "effect": "^3.14.7" } }, "sha512-z/Z14/moeu9x45IpkGaRwuvb+CQ3s3UCc/agcpZibTz1yPb3RgSDXx4rOHIuyb6hG6oNzqe9yY4GbbMq3Hb5Ug=="],
@@ -420,7 +420,7 @@
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
"@types/react": ["@types/react@19.2.8", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg=="],
"@types/react": ["@types/react@19.2.9", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
@@ -470,7 +470,7 @@
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
"effect": ["effect@3.19.14", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-3vwdq0zlvQOxXzXNKRIPKTqZNMyGCdaFUBfMPqpsyzZDre67kgC1EEHDV4EoQTovJ4w5fmJW756f86kkuz7WFA=="],
"effect": ["effect@3.19.15", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-vzMmgfZKLcojmUjBdlQx+uaKryO7yULlRxjpDnHdnvcp1NPHxJyoM6IOXBLlzz2I/uPtZpGKavt5hBv7IvGZkA=="],
"effect-fc": ["effect-fc@workspace:packages/effect-fc"],
@@ -604,19 +604,19 @@
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
"turbo": ["turbo@2.7.4", "", { "optionalDependencies": { "turbo-darwin-64": "2.7.4", "turbo-darwin-arm64": "2.7.4", "turbo-linux-64": "2.7.4", "turbo-linux-arm64": "2.7.4", "turbo-windows-64": "2.7.4", "turbo-windows-arm64": "2.7.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-bkO4AddmDishzJB2ze7aYYPaejMoJVfS0XnaR6RCdXFOY8JGJfQE+l9fKiV7uDPa5Ut44gmOWJL3894CIMeH9g=="],
"turbo": ["turbo@2.7.5", "", { "optionalDependencies": { "turbo-darwin-64": "2.7.5", "turbo-darwin-arm64": "2.7.5", "turbo-linux-64": "2.7.5", "turbo-linux-arm64": "2.7.5", "turbo-windows-64": "2.7.5", "turbo-windows-arm64": "2.7.5" }, "bin": { "turbo": "bin/turbo" } }, "sha512-7Imdmg37joOloTnj+DPrab9hIaQcDdJ5RwSzcauo/wMOSAgO+A/I/8b3hsGGs6PWQz70m/jkPgdqWsfNKtwwDQ=="],
"turbo-darwin-64": ["turbo-darwin-64@2.7.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xDR30ltfkSsRfGzABBckvl1nz1cZ3ssTujvdj+TPwOweeDRvZ0e06t5DS0rmRBvyKpgGs42K/EK6Mn2qLlFY9A=="],
"turbo-darwin-64": ["turbo-darwin-64@2.7.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-nN3wfLLj4OES/7awYyyM7fkU8U8sAFxsXau2bYJwAWi6T09jd87DgHD8N31zXaJ7LcpyppHWPRI2Ov9MuZEwnQ=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.7.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P7sjqXtOL/+nYWPvcDGWhi8wf8M8mZHHB8XEzw2VX7VJrS8IGHyJHGD1AYfDvhAEcr7pnk3gGifz3/xyhI655w=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.7.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wCoDHMiTf3FgLAbZHDDx/unNNonSGhsF5AbbYODbxnpYyoKDpEYacUEPjZD895vDhNvYCH0Nnk24YsP4n/cD6g=="],
"turbo-linux-64": ["turbo-linux-64@2.7.4", "", { "os": "linux", "cpu": "x64" }, "sha512-GofFOxRO/IhG8BcPyMSSB3Y2+oKQotsaYbHxL9yD6JPb20/o35eo+zUSyazOtilAwDHnak5dorAJFoFU8MIg2A=="],
"turbo-linux-64": ["turbo-linux-64@2.7.5", "", { "os": "linux", "cpu": "x64" }, "sha512-KKPvhOmJMmzWj/yjeO4LywkQ85vOJyhru7AZk/+c4B6OUh/odQ++SiIJBSbTG2lm1CuV5gV5vXZnf/2AMlu3Zg=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.7.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+RQKgNjksVPxYAyAgmDV7w/1qj++qca+nSNTAOKGOfJiDtSvRKoci89oftJ6anGs00uamLKVEQ712TI/tfNAIw=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.7.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-8PIva4L6BQhiPikUTds9lSFSHXVDAsEvV6QUlgwPsXrtXVQMVi6Sv9p+IxtlWQFvGkdYJUgX9GnK2rC030Xcmw=="],
"turbo-windows-64": ["turbo-windows-64@2.7.4", "", { "os": "win32", "cpu": "x64" }, "sha512-rfak1+g+ON3czs1mDYsCS4X74ZmK6gOgRQTXjDICtzvR4o61paqtgAYtNPofcVsMWeF4wvCajSeoAkkeAnQ1kg=="],
"turbo-windows-64": ["turbo-windows-64@2.7.5", "", { "os": "win32", "cpu": "x64" }, "sha512-rupskv/mkIUgQXzX/wUiK00mKMorQcK8yzhGFha/D5lm05FEnLx8dsip6rWzMcVpvh+4GUMA56PgtnOgpel2AA=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.7.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-1ZgBNjNRbDu/fPeqXuX9i26x3CJ/Y1gcwUpQ+Vp7kN9Un6RZ9kzs164f/knrjcu5E+szCRexVjRSJay1k5jApA=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.7.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-G377Gxn6P42RnCzfMyDvsqQV7j69kVHKlhz9J4RhtJOB5+DyY4yYh/w0oTIxZQ4JRMmhjwLu3w9zncMoQ6nNDw=="],
"type-fest": ["type-fest@5.4.1", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ=="],

View File

@@ -1,6 +1,6 @@
{
"name": "@effect-fc/monorepo",
"packageManager": "bun@1.3.3",
"packageManager": "bun@1.3.6",
"private": true,
"workspaces": [
"./packages/*"
@@ -15,12 +15,12 @@
"clean:modules": "turbo clean:modules && rm -rf node_modules"
},
"devDependencies": {
"@biomejs/biome": "^2.3.8",
"@biomejs/biome": "^2.3.11",
"@effect/language-service": "^0.72.0",
"@types/bun": "^1.3.3",
"npm-check-updates": "^19.1.2",
"@types/bun": "^1.3.6",
"npm-check-updates": "^19.3.1",
"npm-sort": "^0.0.4",
"turbo": "^2.6.1",
"turbo": "^2.7.5",
"typescript": "^5.9.3"
}
}

View File

@@ -1,31 +1,25 @@
# Effect FC
[Effect-TS](https://effect.website/) integration for React 19+ that allows you to write function components using Effect generators.
[Effect-TS](https://effect.website/) integration for React 19.2+ that allows you to write function components using Effect generators.
This library is in early development. While it is (almost) feature complete and mostly usable, expect bugs and quirks. Things are still being ironed out, so ideas and criticisms are more than welcome.
Documentation is currently being written. In the meantime, you can take a look at the `packages/example` directory.
## Peer dependencies
- `effect` 3.15+
- `react` & `@types/react` 19+
- `effect` 3.19+
- `react` & `@types/react` 19.2+
## Known issues
- React Refresh doesn't work for Effect FC's yet. Page reload is required to view changes. Regular React components are unaffected.
## What writing components looks like
```typescript
import { Component } from "effect-fc"
import { useOnce, useSubscribables } from "effect-fc/Hooks"
import { Todo } from "./Todo"
import { TodosState } from "./TodosState.service"
export class Todos extends Component.makeUntraced("Todos")(function*() {
export class Todos extends Component.make("Todos")(function*() {
const state = yield* TodosState
const [todos] = yield* useSubscribables(state.ref)
yield* useOnce(() => Effect.andThen(
yield* useOnMount(() => Effect.andThen(
Console.log("Todos mounted"),
Effect.addFinalizer(() => Console.log("Todos unmounted")),
))
@@ -49,8 +43,8 @@ export class Todos extends Component.makeUntraced("Todos")(function*() {
const TodosStateLive = TodosState.Default("todos")
const Index = Component.makeUntraced("Index")(function*() {
const context = yield* useContext(TodosStateLive, { finalizerExecutionMode: "fork" })
const Index = Component.make("Index")(function*() {
const context = yield* useContext(TodosStateLive)
const TodosFC = yield* Effect.provide(Todos, context)
return <TodosFC />

View File

@@ -1,7 +1,7 @@
{
"name": "effect-fc",
"description": "Write React function components with Effect",
"version": "0.2.2",
"version": "0.2.3",
"type": "module",
"files": [
"./README.md",

View File

@@ -47,16 +47,18 @@ export declare namespace Component {
* Options that can be set on the component
*/
export interface Options {
/** Custom displayName for React DevTools and debugging */
/** Custom displayName for React DevTools and debugging. */
readonly displayName?: string
/**
* Strategy used when executing finalizers on unmount/scope close
* Strategy used when executing finalizers on unmount/scope close.
* @default ExecutionStrategy.sequential
*/
readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy
/**
* Debounce time before executing finalizers after component unmount
* Helps avoid unnecessary work during fast remount/remount cycles
* Debounce time before executing finalizers after component unmount.
* Helps avoid unnecessary work during fast remount/remount cycles.
* @default "100 millis"
*/
readonly finalizerExecutionDebounce: Duration.DurationInput
@@ -338,9 +340,17 @@ export declare namespace make {
}
/**
* Creates an Effect-based React component.
* Creates an Effect-FC Component following the same overloads and pipeline style as `Effect.fn`.
*
* Follows the `Effect.fn` API. Supports both generator syntax (recommended) and direct Effect composition.
* This is the **recommended** way to define components. It supports:
* - Generator syntax (yield* style) — most ergonomic and readable
* - Direct Effect return (non-generator)
* - Chained transformation functions (like Effect.fn pipelines)
* - Optional tracing span with automatic `displayName`
*
* When you provide a `spanName` as the first argument, two things happen automatically:
* 1. A tracing span is created with that name (unless using `makeUntraced`)
* 2. The resulting React component gets `displayName = spanName`
*/
export const make: (
& make.Gen
@@ -371,10 +381,15 @@ export const make: (
}
/**
* Same as `make` but creates an untraced version (no automatic span created).
* Useful for very low-level utilities or when you want full control over tracing.
* Same as `make`, but creates an **untraced** version no automatic tracing span is created.
*
* Follows the `Effect.fnUntraced` API.
* Follows the exact same API shape as `Effect.fnUntraced`.
* Useful for:
* - Components where you want full manual control over tracing
* - Avoiding span noise in deeply nested UI
*
* When a string is provided as first argument, it is **only** used as the React component's `displayName`
* (no tracing span is created).
*/
export const makeUntraced: (
& make.Gen
@@ -442,7 +457,7 @@ export const withOptions: {
*
* // Route
* export const Route = createFileRoute("/")({
* component: withRuntime(HomePage, runtime.context),
* component: Component.withRuntime(HomePage, runtime.context)
* })
* ```
*
@@ -468,6 +483,10 @@ export const withRuntime: {
})
/**
* Service that keeps track of scopes associated with React components
* (used internally by the `useScope` hook).
*/
export class ScopeMap extends Effect.Service<ScopeMap>()("@effect-fc/Component/ScopeMap", {
effect: Effect.bind(Effect.Do, "ref", () => Ref.make(HashMap.empty<object, ScopeMap.Entry>()))
}) {}
@@ -487,6 +506,14 @@ export declare namespace useScope {
}
}
/**
* Hook that creates and manages a `Scope` for the current component instance.
*
* Automatically closes the scope whenever `deps` changes or the component unmounts.
*
* @param deps - dependency array like in `React.useEffect`
* @param options - finalizer execution control
*/
export const useScope = Effect.fnUntraced(function*(
deps: React.DependencyList,
options?: useScope.Options,
@@ -495,43 +522,40 @@ export const useScope = Effect.fnUntraced(function*(
const runtimeRef = React.useRef<Runtime.Runtime<never>>(null!)
runtimeRef.current = yield* Effect.runtime()
const scopeMap = yield* ScopeMap as unknown as Effect.Effect<ScopeMap>
const [key, scope] = React.useMemo(() => Runtime.runSync(runtimeRef.current)(Effect.andThen(
Effect.all([Effect.succeed({}), scopeMap.ref]),
([key, map]) => Effect.andThen(
Option.match(HashMap.get(map, key), {
onSome: entry => Effect.succeed(entry.scope),
onNone: () => Effect.tap(
Scope.make(options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy),
scope => Ref.update(scopeMap.ref, HashMap.set(key, {
scope,
closeFiber: Option.none(),
})),
),
}),
scope => [key, scope] as const,
const { key, scope } = React.useMemo(() => Runtime.runSync(runtimeRef.current)(Effect.Do.pipe(
Effect.bind("scopeMapRef", () => Effect.map(
ScopeMap as unknown as Effect.Effect<ScopeMap>,
scopeMap => scopeMap.ref,
)),
Effect.let("key", () => ({})),
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy)),
Effect.tap(({ scopeMapRef, key, scope }) =>
Ref.update(scopeMapRef, HashMap.set(key, {
scope,
closeFiber: Option.none(),
}))
),
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
)), deps)
// biome-ignore lint/correctness/useExhaustiveDependencies: only reactive on "key"
React.useEffect(() => Runtime.runSync(runtimeRef.current)(scopeMap.ref.pipe(
Effect.andThen(HashMap.get(key)),
Effect.tap(entry => Option.match(entry.closeFiber, {
onSome: fiber => Effect.andThen(
Ref.update(scopeMap.ref, HashMap.set(key, { ...entry, closeFiber: Option.none() })),
Fiber.interruptFork(fiber),
),
onNone: () => Effect.void,
})),
Effect.map(({ scope }) =>
React.useEffect(() => Runtime.runSync(runtimeRef.current)((ScopeMap as unknown as Effect.Effect<ScopeMap>).pipe(
Effect.map(scopeMap => scopeMap.ref),
Effect.tap(ref => ref.pipe(
Effect.andThen(HashMap.get(key)),
Effect.andThen(entry => Option.match(entry.closeFiber, {
onSome: Fiber.interruptFork,
onNone: () => Effect.void,
})),
)),
Effect.map(ref =>
() => Runtime.runSync(runtimeRef.current)(Effect.andThen(
Effect.forkDaemon(Effect.sleep(options?.finalizerExecutionDebounce ?? defaultOptions.finalizerExecutionDebounce).pipe(
Effect.sleep(options?.finalizerExecutionDebounce ?? defaultOptions.finalizerExecutionDebounce).pipe(
Effect.andThen(Scope.close(scope, Exit.void)),
Effect.andThen(Ref.update(scopeMap.ref, HashMap.remove(key))),
)),
fiber => Ref.update(scopeMap.ref, HashMap.set(key, {
Effect.onExit(() => Ref.update(ref, HashMap.remove(key))),
Effect.forkDaemon,
),
fiber => Ref.update(ref, HashMap.set(key, {
scope,
closeFiber: Option.some(fiber),
})),
@@ -542,6 +566,9 @@ export const useScope = Effect.fnUntraced(function*(
return scope
})
/**
* Runs an effect and returns its result only once on component mount.
*/
export const useOnMount = Effect.fnUntraced(function* <A, E, R>(
f: () => Effect.Effect<A, E, R>
): Effect.fn.Return<A, E, R> {
@@ -553,6 +580,11 @@ export declare namespace useOnChange {
export interface Options extends useScope.Options {}
}
/**
* Runs an effect and returns its result whenever dependencies change.
*
* Provides its own `Scope` which closes whenever `deps` changes or the component unmounts.
*/
export const useOnChange = Effect.fnUntraced(function* <A, E, R>(
f: () => Effect.Effect<A, E, R>,
deps: React.DependencyList,
@@ -574,6 +606,11 @@ export declare namespace useReactEffect {
}
}
/**
* Like `React.useEffect` but accepts an effect.
*
* Cleanup logic is handled through the `Scope` API rather than using imperative cleanup.
*/
export const useReactEffect = Effect.fnUntraced(function* <E, R>(
f: () => Effect.Effect<void, E, R>,
deps?: React.DependencyList,
@@ -610,6 +647,11 @@ export declare namespace useReactLayoutEffect {
export interface Options extends useReactEffect.Options {}
}
/**
* Like `React.useReactLayoutEffect` but accepts an effect.
*
* Cleanup logic is handled through the `Scope` API rather than using imperative cleanup.
*/
export const useReactLayoutEffect = Effect.fnUntraced(function* <E, R>(
f: () => Effect.Effect<void, E, R>,
deps?: React.DependencyList,
@@ -620,18 +662,27 @@ export const useReactLayoutEffect = Effect.fnUntraced(function* <E, R>(
React.useLayoutEffect(() => runReactEffect(runtime, f, options), deps)
})
/**
* Get a synchronous run function for the current runtime context.
*/
export const useRunSync = <R = never>(): Effect.Effect<
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => A,
never,
Scope.Scope | R
> => Effect.andThen(Effect.runtime(), Runtime.runSync)
/**
* Get a Promise-based run function for the current runtime context.
*/
export const useRunPromise = <R = never>(): Effect.Effect<
<A, E = never>(effect: Effect.Effect<A, E, Scope.Scope | R>) => Promise<A>,
never,
Scope.Scope | R
> => Effect.andThen(Effect.runtime(), context => Runtime.runPromise(context))
/**
* Turns a function returning an effect into a memoized synchronous function.
*/
export const useCallbackSync = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
f: (...args: Args) => Effect.Effect<A, E, R>,
deps: React.DependencyList,
@@ -644,6 +695,9 @@ export const useCallbackSync = Effect.fnUntraced(function* <Args extends unknown
return React.useCallback((...args: Args) => Runtime.runSync(runtimeRef.current)(f(...args)), deps)
})
/**
* Turns a function returning an effect into a memoized Promise-based asynchronous function.
*/
export const useCallbackPromise = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
f: (...args: Args) => Effect.Effect<A, E, R>,
deps: React.DependencyList,
@@ -660,10 +714,17 @@ export declare namespace useContext {
export interface Options extends useOnChange.Options {}
}
/**
* Hook that constructs a layer and returns the created context.
*
* The layer gets reconstructed everytime `layer` changes, so make sure its value is stable.
*
* Building a layer containing asynchronous effects require the component calling this hook to be made async using `Async.async`.
*/
export const useContext = <ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>,
options?: useContext.Options,
): Effect.Effect<Context.Context<ROut>, E, Scope.Scope | RIn> => useOnChange(() => Effect.context<RIn>().pipe(
): Effect.Effect<Context.Context<ROut>, E, Exclude<RIn, Scope.Scope>> => useOnChange(() => Effect.context<RIn>().pipe(
Effect.map(context => ManagedRuntime.make(Layer.provide(layer, Layer.succeedContext(context)))),
Effect.tap(runtime => Effect.addFinalizer(() => runtime.disposeEffect)),
Effect.andThen(runtime => runtime.runtimeEffect),

View File

@@ -13,30 +13,30 @@
"clean:modules": "rm -rf node_modules"
},
"devDependencies": {
"@tanstack/react-router": "^1.139.12",
"@tanstack/react-router-devtools": "^1.139.12",
"@tanstack/router-plugin": "^1.139.12",
"@types/react": "^19.2.7",
"@tanstack/react-router": "^1.154.12",
"@tanstack/react-router-devtools": "^1.154.12",
"@tanstack/router-plugin": "^1.154.12",
"@types/react": "^19.2.9",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"@vitejs/plugin-react": "^5.1.2",
"globals": "^17.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"type-fest": "^5.2.0",
"vite": "^7.2.6"
"react": "^19.2.3",
"react-dom": "^19.2.3",
"type-fest": "^5.4.1",
"vite": "^7.3.1"
},
"dependencies": {
"@effect/platform": "^0.94.0",
"@effect/platform": "^0.94.2",
"@effect/platform-browser": "^0.74.0",
"@radix-ui/themes": "^3.2.1",
"@typed/id": "^0.17.2",
"effect": "^3.19.8",
"effect": "^3.19.15",
"effect-fc": "workspace:*",
"react-icons": "^5.5.0"
},
"overrides": {
"@types/react": "^19.2.7",
"effect": "^3.19.8",
"react": "^19.2.0"
"@types/react": "^19.2.9",
"effect": "^3.19.15",
"react": "^19.2.3"
}
}