diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e8f20bb..d5945799 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,6 +193,46 @@ importers: version: 3.5.22(typescript@5.9.3) publishDirectory: dist + sample/react: + dependencies: + react: + specifier: ^19.1.1 + version: 19.1.1 + react-dom: + specifier: ^19.1.1 + version: 19.1.1(react@19.1.1) + devDependencies: + '@effect-atom/atom': + specifier: workspace:* + version: link:../../packages/atom/dist + '@effect-atom/atom-react': + specifier: workspace:* + version: link:../../packages/atom-react/dist + '@effect/rpc': + specifier: ^0.71.0 + version: 0.71.0(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4) + '@types/node': + specifier: ^24.6.0 + version: 24.6.0 + '@types/react': + specifier: ^19.1.16 + version: 19.2.2 + '@types/react-dom': + specifier: ^19.1.9 + version: 19.1.9(@types/react@19.2.2) + '@vitejs/plugin-react': + specifier: ^5.1.0 + version: 5.1.0(vite@7.1.9(@types/node@24.6.0)(tsx@4.20.6)(yaml@2.8.1)) + effect: + specifier: ^3.18.4 + version: 3.18.4 + typescript: + specifier: ~5.8.3 + version: 5.8.3 + vite: + specifier: ^7.1.9 + version: 7.1.9(@types/node@24.6.0)(tsx@4.20.6)(yaml@2.8.1) + sample/vue: devDependencies: '@effect-atom/atom': @@ -322,6 +362,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} @@ -1029,36 +1081,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -1093,6 +1151,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.29': resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + '@rolldown/pluginutils@1.0.0-beta.43': + resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==} + '@rollup/rollup-android-arm-eabi@4.52.4': resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} cpu: [arm] @@ -1127,56 +1188,67 @@ packages: resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.4': resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.4': resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.4': resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.4': resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.4': resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.4': resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.4': resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.4': resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.4': resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.4': resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.4': resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} @@ -1335,6 +1407,9 @@ packages: '@types/react@19.1.15': resolution: {integrity: sha512-+kLxJpaJzXybyDyFXYADyP1cznTO8HSuBpenGlnKOAkH4hyNINiywvXS/tGJhsrGGP/gM185RA3xpjY0Yg4erA==} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/scheduler@0.26.0': resolution: {integrity: sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==} @@ -1475,41 +1550,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -1531,6 +1614,12 @@ packages: cpu: [x64] os: [win32] + '@vitejs/plugin-react@5.1.0': + resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitejs/plugin-vue@6.0.1': resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3522,6 +3611,10 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + react@19.1.1: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} @@ -4389,6 +4482,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': @@ -5220,6 +5323,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.29': {} + '@rolldown/pluginutils@1.0.0-beta.43': {} + '@rollup/rollup-android-arm-eabi@4.52.4': optional: true @@ -5427,10 +5532,18 @@ snapshots: dependencies: '@types/react': 19.1.15 + '@types/react-dom@19.1.9(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + '@types/react@19.1.15': dependencies: csstype: 3.1.3 + '@types/react@19.2.2': + dependencies: + csstype: 3.1.3 + '@types/scheduler@0.26.0': {} '@types/stack-utils@2.0.3': {} @@ -5633,6 +5746,18 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vitejs/plugin-react@5.1.0(vite@7.1.9(@types/node@24.6.0)(tsx@4.20.6)(yaml@2.8.1))': + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@rolldown/pluginutils': 1.0.0-beta.43 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.1.9(@types/node@24.6.0)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-vue@6.0.1(vite@7.1.9(@types/node@24.6.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 @@ -7910,6 +8035,8 @@ snapshots: react-is@18.3.1: {} + react-refresh@0.18.0: {} + react@19.1.1: {} read-pkg-up@7.0.1: diff --git a/sample/react/.gitignore b/sample/react/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/sample/react/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/sample/react/README.md b/sample/react/README.md new file mode 100644 index 00000000..3ad26046 --- /dev/null +++ b/sample/react/README.md @@ -0,0 +1,5 @@ +# React + TypeScript + Effect Atom + +This is a sample for using Effect Atom with React and Typescript. + +See HelloWorld.tsx for example usage. diff --git a/sample/react/index.html b/sample/react/index.html new file mode 100644 index 00000000..52b81c6d --- /dev/null +++ b/sample/react/index.html @@ -0,0 +1,13 @@ + + + + + + + react + + +
+ + + diff --git a/sample/react/package.json b/sample/react/package.json new file mode 100644 index 00000000..dbc10e2e --- /dev/null +++ b/sample/react/package.json @@ -0,0 +1,32 @@ +{ + "name": "@effect-atom/sample-react", + "private": true, + "version": "0.0.0", + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/tim-smart/effect-atom.git" + }, + "homepage": "https://github.com/tim-smart/effect-atom", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@effect-atom/atom": "workspace:*", + "@effect-atom/atom-react": "workspace:*", + "@effect/rpc": "^0.71.0", + "@vitejs/plugin-react": "^5.1.0", + "effect": "^3.18.4", + "typescript": "~5.8.3", + "vite": "^7.1.9", + "@types/node": "^24.6.0", + "@types/react": "^19.1.16", + "@types/react-dom": "^19.1.9" + } +} diff --git a/sample/react/public/vite.svg b/sample/react/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/sample/react/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sample/react/src/App.css b/sample/react/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/sample/react/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/sample/react/src/App.tsx b/sample/react/src/App.tsx new file mode 100644 index 00000000..832c842e --- /dev/null +++ b/sample/react/src/App.tsx @@ -0,0 +1,32 @@ +import { Atom } from "@effect-atom/atom-react" +import "./App.css" +import reactLogo from "./assets/react.svg" +import { HelloWorld } from "./components/HelloWorld" +import viteLogo from "/vite.svg" + + +export default function App() { + return ( + <> +
+ + Vite logo + + + React logo + +
+

Vite + React

+ +
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} diff --git a/sample/react/src/assets/react.svg b/sample/react/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/sample/react/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sample/react/src/components/HelloWorld.tsx b/sample/react/src/components/HelloWorld.tsx new file mode 100644 index 00000000..66d3d0d3 --- /dev/null +++ b/sample/react/src/components/HelloWorld.tsx @@ -0,0 +1,104 @@ +import { Atom, useAtomSet, useAtomValue } from "@effect-atom/atom-react" +import { memo, useEffect, useMemo, useState } from "react" +import { TestClient } from "../fixtures/TestClient" +import { Exit } from "effect" + +const INTERVAL_DURATION = 1_000 +const countAtom = Atom.make(0) + +// separate component to visualize that only this part re-renders on count change +// memo to prevent re-renders when the parent re-renders +// use react-scan to see the visualization +const Counter = memo(function Counter() { + const count = useAtomValue(countAtom) + const setCount = useAtomSet(countAtom) + return ( + + ) +}) + +export function HelloWorld() { + const [intervalEnabled, setIntervalEnabled] = useState(false) + const [value, setValue] = useState({ + echo: "initial", + state: "", + at: new Date(), + }) + + const queryAtom = useMemo(() => { + return TestClient.query("Get", value, { + reactivityKeys: ["Get"], + }) + .pipe(Atom.refreshOnWindowFocus) + .pipe( + Atom.map((res) => { + console.log("Got query result", res) + return res + }), + ) + }, [value]) + + const result = useAtomValue(queryAtom) + + const set = useAtomSet(TestClient.mutation("Set"), { + mode: "promiseExit", + }) + + const onSet = () => { + set({ + payload: { state: "state " + new Date().toISOString() }, + reactivityKeys: ["Get"], + }).then((_) => + console.log( + "finished", + Exit.match(_, { + onSuccess: (_) => ({ success: _ }), + onFailure: (_) => ({ failure: _ }), + }), + ), + ) + } + + useEffect(() => { + if (!intervalEnabled) return + + const interval = setInterval(() => { + setValue({ + echo: `Hello World ${new Date().toLocaleTimeString()}`, + state: "", + at: new Date(), + }) + }, INTERVAL_DURATION) + + return () => clearInterval(interval) + }, [intervalEnabled]) + + return ( + <> + {result._tag === "Initial" &&
Initial
} + {result._tag === "Failure" && ( +
+ {result.waiting &&
Waiting...
} + Failure.. {String(result.cause)} +
+ )} + {result._tag === "Success" && ( +
+ {result.waiting &&
Waiting...
} + Success: {JSON.stringify(result.value)} +
+ )} + + + + +
+ +
+ + ) +} diff --git a/sample/react/src/fixtures/TestClient.ts b/sample/react/src/fixtures/TestClient.ts new file mode 100644 index 00000000..73b4c44c --- /dev/null +++ b/sample/react/src/fixtures/TestClient.ts @@ -0,0 +1,24 @@ +import { AtomRpc } from "@effect-atom/atom-react" +import { Rpc, RpcGroup, RpcTest } from "@effect/rpc" +import { Effect, Schema } from "effect" + +class Rpcs extends RpcGroup.make( + Rpc.make("Get", { + payload: { echo: Schema.String }, + success: Schema.Struct({ echo: Schema.String, at: Schema.Date }), + }), + Rpc.make("Set", { payload: { state: Schema.String } }), +) {} + +let state = "initial" +export class TestClient extends AtomRpc.Tag()("TestClient", { + group: Rpcs, + makeEffect: RpcTest.makeClient(Rpcs, { flatten: true }), + protocol: Rpcs.toLayer({ + Get: (req) => Effect.succeed({ echo: req.echo, state, at: new Date() }), + Set: (req) => + Effect.sync(() => { + state = req.state + }), + }), +}) {} diff --git a/sample/react/src/index.css b/sample/react/src/index.css new file mode 100644 index 00000000..08a3ac9e --- /dev/null +++ b/sample/react/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/sample/react/src/main.tsx b/sample/react/src/main.tsx new file mode 100644 index 00000000..86e0ef88 --- /dev/null +++ b/sample/react/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import "./index.css" +import App from "./App.tsx" + +createRoot(document.getElementById("root")!).render( + + + , +) diff --git a/sample/react/tsconfig.app.json b/sample/react/tsconfig.app.json new file mode 100644 index 00000000..a9639d27 --- /dev/null +++ b/sample/react/tsconfig.app.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + + // Path mapping to local packages + "paths": { + "@effect-atom/atom": ["../../packages/atom/src"], + "@effect-atom/atom-react": ["../../packages/atom-react/src"] + } + }, + "include": ["src"] +} diff --git a/sample/react/tsconfig.json b/sample/react/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/sample/react/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/sample/react/tsconfig.node.json b/sample/react/tsconfig.node.json new file mode 100644 index 00000000..9ca1f5a0 --- /dev/null +++ b/sample/react/tsconfig.node.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + + // Path mapping to local packages + "paths": { + "@effect-atom/atom": ["../../packages/atom/src"], + "@effect-atom/atom-react": ["../../packages/atom-react/src"] + } + }, + "include": ["vite.config.ts"] +} diff --git a/sample/react/vite.config.ts b/sample/react/vite.config.ts new file mode 100644 index 00000000..c5ef152d --- /dev/null +++ b/sample/react/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import { fileURLToPath } from "node:url" +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@effect-atom/atom": fileURLToPath(new URL("../../packages/atom/src", import.meta.url)), + "@effect-atom/atom-react": fileURLToPath(new URL("../../packages/atom-react/src", import.meta.url)) + } + } +})