A tool for people curious about the React Server Components protocol

port to typescript

+1 -1
embed.html
··· 48 48 </head> 49 49 <body> 50 50 <div id="embed-root"></div> 51 - <script type="module" src="/src/client/embed.jsx"></script> 51 + <script type="module" src="/src/client/embed.tsx"></script> 52 52 </body> 53 53 </html>
+1 -1
index.html
··· 1233 1233 id="app" 1234 1234 style="display: flex; flex-direction: column; height: 100%; overflow: hidden" 1235 1235 ></div> 1236 - <script type="module" src="/src/client/index.jsx"></script> 1236 + <script type="module" src="/src/client/index.tsx"></script> 1237 1237 </body> 1238 1238 </html>
+733 -13
package-lock.json
··· 26 26 "web-streams-polyfill": "^4.2.0" 27 27 }, 28 28 "devDependencies": { 29 + "@babel/types": "^7.28.5", 29 30 "@eslint/js": "^9.39.2", 31 + "@types/babel__core": "^7.20.5", 32 + "@types/babel__standalone": "^7.1.9", 33 + "@types/node": "^25.0.2", 34 + "@types/react": "^19.2.7", 35 + "@types/react-dom": "^19.2.3", 36 + "@types/text-encoding": "^0.0.40", 30 37 "@vitejs/plugin-react": "^5.1.2", 31 38 "@vitest/browser": "^4.0.15", 32 39 "@vitest/browser-playwright": "^4.0.15", ··· 37 44 "playwright": "^1.57.0", 38 45 "prettier": "^3.7.4", 39 46 "rolldown": "^1.0.0-beta.54", 47 + "start-server-and-test": "^2.1.3", 48 + "typescript": "^5.9.3", 40 49 "typescript-eslint": "^8.50.0", 41 50 "vite": "8.0.0-beta.2", 42 51 "vitest": "^4.0.15", ··· 795 804 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 796 805 } 797 806 }, 807 + "node_modules/@hapi/address": { 808 + "version": "5.1.1", 809 + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", 810 + "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", 811 + "dev": true, 812 + "license": "BSD-3-Clause", 813 + "dependencies": { 814 + "@hapi/hoek": "^11.0.2" 815 + }, 816 + "engines": { 817 + "node": ">=14.0.0" 818 + } 819 + }, 820 + "node_modules/@hapi/formula": { 821 + "version": "3.0.2", 822 + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", 823 + "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", 824 + "dev": true, 825 + "license": "BSD-3-Clause" 826 + }, 827 + "node_modules/@hapi/hoek": { 828 + "version": "11.0.7", 829 + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", 830 + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", 831 + "dev": true, 832 + "license": "BSD-3-Clause" 833 + }, 834 + "node_modules/@hapi/pinpoint": { 835 + "version": "2.0.1", 836 + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", 837 + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", 838 + "dev": true, 839 + "license": "BSD-3-Clause" 840 + }, 841 + "node_modules/@hapi/tlds": { 842 + "version": "1.1.4", 843 + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.4.tgz", 844 + "integrity": "sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==", 845 + "dev": true, 846 + "license": "BSD-3-Clause", 847 + "engines": { 848 + "node": ">=14.0.0" 849 + } 850 + }, 851 + "node_modules/@hapi/topo": { 852 + "version": "6.0.2", 853 + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", 854 + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", 855 + "dev": true, 856 + "license": "BSD-3-Clause", 857 + "dependencies": { 858 + "@hapi/hoek": "^11.0.2" 859 + } 860 + }, 798 861 "node_modules/@humanfs/core": { 799 862 "version": "0.19.1", 800 863 "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", ··· 1697 1760 "@babel/types": "^7.0.0" 1698 1761 } 1699 1762 }, 1763 + "node_modules/@types/babel__standalone": { 1764 + "version": "7.1.9", 1765 + "resolved": "https://registry.npmjs.org/@types/babel__standalone/-/babel__standalone-7.1.9.tgz", 1766 + "integrity": "sha512-IcCNPLqpevUD7UpV8QB0uwQPOyoOKACFf0YtYWRHcmxcakaje4Q7dbG2+jMqxw/I8Zk0NHvEps66WwS7z/UaaA==", 1767 + "dev": true, 1768 + "license": "MIT", 1769 + "dependencies": { 1770 + "@babel/parser": "^7.25.6", 1771 + "@babel/types": "^7.25.6", 1772 + "@types/babel__core": "^7.20.5", 1773 + "@types/babel__generator": "^7.6.8", 1774 + "@types/babel__template": "^7.4.4", 1775 + "@types/babel__traverse": "^7.20.6" 1776 + } 1777 + }, 1700 1778 "node_modules/@types/babel__template": { 1701 1779 "version": "7.4.4", 1702 1780 "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", ··· 1771 1849 "license": "MIT" 1772 1850 }, 1773 1851 "node_modules/@types/node": { 1774 - "version": "25.0.1", 1775 - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.1.tgz", 1776 - "integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==", 1852 + "version": "25.0.2", 1853 + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.2.tgz", 1854 + "integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==", 1777 1855 "license": "MIT", 1778 - "peer": true, 1779 1856 "dependencies": { 1780 1857 "undici-types": "~7.16.0" 1781 1858 } 1782 1859 }, 1860 + "node_modules/@types/react": { 1861 + "version": "19.2.7", 1862 + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", 1863 + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", 1864 + "dev": true, 1865 + "license": "MIT", 1866 + "dependencies": { 1867 + "csstype": "^3.2.2" 1868 + } 1869 + }, 1870 + "node_modules/@types/react-dom": { 1871 + "version": "19.2.3", 1872 + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", 1873 + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", 1874 + "dev": true, 1875 + "license": "MIT", 1876 + "peerDependencies": { 1877 + "@types/react": "^19.2.0" 1878 + } 1879 + }, 1880 + "node_modules/@types/text-encoding": { 1881 + "version": "0.0.40", 1882 + "resolved": "https://registry.npmjs.org/@types/text-encoding/-/text-encoding-0.0.40.tgz", 1883 + "integrity": "sha512-dHzoIdwBfY7jcSTTt6XBkaeiuFQAQD7r/7aJySKDdHkYBCDOvs9jPVt4NYXuwBMn89PP6gSd29WubIS19wTiXg==", 1884 + "dev": true, 1885 + "license": "MIT" 1886 + }, 1783 1887 "node_modules/@typescript-eslint/eslint-plugin": { 1784 1888 "version": "8.50.0", 1785 1889 "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", ··· 2580 2684 "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2581 2685 } 2582 2686 }, 2687 + "node_modules/arg": { 2688 + "version": "5.0.2", 2689 + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", 2690 + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", 2691 + "dev": true, 2692 + "license": "MIT" 2693 + }, 2583 2694 "node_modules/argparse": { 2584 2695 "version": "2.0.1", 2585 2696 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", ··· 2597 2708 "node": ">=12" 2598 2709 } 2599 2710 }, 2711 + "node_modules/asynckit": { 2712 + "version": "0.4.0", 2713 + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 2714 + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 2715 + "dev": true, 2716 + "license": "MIT" 2717 + }, 2718 + "node_modules/axios": { 2719 + "version": "1.13.2", 2720 + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", 2721 + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", 2722 + "dev": true, 2723 + "license": "MIT", 2724 + "dependencies": { 2725 + "follow-redirects": "^1.15.6", 2726 + "form-data": "^4.0.4", 2727 + "proxy-from-env": "^1.1.0" 2728 + } 2729 + }, 2600 2730 "node_modules/balanced-match": { 2601 2731 "version": "1.0.2", 2602 2732 "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", ··· 2617 2747 "version": "2.1.5", 2618 2748 "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", 2619 2749 "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", 2750 + "dev": true, 2751 + "license": "MIT" 2752 + }, 2753 + "node_modules/bluebird": { 2754 + "version": "3.7.2", 2755 + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 2756 + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", 2620 2757 "dev": true, 2621 2758 "license": "MIT" 2622 2759 }, ··· 2684 2821 "license": "MIT", 2685 2822 "peer": true 2686 2823 }, 2824 + "node_modules/call-bind-apply-helpers": { 2825 + "version": "1.0.2", 2826 + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 2827 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 2828 + "dev": true, 2829 + "license": "MIT", 2830 + "dependencies": { 2831 + "es-errors": "^1.3.0", 2832 + "function-bind": "^1.1.2" 2833 + }, 2834 + "engines": { 2835 + "node": ">= 0.4" 2836 + } 2837 + }, 2687 2838 "node_modules/callsites": { 2688 2839 "version": "3.1.0", 2689 2840 "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", ··· 2752 2903 }, 2753 2904 "engines": { 2754 2905 "node": ">=8" 2906 + } 2907 + }, 2908 + "node_modules/check-more-types": { 2909 + "version": "2.24.0", 2910 + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", 2911 + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", 2912 + "dev": true, 2913 + "license": "MIT", 2914 + "engines": { 2915 + "node": ">= 0.8.0" 2755 2916 } 2756 2917 }, 2757 2918 "node_modules/chrome-trace-event": { ··· 2864 3025 "dev": true, 2865 3026 "license": "MIT" 2866 3027 }, 3028 + "node_modules/combined-stream": { 3029 + "version": "1.0.8", 3030 + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 3031 + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 3032 + "dev": true, 3033 + "license": "MIT", 3034 + "dependencies": { 3035 + "delayed-stream": "~1.0.0" 3036 + }, 3037 + "engines": { 3038 + "node": ">= 0.8" 3039 + } 3040 + }, 2867 3041 "node_modules/commander": { 2868 3042 "version": "2.20.3", 2869 3043 "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", ··· 2906 3080 "node": ">= 8" 2907 3081 } 2908 3082 }, 3083 + "node_modules/csstype": { 3084 + "version": "3.2.3", 3085 + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", 3086 + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", 3087 + "dev": true, 3088 + "license": "MIT" 3089 + }, 2909 3090 "node_modules/debug": { 2910 3091 "version": "4.4.3", 2911 3092 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", ··· 2931 3112 "dev": true, 2932 3113 "license": "MIT" 2933 3114 }, 3115 + "node_modules/delayed-stream": { 3116 + "version": "1.0.0", 3117 + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 3118 + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 3119 + "dev": true, 3120 + "license": "MIT", 3121 + "engines": { 3122 + "node": ">=0.4.0" 3123 + } 3124 + }, 2934 3125 "node_modules/detect-libc": { 2935 3126 "version": "2.1.2", 2936 3127 "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", ··· 2941 3132 "node": ">=8" 2942 3133 } 2943 3134 }, 3135 + "node_modules/dunder-proto": { 3136 + "version": "1.0.1", 3137 + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 3138 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 3139 + "dev": true, 3140 + "license": "MIT", 3141 + "dependencies": { 3142 + "call-bind-apply-helpers": "^1.0.1", 3143 + "es-errors": "^1.3.0", 3144 + "gopd": "^1.2.0" 3145 + }, 3146 + "engines": { 3147 + "node": ">= 0.4" 3148 + } 3149 + }, 3150 + "node_modules/duplexer": { 3151 + "version": "0.1.2", 3152 + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", 3153 + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", 3154 + "dev": true, 3155 + "license": "MIT" 3156 + }, 2944 3157 "node_modules/electron-to-chromium": { 2945 3158 "version": "1.5.267", 2946 3159 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", ··· 2991 3204 "url": "https://github.com/sponsors/antfu" 2992 3205 } 2993 3206 }, 3207 + "node_modules/es-define-property": { 3208 + "version": "1.0.1", 3209 + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 3210 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 3211 + "dev": true, 3212 + "license": "MIT", 3213 + "engines": { 3214 + "node": ">= 0.4" 3215 + } 3216 + }, 3217 + "node_modules/es-errors": { 3218 + "version": "1.3.0", 3219 + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 3220 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 3221 + "dev": true, 3222 + "license": "MIT", 3223 + "engines": { 3224 + "node": ">= 0.4" 3225 + } 3226 + }, 2994 3227 "node_modules/es-module-lexer": { 2995 3228 "version": "1.7.0", 2996 3229 "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", 2997 3230 "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", 2998 3231 "license": "MIT" 3232 + }, 3233 + "node_modules/es-object-atoms": { 3234 + "version": "1.1.1", 3235 + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 3236 + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 3237 + "dev": true, 3238 + "license": "MIT", 3239 + "dependencies": { 3240 + "es-errors": "^1.3.0" 3241 + }, 3242 + "engines": { 3243 + "node": ">= 0.4" 3244 + } 3245 + }, 3246 + "node_modules/es-set-tostringtag": { 3247 + "version": "2.1.0", 3248 + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 3249 + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 3250 + "dev": true, 3251 + "license": "MIT", 3252 + "dependencies": { 3253 + "es-errors": "^1.3.0", 3254 + "get-intrinsic": "^1.2.6", 3255 + "has-tostringtag": "^1.0.2", 3256 + "hasown": "^2.0.2" 3257 + }, 3258 + "engines": { 3259 + "node": ">= 0.4" 3260 + } 2999 3261 }, 3000 3262 "node_modules/escalade": { 3001 3263 "version": "3.2.0", ··· 3292 3554 "node": ">=0.10.0" 3293 3555 } 3294 3556 }, 3557 + "node_modules/event-stream": { 3558 + "version": "3.3.4", 3559 + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", 3560 + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", 3561 + "dev": true, 3562 + "license": "MIT", 3563 + "dependencies": { 3564 + "duplexer": "~0.1.1", 3565 + "from": "~0", 3566 + "map-stream": "~0.1.0", 3567 + "pause-stream": "0.0.11", 3568 + "split": "0.3", 3569 + "stream-combiner": "~0.0.4", 3570 + "through": "~2.3.1" 3571 + } 3572 + }, 3295 3573 "node_modules/eventemitter3": { 3296 3574 "version": "5.0.1", 3297 3575 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", ··· 3308 3586 "engines": { 3309 3587 "node": ">=0.8.x" 3310 3588 } 3589 + }, 3590 + "node_modules/execa": { 3591 + "version": "5.1.1", 3592 + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", 3593 + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", 3594 + "dev": true, 3595 + "license": "MIT", 3596 + "dependencies": { 3597 + "cross-spawn": "^7.0.3", 3598 + "get-stream": "^6.0.0", 3599 + "human-signals": "^2.1.0", 3600 + "is-stream": "^2.0.0", 3601 + "merge-stream": "^2.0.0", 3602 + "npm-run-path": "^4.0.1", 3603 + "onetime": "^5.1.2", 3604 + "signal-exit": "^3.0.3", 3605 + "strip-final-newline": "^2.0.0" 3606 + }, 3607 + "engines": { 3608 + "node": ">=10" 3609 + }, 3610 + "funding": { 3611 + "url": "https://github.com/sindresorhus/execa?sponsor=1" 3612 + } 3613 + }, 3614 + "node_modules/execa/node_modules/onetime": { 3615 + "version": "5.1.2", 3616 + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 3617 + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 3618 + "dev": true, 3619 + "license": "MIT", 3620 + "dependencies": { 3621 + "mimic-fn": "^2.1.0" 3622 + }, 3623 + "engines": { 3624 + "node": ">=6" 3625 + }, 3626 + "funding": { 3627 + "url": "https://github.com/sponsors/sindresorhus" 3628 + } 3629 + }, 3630 + "node_modules/execa/node_modules/signal-exit": { 3631 + "version": "3.0.7", 3632 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 3633 + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 3634 + "dev": true, 3635 + "license": "ISC" 3311 3636 }, 3312 3637 "node_modules/exit-hook": { 3313 3638 "version": "2.2.1", ··· 3451 3776 "dev": true, 3452 3777 "license": "ISC" 3453 3778 }, 3779 + "node_modules/follow-redirects": { 3780 + "version": "1.15.11", 3781 + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", 3782 + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", 3783 + "dev": true, 3784 + "funding": [ 3785 + { 3786 + "type": "individual", 3787 + "url": "https://github.com/sponsors/RubenVerborgh" 3788 + } 3789 + ], 3790 + "license": "MIT", 3791 + "engines": { 3792 + "node": ">=4.0" 3793 + }, 3794 + "peerDependenciesMeta": { 3795 + "debug": { 3796 + "optional": true 3797 + } 3798 + } 3799 + }, 3800 + "node_modules/form-data": { 3801 + "version": "4.0.5", 3802 + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", 3803 + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", 3804 + "dev": true, 3805 + "license": "MIT", 3806 + "dependencies": { 3807 + "asynckit": "^0.4.0", 3808 + "combined-stream": "^1.0.8", 3809 + "es-set-tostringtag": "^2.1.0", 3810 + "hasown": "^2.0.2", 3811 + "mime-types": "^2.1.12" 3812 + }, 3813 + "engines": { 3814 + "node": ">= 6" 3815 + } 3816 + }, 3817 + "node_modules/from": { 3818 + "version": "0.1.7", 3819 + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", 3820 + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", 3821 + "dev": true, 3822 + "license": "MIT" 3823 + }, 3454 3824 "node_modules/fsevents": { 3455 3825 "version": "2.3.2", 3456 3826 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", ··· 3466 3836 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 3467 3837 } 3468 3838 }, 3839 + "node_modules/function-bind": { 3840 + "version": "1.1.2", 3841 + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 3842 + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 3843 + "dev": true, 3844 + "license": "MIT", 3845 + "funding": { 3846 + "url": "https://github.com/sponsors/ljharb" 3847 + } 3848 + }, 3469 3849 "node_modules/gensync": { 3470 3850 "version": "1.0.0-beta.2", 3471 3851 "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", ··· 3489 3869 "url": "https://github.com/sponsors/sindresorhus" 3490 3870 } 3491 3871 }, 3872 + "node_modules/get-intrinsic": { 3873 + "version": "1.3.0", 3874 + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 3875 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 3876 + "dev": true, 3877 + "license": "MIT", 3878 + "dependencies": { 3879 + "call-bind-apply-helpers": "^1.0.2", 3880 + "es-define-property": "^1.0.1", 3881 + "es-errors": "^1.3.0", 3882 + "es-object-atoms": "^1.1.1", 3883 + "function-bind": "^1.1.2", 3884 + "get-proto": "^1.0.1", 3885 + "gopd": "^1.2.0", 3886 + "has-symbols": "^1.1.0", 3887 + "hasown": "^2.0.2", 3888 + "math-intrinsics": "^1.1.0" 3889 + }, 3890 + "engines": { 3891 + "node": ">= 0.4" 3892 + }, 3893 + "funding": { 3894 + "url": "https://github.com/sponsors/ljharb" 3895 + } 3896 + }, 3897 + "node_modules/get-proto": { 3898 + "version": "1.0.1", 3899 + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 3900 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 3901 + "dev": true, 3902 + "license": "MIT", 3903 + "dependencies": { 3904 + "dunder-proto": "^1.0.1", 3905 + "es-object-atoms": "^1.0.0" 3906 + }, 3907 + "engines": { 3908 + "node": ">= 0.4" 3909 + } 3910 + }, 3911 + "node_modules/get-stream": { 3912 + "version": "6.0.1", 3913 + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", 3914 + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", 3915 + "dev": true, 3916 + "license": "MIT", 3917 + "engines": { 3918 + "node": ">=10" 3919 + }, 3920 + "funding": { 3921 + "url": "https://github.com/sponsors/sindresorhus" 3922 + } 3923 + }, 3492 3924 "node_modules/glob-parent": { 3493 3925 "version": "6.0.2", 3494 3926 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", ··· 3521 3953 "url": "https://github.com/sponsors/sindresorhus" 3522 3954 } 3523 3955 }, 3956 + "node_modules/gopd": { 3957 + "version": "1.2.0", 3958 + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 3959 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 3960 + "dev": true, 3961 + "license": "MIT", 3962 + "engines": { 3963 + "node": ">= 0.4" 3964 + }, 3965 + "funding": { 3966 + "url": "https://github.com/sponsors/ljharb" 3967 + } 3968 + }, 3524 3969 "node_modules/graceful-fs": { 3525 3970 "version": "4.2.11", 3526 3971 "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", ··· 3537 3982 "node": ">=8" 3538 3983 } 3539 3984 }, 3985 + "node_modules/has-symbols": { 3986 + "version": "1.1.0", 3987 + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 3988 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 3989 + "dev": true, 3990 + "license": "MIT", 3991 + "engines": { 3992 + "node": ">= 0.4" 3993 + }, 3994 + "funding": { 3995 + "url": "https://github.com/sponsors/ljharb" 3996 + } 3997 + }, 3998 + "node_modules/has-tostringtag": { 3999 + "version": "1.0.2", 4000 + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 4001 + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 4002 + "dev": true, 4003 + "license": "MIT", 4004 + "dependencies": { 4005 + "has-symbols": "^1.0.3" 4006 + }, 4007 + "engines": { 4008 + "node": ">= 0.4" 4009 + }, 4010 + "funding": { 4011 + "url": "https://github.com/sponsors/ljharb" 4012 + } 4013 + }, 4014 + "node_modules/hasown": { 4015 + "version": "2.0.2", 4016 + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 4017 + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 4018 + "dev": true, 4019 + "license": "MIT", 4020 + "dependencies": { 4021 + "function-bind": "^1.1.2" 4022 + }, 4023 + "engines": { 4024 + "node": ">= 0.4" 4025 + } 4026 + }, 3540 4027 "node_modules/hermes-estree": { 3541 4028 "version": "0.25.1", 3542 4029 "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", ··· 3552 4039 "license": "MIT", 3553 4040 "dependencies": { 3554 4041 "hermes-estree": "0.25.1" 4042 + } 4043 + }, 4044 + "node_modules/human-signals": { 4045 + "version": "2.1.0", 4046 + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", 4047 + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", 4048 + "dev": true, 4049 + "license": "Apache-2.0", 4050 + "engines": { 4051 + "node": ">=10.17.0" 3555 4052 } 3556 4053 }, 3557 4054 "node_modules/husky": { ··· 3663 4160 "node": ">=0.12.0" 3664 4161 } 3665 4162 }, 4163 + "node_modules/is-stream": { 4164 + "version": "2.0.1", 4165 + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 4166 + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 4167 + "dev": true, 4168 + "license": "MIT", 4169 + "engines": { 4170 + "node": ">=8" 4171 + }, 4172 + "funding": { 4173 + "url": "https://github.com/sponsors/sindresorhus" 4174 + } 4175 + }, 3666 4176 "node_modules/isexe": { 3667 4177 "version": "2.0.0", 3668 4178 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", ··· 3683 4193 }, 3684 4194 "engines": { 3685 4195 "node": ">= 10.13.0" 4196 + } 4197 + }, 4198 + "node_modules/joi": { 4199 + "version": "18.0.2", 4200 + "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz", 4201 + "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==", 4202 + "dev": true, 4203 + "license": "BSD-3-Clause", 4204 + "dependencies": { 4205 + "@hapi/address": "^5.1.1", 4206 + "@hapi/formula": "^3.0.2", 4207 + "@hapi/hoek": "^11.0.7", 4208 + "@hapi/pinpoint": "^2.0.1", 4209 + "@hapi/tlds": "^1.1.1", 4210 + "@hapi/topo": "^6.0.2", 4211 + "@standard-schema/spec": "^1.0.0" 4212 + }, 4213 + "engines": { 4214 + "node": ">= 20" 3686 4215 } 3687 4216 }, 3688 4217 "node_modules/js-tokens": { ··· 3777 4306 "license": "MIT", 3778 4307 "engines": { 3779 4308 "node": ">=6" 4309 + } 4310 + }, 4311 + "node_modules/lazy-ass": { 4312 + "version": "1.6.0", 4313 + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", 4314 + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", 4315 + "dev": true, 4316 + "license": "MIT", 4317 + "engines": { 4318 + "node": "> 0.8" 3780 4319 } 3781 4320 }, 3782 4321 "node_modules/levn": { ··· 4137 4676 "url": "https://github.com/sponsors/sindresorhus" 4138 4677 } 4139 4678 }, 4679 + "node_modules/lodash": { 4680 + "version": "4.17.21", 4681 + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 4682 + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 4683 + "dev": true, 4684 + "license": "MIT" 4685 + }, 4140 4686 "node_modules/lodash.merge": { 4141 4687 "version": "4.6.2", 4142 4688 "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", ··· 4184 4730 "@jridgewell/sourcemap-codec": "^1.5.5" 4185 4731 } 4186 4732 }, 4733 + "node_modules/map-stream": { 4734 + "version": "0.1.0", 4735 + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", 4736 + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", 4737 + "dev": true 4738 + }, 4739 + "node_modules/math-intrinsics": { 4740 + "version": "1.1.0", 4741 + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 4742 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 4743 + "dev": true, 4744 + "license": "MIT", 4745 + "engines": { 4746 + "node": ">= 0.4" 4747 + } 4748 + }, 4187 4749 "node_modules/merge-stream": { 4188 4750 "version": "2.0.0", 4189 4751 "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 4190 4752 "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 4191 - "license": "MIT", 4192 - "peer": true 4753 + "license": "MIT" 4193 4754 }, 4194 4755 "node_modules/micromatch": { 4195 4756 "version": "4.0.8", ··· 4223 4784 "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 4224 4785 "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 4225 4786 "license": "MIT", 4226 - "peer": true, 4227 4787 "engines": { 4228 4788 "node": ">= 0.6" 4229 4789 } ··· 4233 4793 "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 4234 4794 "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 4235 4795 "license": "MIT", 4236 - "peer": true, 4237 4796 "dependencies": { 4238 4797 "mime-db": "1.52.0" 4239 4798 }, ··· 4241 4800 "node": ">= 0.6" 4242 4801 } 4243 4802 }, 4803 + "node_modules/mimic-fn": { 4804 + "version": "2.1.0", 4805 + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 4806 + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 4807 + "dev": true, 4808 + "license": "MIT", 4809 + "engines": { 4810 + "node": ">=6" 4811 + } 4812 + }, 4244 4813 "node_modules/mimic-function": { 4245 4814 "version": "5.0.1", 4246 4815 "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", ··· 4329 4898 "node": "*" 4330 4899 } 4331 4900 }, 4901 + "node_modules/minimist": { 4902 + "version": "1.2.8", 4903 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 4904 + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 4905 + "dev": true, 4906 + "license": "MIT", 4907 + "funding": { 4908 + "url": "https://github.com/sponsors/ljharb" 4909 + } 4910 + }, 4332 4911 "node_modules/mrmime": { 4333 4912 "version": "2.0.1", 4334 4913 "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", ··· 4397 4976 "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", 4398 4977 "license": "MIT" 4399 4978 }, 4979 + "node_modules/npm-run-path": { 4980 + "version": "4.0.1", 4981 + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", 4982 + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", 4983 + "dev": true, 4984 + "license": "MIT", 4985 + "dependencies": { 4986 + "path-key": "^3.0.0" 4987 + }, 4988 + "engines": { 4989 + "node": ">=8" 4990 + } 4991 + }, 4400 4992 "node_modules/obug": { 4401 4993 "version": "2.1.1", 4402 4994 "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", ··· 4513 5105 "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 4514 5106 "dev": true, 4515 5107 "license": "MIT" 5108 + }, 5109 + "node_modules/pause-stream": { 5110 + "version": "0.0.11", 5111 + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", 5112 + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", 5113 + "dev": true, 5114 + "license": [ 5115 + "MIT", 5116 + "Apache2" 5117 + ], 5118 + "dependencies": { 5119 + "through": "~2.3" 5120 + } 4516 5121 }, 4517 5122 "node_modules/picocolors": { 4518 5123 "version": "1.1.1", ··· 4656 5261 "url": "https://github.com/prettier/prettier?sponsor=1" 4657 5262 } 4658 5263 }, 5264 + "node_modules/proxy-from-env": { 5265 + "version": "1.1.0", 5266 + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 5267 + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 5268 + "dev": true, 5269 + "license": "MIT" 5270 + }, 5271 + "node_modules/ps-tree": { 5272 + "version": "1.2.0", 5273 + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", 5274 + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", 5275 + "dev": true, 5276 + "license": "MIT", 5277 + "dependencies": { 5278 + "event-stream": "=3.3.4" 5279 + }, 5280 + "bin": { 5281 + "ps-tree": "bin/ps-tree.js" 5282 + }, 5283 + "engines": { 5284 + "node": ">= 0.10" 5285 + } 5286 + }, 4659 5287 "node_modules/punycode": { 4660 5288 "version": "2.3.1", 4661 5289 "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", ··· 4809 5437 "dev": true, 4810 5438 "license": "MIT" 4811 5439 }, 5440 + "node_modules/rxjs": { 5441 + "version": "7.8.2", 5442 + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", 5443 + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", 5444 + "dev": true, 5445 + "license": "Apache-2.0", 5446 + "dependencies": { 5447 + "tslib": "^2.1.0" 5448 + } 5449 + }, 4812 5450 "node_modules/safe-buffer": { 4813 5451 "version": "5.2.1", 4814 5452 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", ··· 5058 5696 "source-map": "^0.6.0" 5059 5697 } 5060 5698 }, 5699 + "node_modules/split": { 5700 + "version": "0.3.3", 5701 + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", 5702 + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", 5703 + "dev": true, 5704 + "license": "MIT", 5705 + "dependencies": { 5706 + "through": "2" 5707 + }, 5708 + "engines": { 5709 + "node": "*" 5710 + } 5711 + }, 5061 5712 "node_modules/stackback": { 5062 5713 "version": "0.0.2", 5063 5714 "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", ··· 5065 5716 "dev": true, 5066 5717 "license": "MIT" 5067 5718 }, 5719 + "node_modules/start-server-and-test": { 5720 + "version": "2.1.3", 5721 + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.1.3.tgz", 5722 + "integrity": "sha512-k4EcbNjeg0odaDkAMlIeDVDByqX9PIgL4tivgP2tES6Zd8o+4pTq/HgbWCyA3VHIoZopB+wGnNPKYGGSByNriQ==", 5723 + "dev": true, 5724 + "license": "MIT", 5725 + "dependencies": { 5726 + "arg": "^5.0.2", 5727 + "bluebird": "3.7.2", 5728 + "check-more-types": "2.24.0", 5729 + "debug": "4.4.3", 5730 + "execa": "5.1.1", 5731 + "lazy-ass": "1.6.0", 5732 + "ps-tree": "1.2.0", 5733 + "wait-on": "9.0.3" 5734 + }, 5735 + "bin": { 5736 + "server-test": "src/bin/start.js", 5737 + "start-server-and-test": "src/bin/start.js", 5738 + "start-test": "src/bin/start.js" 5739 + }, 5740 + "engines": { 5741 + "node": ">=16" 5742 + } 5743 + }, 5068 5744 "node_modules/std-env": { 5069 5745 "version": "3.10.0", 5070 5746 "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", ··· 5083 5759 "npm": ">=6" 5084 5760 } 5085 5761 }, 5762 + "node_modules/stream-combiner": { 5763 + "version": "0.0.4", 5764 + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", 5765 + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", 5766 + "dev": true, 5767 + "license": "MIT", 5768 + "dependencies": { 5769 + "duplexer": "~0.1.1" 5770 + } 5771 + }, 5086 5772 "node_modules/string-argv": { 5087 5773 "version": "0.3.2", 5088 5774 "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", ··· 5124 5810 }, 5125 5811 "funding": { 5126 5812 "url": "https://github.com/chalk/strip-ansi?sponsor=1" 5813 + } 5814 + }, 5815 + "node_modules/strip-final-newline": { 5816 + "version": "2.0.0", 5817 + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", 5818 + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", 5819 + "dev": true, 5820 + "license": "MIT", 5821 + "engines": { 5822 + "node": ">=6" 5127 5823 } 5128 5824 }, 5129 5825 "node_modules/strip-json-comments": { ··· 5236 5932 "deprecated": "no longer maintained", 5237 5933 "license": "(Unlicense OR Apache-2.0)" 5238 5934 }, 5935 + "node_modules/through": { 5936 + "version": "2.3.8", 5937 + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 5938 + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", 5939 + "dev": true, 5940 + "license": "MIT" 5941 + }, 5239 5942 "node_modules/tinybench": { 5240 5943 "version": "2.9.0", 5241 5944 "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", ··· 5321 6024 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 5322 6025 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 5323 6026 "dev": true, 5324 - "license": "0BSD", 5325 - "optional": true 6027 + "license": "0BSD" 5326 6028 }, 5327 6029 "node_modules/type-check": { 5328 6030 "version": "0.4.0", ··· 5343 6045 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 5344 6046 "dev": true, 5345 6047 "license": "Apache-2.0", 5346 - "peer": true, 5347 6048 "bin": { 5348 6049 "tsc": "bin/tsc", 5349 6050 "tsserver": "bin/tsserver" ··· 5390 6091 "version": "7.16.0", 5391 6092 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", 5392 6093 "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", 5393 - "license": "MIT", 5394 - "peer": true 6094 + "license": "MIT" 5395 6095 }, 5396 6096 "node_modules/unenv": { 5397 6097 "version": "2.0.0-rc.24", ··· 5644 6344 "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", 5645 6345 "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", 5646 6346 "license": "MIT" 6347 + }, 6348 + "node_modules/wait-on": { 6349 + "version": "9.0.3", 6350 + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.3.tgz", 6351 + "integrity": "sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==", 6352 + "dev": true, 6353 + "license": "MIT", 6354 + "dependencies": { 6355 + "axios": "^1.13.2", 6356 + "joi": "^18.0.1", 6357 + "lodash": "^4.17.21", 6358 + "minimist": "^1.2.8", 6359 + "rxjs": "^7.8.2" 6360 + }, 6361 + "bin": { 6362 + "wait-on": "bin/wait-on" 6363 + }, 6364 + "engines": { 6365 + "node": ">=20.0.0" 6366 + } 5647 6367 }, 5648 6368 "node_modules/watchpack": { 5649 6369 "version": "2.4.4",
+11 -1
package.json
··· 12 12 "preview": "vite preview", 13 13 "deploy": "npm install && npm run lint && npm test && npm run build:versions && wrangler pages deploy dist --project-name=rscexplorer", 14 14 "test": "vitest run --reporter=verbose", 15 - "lint": "eslint .", 15 + "typecheck": "tsc", 16 + "lint": "tsc && eslint .", 16 17 "format": "prettier --write .", 17 18 "format:check": "prettier --check .", 18 19 "prepare": "husky" ··· 41 42 "vite": "8.0.0-beta.2" 42 43 }, 43 44 "devDependencies": { 45 + "@babel/types": "^7.28.5", 44 46 "@eslint/js": "^9.39.2", 47 + "@types/babel__core": "^7.20.5", 48 + "@types/babel__standalone": "^7.1.9", 49 + "@types/node": "^25.0.2", 50 + "@types/react": "^19.2.7", 51 + "@types/react-dom": "^19.2.3", 52 + "@types/text-encoding": "^0.0.40", 45 53 "@vitejs/plugin-react": "^5.1.2", 46 54 "@vitest/browser": "^4.0.15", 47 55 "@vitest/browser-playwright": "^4.0.15", ··· 52 60 "playwright": "^1.57.0", 53 61 "prettier": "^3.7.4", 54 62 "rolldown": "^1.0.0-beta.54", 63 + "start-server-and-test": "^2.1.3", 64 + "typescript": "^5.9.3", 55 65 "typescript-eslint": "^8.50.0", 56 66 "vite": "8.0.0-beta.2", 57 67 "vitest": "^4.0.15",
+2 -1
src/client/byte-stream-polyfill.js src/client/byte-stream-polyfill.ts
··· 10 10 if (typeof globalThis.ReadableByteStreamController === "undefined") { 11 11 // Safari doesn't have byte stream support - use the polyfill's ReadableStream 12 12 // which includes full byte stream support 13 - globalThis.ReadableStream = PolyfillReadableStream; 13 + globalThis.ReadableStream = PolyfillReadableStream as typeof ReadableStream; 14 + // @ts-expect-error: ReadableByteStreamController polyfill assignment 14 15 globalThis.ReadableByteStreamController = PolyfillReadableByteStreamController; 15 16 }
-116
src/client/embed.jsx
··· 1 - // Must be first - shims webpack globals for react-server-dom-webpack 2 - import "./webpack-shim.js"; 3 - 4 - import "./byte-stream-polyfill.js"; 5 - import "web-streams-polyfill/polyfill"; 6 - import "text-encoding"; 7 - 8 - import React, { useState, useEffect } from "react"; 9 - import { createRoot } from "react-dom/client"; 10 - import { Workspace } from "./ui/Workspace.jsx"; 11 - import "./styles/workspace.css"; 12 - 13 - // Default code shown when no code is provided 14 - const DEFAULT_SERVER = `export default function App() { 15 - return <h1>RSC Explorer</h1>; 16 - }`; 17 - 18 - const DEFAULT_CLIENT = `'use client' 19 - 20 - export function Button({ children }) { 21 - return <button>{children}</button>; 22 - }`; 23 - 24 - function EmbedApp() { 25 - const [code, setCode] = useState(null); 26 - const [showFullscreen, setShowFullscreen] = useState(false); 27 - 28 - useEffect(() => { 29 - const handleMessage = (event) => { 30 - const { data } = event; 31 - if (data?.type === "rsc-embed:init") { 32 - setCode({ 33 - server: (data.code?.server || DEFAULT_SERVER).trim(), 34 - client: (data.code?.client || DEFAULT_CLIENT).trim(), 35 - }); 36 - if (data.showFullscreen !== false) { 37 - setShowFullscreen(true); 38 - } 39 - } 40 - }; 41 - 42 - window.addEventListener("message", handleMessage); 43 - 44 - // Signal to parent that we're ready to receive code 45 - if (window.parent !== window) { 46 - window.parent.postMessage({ type: "rsc-embed:ready" }, "*"); 47 - } 48 - 49 - return () => window.removeEventListener("message", handleMessage); 50 - }, []); 51 - 52 - // Report code changes back to parent 53 - const handleCodeChange = (server, client) => { 54 - if (window.parent !== window) { 55 - window.parent.postMessage( 56 - { 57 - type: "rsc-embed:code-changed", 58 - code: { server, client }, 59 - }, 60 - "*", 61 - ); 62 - } 63 - }; 64 - 65 - // Generate fullscreen URL 66 - const getFullscreenUrl = () => { 67 - if (!code) return "#"; 68 - const json = JSON.stringify({ server: code.server, client: code.client }); 69 - const encoded = encodeURIComponent(btoa(unescape(encodeURIComponent(json)))); 70 - return `https://rscexplorer.dev/?c=${encoded}`; 71 - }; 72 - 73 - // Show nothing until we receive code from parent 74 - if (!code) { 75 - return null; 76 - } 77 - 78 - return ( 79 - <> 80 - {showFullscreen && ( 81 - <div className="embed-header"> 82 - <span className="embed-title">RSC Explorer</span> 83 - <a 84 - href={getFullscreenUrl()} 85 - target="_blank" 86 - rel="noopener noreferrer" 87 - className="embed-fullscreen-link" 88 - title="Open in RSC Explorer" 89 - > 90 - <svg 91 - width="14" 92 - height="14" 93 - viewBox="0 0 24 24" 94 - fill="none" 95 - stroke="currentColor" 96 - strokeWidth="2" 97 - > 98 - <path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3" /> 99 - </svg> 100 - </a> 101 - </div> 102 - )} 103 - <Workspace 104 - key={`${code.server}:${code.client}`} 105 - initialServerCode={code.server} 106 - initialClientCode={code.client} 107 - onCodeChange={handleCodeChange} 108 - /> 109 - </> 110 - ); 111 - } 112 - 113 - document.addEventListener("DOMContentLoaded", () => { 114 - const root = createRoot(document.getElementById("embed-root")); 115 - root.render(<EmbedApp />); 116 - });
+148
src/client/embed.tsx
··· 1 + // Must be first - shims webpack globals for react-server-dom-webpack 2 + import "./webpack-shim.ts"; 3 + 4 + import "./byte-stream-polyfill.ts"; 5 + import "web-streams-polyfill/polyfill"; 6 + import "text-encoding"; 7 + 8 + import React, { useState, useEffect } from "react"; 9 + import { createRoot } from "react-dom/client"; 10 + import { Workspace } from "./ui/Workspace.tsx"; 11 + import "./styles/workspace.css"; 12 + 13 + const DEFAULT_SERVER = `export default function App() { 14 + return <h1>RSC Explorer</h1>; 15 + }`; 16 + 17 + const DEFAULT_CLIENT = `'use client' 18 + 19 + export function Button({ children }) { 20 + return <button>{children}</button>; 21 + }`; 22 + 23 + type CodeState = { 24 + server: string; 25 + client: string; 26 + }; 27 + 28 + type EmbedInitMessage = { 29 + type: "rsc-embed:init"; 30 + code?: { 31 + server?: string; 32 + client?: string; 33 + }; 34 + showFullscreen?: boolean; 35 + }; 36 + 37 + type EmbedReadyMessage = { 38 + type: "rsc-embed:ready"; 39 + }; 40 + 41 + type EmbedCodeChangedMessage = { 42 + type: "rsc-embed:code-changed"; 43 + code: { 44 + server: string; 45 + client: string; 46 + }; 47 + }; 48 + 49 + function isEmbedInitMessage(data: unknown): data is EmbedInitMessage { 50 + return ( 51 + typeof data === "object" && 52 + data !== null && 53 + (data as { type?: string }).type === "rsc-embed:init" 54 + ); 55 + } 56 + 57 + function EmbedApp(): React.ReactElement | null { 58 + const [code, setCode] = useState<CodeState | null>(null); 59 + const [showFullscreen, setShowFullscreen] = useState(false); 60 + 61 + useEffect(() => { 62 + const handleMessage = (event: MessageEvent<unknown>): void => { 63 + const { data } = event; 64 + if (isEmbedInitMessage(data)) { 65 + setCode({ 66 + server: (data.code?.server ?? DEFAULT_SERVER).trim(), 67 + client: (data.code?.client ?? DEFAULT_CLIENT).trim(), 68 + }); 69 + if (data.showFullscreen !== false) { 70 + setShowFullscreen(true); 71 + } 72 + } 73 + }; 74 + 75 + window.addEventListener("message", handleMessage); 76 + 77 + if (window.parent !== window) { 78 + const readyMessage: EmbedReadyMessage = { type: "rsc-embed:ready" }; 79 + window.parent.postMessage(readyMessage, "*"); 80 + } 81 + 82 + return () => window.removeEventListener("message", handleMessage); 83 + }, []); 84 + 85 + const handleCodeChange = (server: string, client: string): void => { 86 + if (window.parent !== window) { 87 + const changedMessage: EmbedCodeChangedMessage = { 88 + type: "rsc-embed:code-changed", 89 + code: { server, client }, 90 + }; 91 + window.parent.postMessage(changedMessage, "*"); 92 + } 93 + }; 94 + 95 + const getFullscreenUrl = (): string => { 96 + if (!code) return "#"; 97 + const json = JSON.stringify({ server: code.server, client: code.client }); 98 + const encoded = encodeURIComponent(btoa(unescape(encodeURIComponent(json)))); 99 + return `https://rscexplorer.dev/?c=${encoded}`; 100 + }; 101 + 102 + if (!code) { 103 + return null; 104 + } 105 + 106 + return ( 107 + <> 108 + {showFullscreen && ( 109 + <div className="embed-header"> 110 + <span className="embed-title">RSC Explorer</span> 111 + <a 112 + href={getFullscreenUrl()} 113 + target="_blank" 114 + rel="noopener noreferrer" 115 + className="embed-fullscreen-link" 116 + title="Open in RSC Explorer" 117 + > 118 + <svg 119 + width="14" 120 + height="14" 121 + viewBox="0 0 24 24" 122 + fill="none" 123 + stroke="currentColor" 124 + strokeWidth="2" 125 + > 126 + <path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3" /> 127 + </svg> 128 + </a> 129 + </div> 130 + )} 131 + <Workspace 132 + key={`${code.server}:${code.client}`} 133 + initialServerCode={code.server} 134 + initialClientCode={code.client} 135 + onCodeChange={handleCodeChange} 136 + /> 137 + </> 138 + ); 139 + } 140 + 141 + document.addEventListener("DOMContentLoaded", () => { 142 + const container = document.getElementById("embed-root"); 143 + if (!container) { 144 + throw new Error("Could not find #embed-root element"); 145 + } 146 + const root = createRoot(container); 147 + root.render(<EmbedApp />); 148 + });
-16
src/client/index.jsx
··· 1 - // Must be first - shims webpack globals for react-server-dom-webpack 2 - import "./webpack-shim.js"; 3 - 4 - import "./byte-stream-polyfill.js"; 5 - import "web-streams-polyfill/polyfill"; 6 - import "text-encoding"; 7 - 8 - import React from "react"; 9 - import { createRoot } from "react-dom/client"; 10 - import { App } from "./ui/App.jsx"; 11 - 12 - // Mount the app 13 - document.addEventListener("DOMContentLoaded", () => { 14 - const root = createRoot(document.getElementById("app")); 15 - root.render(<App />); 16 - });
+18
src/client/index.tsx
··· 1 + // Must be first - shims webpack globals for react-server-dom-webpack 2 + import "./webpack-shim.ts"; 3 + 4 + import "./byte-stream-polyfill.ts"; 5 + import "web-streams-polyfill/polyfill"; 6 + import "text-encoding"; 7 + 8 + import { createRoot } from "react-dom/client"; 9 + import { App } from "./ui/App.tsx"; 10 + 11 + document.addEventListener("DOMContentLoaded", () => { 12 + const container = document.getElementById("app"); 13 + if (!container) { 14 + throw new Error("Could not find #app element"); 15 + } 16 + const root = createRoot(container); 17 + root.render(<App />); 18 + });
-3
src/client/runtime/index.js
··· 1 - export { registerClientModule, evaluateClientModule } from "./module-registry.js"; 2 - export { SteppableStream } from "./steppable-stream.js"; 3 - export { Timeline } from "./timeline.js";
+15
src/client/runtime/index.ts
··· 1 + export { registerClientModule, evaluateClientModule } from "./module-registry.ts"; 2 + export { 3 + SteppableStream, 4 + type SteppableStreamOptions, 5 + type Thenable, 6 + type CallServerCallback, 7 + } from "./steppable-stream.ts"; 8 + export { 9 + Timeline, 10 + type TimelineEntry, 11 + type RenderEntry, 12 + type ActionEntry, 13 + type TimelineSnapshot, 14 + type TimelinePosition, 15 + } from "./timeline.ts";
-18
src/client/runtime/module-registry.js
··· 1 - import React from "react"; 2 - 3 - export function registerClientModule(moduleId, moduleExports) { 4 - if (typeof __webpack_module_cache__ !== "undefined") { 5 - __webpack_module_cache__[moduleId] = { exports: moduleExports }; 6 - } 7 - } 8 - 9 - export function evaluateClientModule(compiledCode) { 10 - const module = { exports: {} }; 11 - const require = (id) => { 12 - if (id === "react") return React; 13 - throw new Error(`Module "${id}" not found in client context`); 14 - }; 15 - const fn = new Function("module", "exports", "require", "React", compiledCode); 16 - fn(module, module.exports, require, React); 17 - return module.exports; 18 - }
+28
src/client/runtime/module-registry.ts
··· 1 + import React from "react"; 2 + 3 + declare const __webpack_module_cache__: Record<string, { exports: unknown }>; 4 + 5 + export function registerClientModule(moduleId: string, moduleExports: unknown): void { 6 + if (typeof __webpack_module_cache__ !== "undefined") { 7 + __webpack_module_cache__[moduleId] = { exports: moduleExports }; 8 + } 9 + } 10 + 11 + type ModuleExports = Record<string, unknown>; 12 + type RequireFn = (id: string) => unknown; 13 + 14 + export function evaluateClientModule(compiledCode: string): ModuleExports { 15 + const module: { exports: ModuleExports } = { exports: {} }; 16 + const require: RequireFn = (id: string): unknown => { 17 + if (id === "react") return React; 18 + throw new Error(`Module "${id}" not found in client context`); 19 + }; 20 + const fn = new Function("module", "exports", "require", "React", compiledCode) as ( 21 + module: { exports: ModuleExports }, 22 + exports: ModuleExports, 23 + require: RequireFn, 24 + ReactLib: typeof React, 25 + ) => void; 26 + fn(module, module.exports, require, React); 27 + return module.exports; 28 + }
-68
src/client/runtime/steppable-stream.js
··· 1 - import { createFromReadableStream } from "react-server-dom-webpack/client"; 2 - 3 - /** 4 - * SteppableStream - makes a Flight stream steppable for debugging. 5 - * 6 - * Buffers incoming rows and controls their release to the Flight decoder. 7 - * The flightPromise only resolves when all rows have been released. 8 - */ 9 - export class SteppableStream { 10 - constructor(source, { callServer } = {}) { 11 - this.rows = []; 12 - this.releasedCount = 0; 13 - this.buffered = false; 14 - this.closed = false; 15 - 16 - const encoder = new TextEncoder(); 17 - let controller; 18 - const output = new ReadableStream({ 19 - start: (c) => { 20 - controller = c; 21 - }, 22 - }); 23 - 24 - this.release = (count) => { 25 - while (this.releasedCount < count && this.releasedCount < this.rows.length) { 26 - controller.enqueue(encoder.encode(this.rows[this.releasedCount] + "\n")); 27 - this.releasedCount++; 28 - } 29 - if (this.releasedCount >= this.rows.length && this.buffered && !this.closed) { 30 - controller.close(); 31 - this.closed = true; 32 - } 33 - }; 34 - 35 - this.flightPromise = createFromReadableStream(output, { callServer }); 36 - this.bufferPromise = this.buffer(source); 37 - } 38 - 39 - async buffer(stream) { 40 - const reader = stream.getReader(); 41 - const decoder = new TextDecoder(); 42 - let partial = ""; 43 - 44 - try { 45 - while (true) { 46 - const { done, value } = await reader.read(); 47 - if (done) break; 48 - 49 - partial += decoder.decode(value, { stream: true }); 50 - const lines = partial.split("\n"); 51 - partial = lines.pop(); 52 - 53 - for (const line of lines) { 54 - if (line.trim()) this.rows.push(line); 55 - } 56 - } 57 - 58 - partial += decoder.decode(); 59 - if (partial.trim()) this.rows.push(partial); 60 - } finally { 61 - this.buffered = true; 62 - } 63 - } 64 - 65 - async waitForBuffer() { 66 - await this.bufferPromise; 67 - } 68 - }
+94
src/client/runtime/steppable-stream.ts
··· 1 + import { 2 + createFromReadableStream, 3 + type CallServerCallback as ImportedCallServerCallback, 4 + } from "react-server-dom-webpack/client"; 5 + 6 + export type CallServerCallback = ImportedCallServerCallback; 7 + 8 + // React's Thenable type (not exported from react package) 9 + export interface Thenable<T> { 10 + then<TResult1 = T, TResult2 = never>( 11 + onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, 12 + onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null, 13 + ): PromiseLike<TResult1 | TResult2>; 14 + } 15 + 16 + export interface SteppableStreamOptions { 17 + callServer?: CallServerCallback; 18 + } 19 + 20 + /** 21 + * SteppableStream - makes a Flight stream steppable for debugging. 22 + * 23 + * Buffers incoming rows and controls their release to the Flight decoder. 24 + * The flightPromise only resolves when all rows have been released. 25 + */ 26 + export class SteppableStream { 27 + rows: string[] = []; 28 + releasedCount = 0; 29 + buffered = false; 30 + closed = false; 31 + release: (count: number) => void; 32 + flightPromise: Thenable<unknown>; 33 + bufferPromise: Promise<void>; 34 + 35 + constructor(source: ReadableStream<Uint8Array>, options: SteppableStreamOptions = {}) { 36 + const { callServer } = options; 37 + 38 + const encoder = new TextEncoder(); 39 + let controller: ReadableStreamDefaultController<Uint8Array>; 40 + const output = new ReadableStream<Uint8Array>({ 41 + start: (c) => { 42 + controller = c; 43 + }, 44 + }); 45 + 46 + this.release = (count: number): void => { 47 + while (this.releasedCount < count && this.releasedCount < this.rows.length) { 48 + const row = this.rows[this.releasedCount]; 49 + if (row !== undefined) { 50 + controller.enqueue(encoder.encode(row + "\n")); 51 + } 52 + this.releasedCount++; 53 + } 54 + if (this.releasedCount >= this.rows.length && this.buffered && !this.closed) { 55 + controller.close(); 56 + this.closed = true; 57 + } 58 + }; 59 + 60 + const streamOptions = callServer ? { callServer } : {}; 61 + this.flightPromise = createFromReadableStream(output, streamOptions); 62 + this.bufferPromise = this.buffer(source); 63 + } 64 + 65 + private async buffer(stream: ReadableStream<Uint8Array>): Promise<void> { 66 + const reader = stream.getReader(); 67 + const decoder = new TextDecoder(); 68 + let partial = ""; 69 + 70 + try { 71 + while (true) { 72 + const { done, value } = await reader.read(); 73 + if (done) break; 74 + 75 + partial += decoder.decode(value, { stream: true }); 76 + const lines = partial.split("\n"); 77 + partial = lines.pop() ?? ""; 78 + 79 + for (const line of lines) { 80 + if (line.trim()) this.rows.push(line); 81 + } 82 + } 83 + 84 + partial += decoder.decode(); 85 + if (partial.trim()) this.rows.push(partial); 86 + } finally { 87 + this.buffered = true; 88 + } 89 + } 90 + 91 + async waitForBuffer(): Promise<void> { 92 + await this.bufferPromise; 93 + } 94 + }
-125
src/client/runtime/timeline.js
··· 1 - /** 2 - * Timeline - manages a sequence of Flight responses for debugging. 3 - * 4 - * Each entry owns its SteppableStream(s). The cursor controls playback. 5 - * Stepping releases data to streams; I/O is handled externally. 6 - * 7 - * Entry types: 8 - * - render: { type, stream } - initial render 9 - * - action: { type, name, args, stream } - action invoked from client or added manually 10 - */ 11 - export class Timeline { 12 - constructor() { 13 - this.entries = []; 14 - this.cursor = 0; 15 - this.listeners = new Set(); 16 - this.snapshot = null; 17 - } 18 - 19 - subscribe = (listener) => { 20 - this.listeners.add(listener); 21 - return () => this.listeners.delete(listener); 22 - }; 23 - 24 - notify() { 25 - this.snapshot = null; // Invalidate cache 26 - this.listeners.forEach((fn) => fn()); 27 - } 28 - 29 - getChunkCount(entry) { 30 - return entry.stream?.rows?.length || 0; 31 - } 32 - 33 - getTotalChunks() { 34 - return this.entries.reduce((sum, e) => sum + this.getChunkCount(e), 0); 35 - } 36 - 37 - getPosition(globalChunk) { 38 - let remaining = globalChunk; 39 - for (let i = 0; i < this.entries.length; i++) { 40 - const count = this.getChunkCount(this.entries[i]); 41 - if (remaining < count) { 42 - return { entryIndex: i, localChunk: remaining }; 43 - } 44 - remaining -= count; 45 - } 46 - return null; 47 - } 48 - 49 - getEntryStart(entryIndex) { 50 - let start = 0; 51 - for (let i = 0; i < entryIndex; i++) { 52 - start += this.getChunkCount(this.entries[i]); 53 - } 54 - return start; 55 - } 56 - 57 - canDeleteEntry(entryIndex) { 58 - if (entryIndex < 0 || entryIndex >= this.entries.length) return false; 59 - return this.cursor <= this.getEntryStart(entryIndex); 60 - } 61 - 62 - // For useSyncExternalStore compatibility - must return cached object 63 - getSnapshot = () => { 64 - if (this.snapshot) return this.snapshot; 65 - 66 - const totalChunks = this.getTotalChunks(); 67 - this.snapshot = { 68 - entries: this.entries, 69 - cursor: this.cursor, 70 - totalChunks, 71 - isAtStart: this.cursor === 0, 72 - isAtEnd: this.cursor >= totalChunks, 73 - }; 74 - return this.snapshot; 75 - }; 76 - 77 - setRender(stream) { 78 - this.entries = [{ type: "render", stream }]; 79 - this.cursor = 0; 80 - this.notify(); 81 - } 82 - 83 - addAction(name, args, stream) { 84 - this.entries = [...this.entries, { type: "action", name, args, stream }]; 85 - this.notify(); 86 - } 87 - 88 - deleteEntry(entryIndex) { 89 - if (!this.canDeleteEntry(entryIndex)) return false; 90 - this.entries = this.entries.filter((_, i) => i !== entryIndex); 91 - this.notify(); 92 - return true; 93 - } 94 - 95 - stepForward() { 96 - const total = this.getTotalChunks(); 97 - if (this.cursor >= total) return; 98 - 99 - const pos = this.getPosition(this.cursor); 100 - if (!pos) return; 101 - 102 - const entry = this.entries[pos.entryIndex]; 103 - this.cursor++; 104 - entry.stream.release(pos.localChunk + 1); 105 - 106 - this.notify(); 107 - } 108 - 109 - skipToEntryEnd() { 110 - const pos = this.getPosition(this.cursor); 111 - if (!pos) return; 112 - 113 - const entryEnd = 114 - this.getEntryStart(pos.entryIndex) + this.getChunkCount(this.entries[pos.entryIndex]); 115 - while (this.cursor < entryEnd) { 116 - this.stepForward(); 117 - } 118 - } 119 - 120 - clear() { 121 - this.entries = []; 122 - this.cursor = 0; 123 - this.notify(); 124 - } 125 - }
+166
src/client/runtime/timeline.ts
··· 1 + import type { SteppableStream } from "./steppable-stream.ts"; 2 + 3 + /** 4 + * Timeline - manages a sequence of Flight responses for debugging. 5 + * 6 + * Each entry owns its SteppableStream(s). The cursor controls playback. 7 + * Stepping releases data to streams; I/O is handled externally. 8 + * 9 + * Entry types: 10 + * - render: { type, stream } - initial render 11 + * - action: { type, name, args, stream } - action invoked from client or added manually 12 + */ 13 + 14 + export interface RenderEntry { 15 + type: "render"; 16 + stream: SteppableStream; 17 + } 18 + 19 + export interface ActionEntry { 20 + type: "action"; 21 + name: string; 22 + args: string; 23 + stream: SteppableStream; 24 + } 25 + 26 + export type TimelineEntry = RenderEntry | ActionEntry; 27 + 28 + export interface TimelineSnapshot { 29 + entries: TimelineEntry[]; 30 + cursor: number; 31 + totalChunks: number; 32 + isAtStart: boolean; 33 + isAtEnd: boolean; 34 + } 35 + 36 + export interface TimelinePosition { 37 + entryIndex: number; 38 + localChunk: number; 39 + } 40 + 41 + type TimelineListener = () => void; 42 + 43 + export class Timeline { 44 + entries: TimelineEntry[] = []; 45 + cursor = 0; 46 + private listeners: Set<TimelineListener> = new Set(); 47 + private snapshot: TimelineSnapshot | null = null; 48 + 49 + subscribe = (listener: TimelineListener): (() => void) => { 50 + this.listeners.add(listener); 51 + return () => { 52 + this.listeners.delete(listener); 53 + }; 54 + }; 55 + 56 + private notify(): void { 57 + this.snapshot = null; // Invalidate cache 58 + this.listeners.forEach((fn) => fn()); 59 + } 60 + 61 + getChunkCount(entry: TimelineEntry): number { 62 + return entry.stream.rows.length; 63 + } 64 + 65 + getTotalChunks(): number { 66 + return this.entries.reduce((sum, e) => sum + this.getChunkCount(e), 0); 67 + } 68 + 69 + getPosition(globalChunk: number): TimelinePosition | null { 70 + let remaining = globalChunk; 71 + for (let i = 0; i < this.entries.length; i++) { 72 + const entry = this.entries[i]; 73 + if (!entry) continue; 74 + const count = this.getChunkCount(entry); 75 + if (remaining < count) { 76 + return { entryIndex: i, localChunk: remaining }; 77 + } 78 + remaining -= count; 79 + } 80 + return null; 81 + } 82 + 83 + getEntryStart(entryIndex: number): number { 84 + let start = 0; 85 + for (let i = 0; i < entryIndex; i++) { 86 + const entry = this.entries[i]; 87 + if (entry) { 88 + start += this.getChunkCount(entry); 89 + } 90 + } 91 + return start; 92 + } 93 + 94 + canDeleteEntry(entryIndex: number): boolean { 95 + if (entryIndex < 0 || entryIndex >= this.entries.length) return false; 96 + return this.cursor <= this.getEntryStart(entryIndex); 97 + } 98 + 99 + // For useSyncExternalStore compatibility - must return cached object 100 + getSnapshot = (): TimelineSnapshot => { 101 + if (this.snapshot) return this.snapshot; 102 + 103 + const totalChunks = this.getTotalChunks(); 104 + this.snapshot = { 105 + entries: this.entries, 106 + cursor: this.cursor, 107 + totalChunks, 108 + isAtStart: this.cursor === 0, 109 + isAtEnd: this.cursor >= totalChunks, 110 + }; 111 + return this.snapshot; 112 + }; 113 + 114 + setRender(stream: SteppableStream): void { 115 + this.entries = [{ type: "render", stream }]; 116 + this.cursor = 0; 117 + this.notify(); 118 + } 119 + 120 + addAction(name: string, args: string, stream: SteppableStream): void { 121 + this.entries = [...this.entries, { type: "action", name, args, stream }]; 122 + this.notify(); 123 + } 124 + 125 + deleteEntry(entryIndex: number): boolean { 126 + if (!this.canDeleteEntry(entryIndex)) return false; 127 + this.entries = this.entries.filter((_, i) => i !== entryIndex); 128 + this.notify(); 129 + return true; 130 + } 131 + 132 + stepForward(): void { 133 + const total = this.getTotalChunks(); 134 + if (this.cursor >= total) return; 135 + 136 + const pos = this.getPosition(this.cursor); 137 + if (!pos) return; 138 + 139 + const entry = this.entries[pos.entryIndex]; 140 + if (!entry) return; 141 + 142 + this.cursor++; 143 + entry.stream.release(pos.localChunk + 1); 144 + 145 + this.notify(); 146 + } 147 + 148 + skipToEntryEnd(): void { 149 + const pos = this.getPosition(this.cursor); 150 + if (!pos) return; 151 + 152 + const entry = this.entries[pos.entryIndex]; 153 + if (!entry) return; 154 + 155 + const entryEnd = this.getEntryStart(pos.entryIndex) + this.getChunkCount(entry); 156 + while (this.cursor < entryEnd) { 157 + this.stepForward(); 158 + } 159 + } 160 + 161 + clear(): void { 162 + this.entries = []; 163 + this.cursor = 0; 164 + this.notify(); 165 + } 166 + }
+7 -1
src/client/samples.js src/client/samples.ts
··· 1 - export const SAMPLES = { 1 + export interface Sample { 2 + name: string; 3 + server: string; 4 + client: string; 5 + } 6 + 7 + export const SAMPLES: Record<string, Sample> = { 2 8 hello: { 3 9 name: "Hello World", 4 10 server: `export default function App() {
-175
src/client/server-worker.js
··· 1 - import workerUrl from "../server/worker.js?rolldown-worker"; 2 - 3 - const randomUUID = 4 - crypto.randomUUID?.bind(crypto) ?? 5 - function () { 6 - const bytes = crypto.getRandomValues(new Uint8Array(16)); 7 - bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4 8 - bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1 9 - const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")); 10 - return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`; 11 - }; 12 - 13 - function serializeForTransfer(encoded) { 14 - if (encoded instanceof FormData) { 15 - return { type: "formdata", data: new URLSearchParams(encoded).toString() }; 16 - } 17 - return { type: "string", data: encoded }; 18 - } 19 - 20 - export class ServerWorker { 21 - constructor() { 22 - this.worker = new Worker(workerUrl); 23 - this.pending = new Map(); 24 - this.streams = new Map(); 25 - this.readyPromise = new Promise((resolve) => { 26 - this.readyResolve = resolve; 27 - }); 28 - 29 - this.worker.onmessage = this.handleMessage.bind(this); 30 - this.worker.onerror = this.handleError.bind(this); 31 - } 32 - 33 - handleMessage(event) { 34 - const { type, requestId, error, chunk } = event.data; 35 - 36 - if (type === "ready") { 37 - this.readyResolve(); 38 - return; 39 - } 40 - 41 - if (type === "stream-start") { 42 - const pending = this.pending.get(requestId); 43 - if (!pending) return; 44 - this.pending.delete(requestId); 45 - 46 - let controller; 47 - const stream = new ReadableStream({ 48 - start: (c) => { 49 - controller = c; 50 - }, 51 - }); 52 - this.streams.set(requestId, controller); 53 - pending.resolve(stream); 54 - return; 55 - } 56 - 57 - if (type === "stream-chunk") { 58 - const controller = this.streams.get(requestId); 59 - if (controller) controller.enqueue(chunk); 60 - return; 61 - } 62 - 63 - if (type === "stream-end") { 64 - const controller = this.streams.get(requestId); 65 - if (controller) { 66 - controller.close(); 67 - this.streams.delete(requestId); 68 - } 69 - return; 70 - } 71 - 72 - if (type === "stream-error") { 73 - const controller = this.streams.get(requestId); 74 - if (controller) { 75 - controller.error(new Error(error.message)); 76 - this.streams.delete(requestId); 77 - } 78 - return; 79 - } 80 - 81 - const pending = this.pending.get(requestId); 82 - if (!pending) { 83 - console.warn(`No pending request for ${requestId}`); 84 - return; 85 - } 86 - 87 - this.pending.delete(requestId); 88 - 89 - if (type === "error") { 90 - const err = new Error(error.message); 91 - err.stack = error.stack; 92 - pending.reject(err); 93 - } else if (type === "deployed") { 94 - pending.resolve(); 95 - } 96 - } 97 - 98 - handleError(event) { 99 - const errorMsg = event.message || event.error?.message || "Unknown worker error"; 100 - console.error(`Worker error: ${errorMsg}`); 101 - 102 - for (const [, pending] of this.pending) { 103 - pending.reject(new Error(`Worker error: ${errorMsg}`)); 104 - } 105 - this.pending.clear(); 106 - } 107 - 108 - async deploy({ compiledCode, manifest, actionNames }) { 109 - await this.readyPromise; 110 - const requestId = randomUUID(); 111 - 112 - return new Promise((resolve, reject) => { 113 - this.pending.set(requestId, { resolve, reject }); 114 - this.worker.postMessage({ 115 - type: "deploy", 116 - requestId, 117 - compiledCode, 118 - manifest, 119 - actionNames, 120 - }); 121 - }); 122 - } 123 - 124 - async render() { 125 - await this.readyPromise; 126 - const requestId = randomUUID(); 127 - 128 - return new Promise((resolve, reject) => { 129 - this.pending.set(requestId, { resolve, reject }); 130 - this.worker.postMessage({ type: "render", requestId }); 131 - }); 132 - } 133 - 134 - async callAction(actionId, encodedArgs) { 135 - await this.readyPromise; 136 - const requestId = randomUUID(); 137 - 138 - return new Promise((resolve, reject) => { 139 - this.pending.set(requestId, { resolve, reject }); 140 - this.worker.postMessage({ 141 - type: "action", 142 - requestId, 143 - actionId, 144 - encodedArgs: serializeForTransfer(encodedArgs), 145 - }); 146 - }); 147 - } 148 - 149 - async callActionRaw(actionId, rawPayload) { 150 - await this.readyPromise; 151 - const requestId = randomUUID(); 152 - 153 - return new Promise((resolve, reject) => { 154 - this.pending.set(requestId, { resolve, reject }); 155 - this.worker.postMessage({ 156 - type: "action", 157 - requestId, 158 - actionId, 159 - encodedArgs: { type: "formdata", data: rawPayload }, 160 - }); 161 - }); 162 - } 163 - 164 - terminate() { 165 - this.worker.terminate(); 166 - for (const [, pending] of this.pending) { 167 - pending.reject(new Error("Worker terminated")); 168 - } 169 - this.pending.clear(); 170 - for (const [, controller] of this.streams) { 171 - controller.error(new Error("Worker terminated")); 172 - } 173 - this.streams.clear(); 174 - } 175 - }
+272
src/client/server-worker.ts
··· 1 + import workerUrl from "../server/worker.ts?rolldown-worker"; 2 + import type { ClientManifest } from "../shared/compiler.ts"; 3 + 4 + const randomUUID: () => string = 5 + crypto.randomUUID?.bind(crypto) ?? 6 + function (): string { 7 + const bytes = crypto.getRandomValues(new Uint8Array(16)); 8 + bytes[6] = (bytes[6]! & 0x0f) | 0x40; // version 4 9 + bytes[8] = (bytes[8]! & 0x3f) | 0x80; // variant 1 10 + const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")); 11 + return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`; 12 + }; 13 + 14 + type EncodedArgsFormData = { 15 + type: "formdata"; 16 + data: string; 17 + }; 18 + 19 + type EncodedArgsString = { 20 + type: "string"; 21 + data: string; 22 + }; 23 + 24 + type EncodedArgsTransfer = EncodedArgsFormData | EncodedArgsString; 25 + 26 + function serializeForTransfer(encoded: FormData | string): EncodedArgsTransfer { 27 + if (encoded instanceof FormData) { 28 + return { 29 + type: "formdata", 30 + data: new URLSearchParams(encoded as unknown as Record<string, string>).toString(), 31 + }; 32 + } 33 + return { type: "string", data: encoded }; 34 + } 35 + 36 + type WorkerReadyMessage = { 37 + type: "ready"; 38 + }; 39 + 40 + type WorkerStreamStartMessage = { 41 + type: "stream-start"; 42 + requestId: string; 43 + }; 44 + 45 + type WorkerStreamChunkMessage = { 46 + type: "stream-chunk"; 47 + requestId: string; 48 + chunk: Uint8Array; 49 + }; 50 + 51 + type WorkerStreamEndMessage = { 52 + type: "stream-end"; 53 + requestId: string; 54 + }; 55 + 56 + type WorkerStreamErrorMessage = { 57 + type: "stream-error"; 58 + requestId: string; 59 + error: { message: string }; 60 + }; 61 + 62 + type WorkerDeployedMessage = { 63 + type: "deployed"; 64 + requestId: string; 65 + }; 66 + 67 + type WorkerErrorMessage = { 68 + type: "error"; 69 + requestId: string; 70 + error: { message: string; stack?: string }; 71 + }; 72 + 73 + type WorkerMessage = 74 + | WorkerReadyMessage 75 + | WorkerStreamStartMessage 76 + | WorkerStreamChunkMessage 77 + | WorkerStreamEndMessage 78 + | WorkerStreamErrorMessage 79 + | WorkerDeployedMessage 80 + | WorkerErrorMessage; 81 + 82 + type PendingRequest = { 83 + resolve: (value: unknown) => void; 84 + reject: (error: Error) => void; 85 + }; 86 + 87 + export class ServerWorker { 88 + private worker: Worker; 89 + private pending: Map<string, PendingRequest> = new Map(); 90 + private streams: Map<string, ReadableStreamDefaultController<Uint8Array>> = new Map(); 91 + private readyPromise: Promise<void>; 92 + private readyResolve!: () => void; 93 + 94 + constructor() { 95 + this.worker = new Worker(workerUrl); 96 + this.readyPromise = new Promise((resolve) => { 97 + this.readyResolve = resolve; 98 + }); 99 + 100 + this.worker.onmessage = this.handleMessage.bind(this); 101 + this.worker.onerror = this.handleError.bind(this); 102 + } 103 + 104 + private handleMessage(event: MessageEvent<WorkerMessage>): void { 105 + const { type } = event.data; 106 + 107 + if (type === "ready") { 108 + this.readyResolve(); 109 + return; 110 + } 111 + 112 + if (type === "stream-start") { 113 + const { requestId } = event.data; 114 + const pending = this.pending.get(requestId); 115 + if (!pending) return; 116 + this.pending.delete(requestId); 117 + 118 + let controller: ReadableStreamDefaultController<Uint8Array>; 119 + const stream = new ReadableStream<Uint8Array>({ 120 + start: (c) => { 121 + controller = c; 122 + }, 123 + }); 124 + this.streams.set(requestId, controller!); 125 + pending.resolve(stream); 126 + return; 127 + } 128 + 129 + if (type === "stream-chunk") { 130 + const { requestId, chunk } = event.data; 131 + const controller = this.streams.get(requestId); 132 + if (controller) controller.enqueue(chunk); 133 + return; 134 + } 135 + 136 + if (type === "stream-end") { 137 + const { requestId } = event.data; 138 + const controller = this.streams.get(requestId); 139 + if (controller) { 140 + controller.close(); 141 + this.streams.delete(requestId); 142 + } 143 + return; 144 + } 145 + 146 + if (type === "stream-error") { 147 + const { requestId, error } = event.data; 148 + const controller = this.streams.get(requestId); 149 + if (controller) { 150 + controller.error(new Error(error.message)); 151 + this.streams.delete(requestId); 152 + } 153 + return; 154 + } 155 + 156 + if (type === "deployed") { 157 + const { requestId } = event.data as WorkerDeployedMessage; 158 + const pending = this.pending.get(requestId); 159 + if (!pending) { 160 + console.warn(`No pending request for ${requestId}`); 161 + return; 162 + } 163 + this.pending.delete(requestId); 164 + pending.resolve(undefined); 165 + return; 166 + } 167 + 168 + if (type === "error") { 169 + const { requestId, error } = event.data as WorkerErrorMessage; 170 + const pending = this.pending.get(requestId); 171 + if (!pending) { 172 + console.warn(`No pending request for ${requestId}`); 173 + return; 174 + } 175 + this.pending.delete(requestId); 176 + const err = new Error(error.message); 177 + if (error.stack) { 178 + err.stack = error.stack; 179 + } 180 + pending.reject(err); 181 + } 182 + } 183 + 184 + private handleError(event: ErrorEvent): void { 185 + const errorMsg = event.message || "Unknown worker error"; 186 + console.error(`Worker error: ${errorMsg}`); 187 + 188 + for (const [, pending] of this.pending) { 189 + pending.reject(new Error(`Worker error: ${errorMsg}`)); 190 + } 191 + this.pending.clear(); 192 + } 193 + 194 + async deploy({ 195 + compiledCode, 196 + manifest, 197 + actionNames, 198 + }: { 199 + compiledCode: string; 200 + manifest: ClientManifest; 201 + actionNames: string[]; 202 + }): Promise<void> { 203 + await this.readyPromise; 204 + const requestId = randomUUID(); 205 + 206 + return new Promise((resolve, reject) => { 207 + this.pending.set(requestId, { resolve: resolve as (value: unknown) => void, reject }); 208 + this.worker.postMessage({ 209 + type: "deploy", 210 + requestId, 211 + compiledCode, 212 + manifest, 213 + actionNames, 214 + }); 215 + }); 216 + } 217 + 218 + async render(): Promise<ReadableStream<Uint8Array>> { 219 + await this.readyPromise; 220 + const requestId = randomUUID(); 221 + 222 + return new Promise((resolve, reject) => { 223 + this.pending.set(requestId, { resolve: resolve as (value: unknown) => void, reject }); 224 + this.worker.postMessage({ type: "render", requestId }); 225 + }); 226 + } 227 + 228 + async callAction( 229 + actionId: string, 230 + encodedArgs: FormData | string, 231 + ): Promise<ReadableStream<Uint8Array>> { 232 + await this.readyPromise; 233 + const requestId = randomUUID(); 234 + 235 + return new Promise((resolve, reject) => { 236 + this.pending.set(requestId, { resolve: resolve as (value: unknown) => void, reject }); 237 + this.worker.postMessage({ 238 + type: "action", 239 + requestId, 240 + actionId, 241 + encodedArgs: serializeForTransfer(encodedArgs), 242 + }); 243 + }); 244 + } 245 + 246 + async callActionRaw(actionId: string, rawPayload: string): Promise<ReadableStream<Uint8Array>> { 247 + await this.readyPromise; 248 + const requestId = randomUUID(); 249 + 250 + return new Promise((resolve, reject) => { 251 + this.pending.set(requestId, { resolve: resolve as (value: unknown) => void, reject }); 252 + this.worker.postMessage({ 253 + type: "action", 254 + requestId, 255 + actionId, 256 + encodedArgs: { type: "formdata", data: rawPayload }, 257 + }); 258 + }); 259 + } 260 + 261 + terminate(): void { 262 + this.worker.terminate(); 263 + for (const [, pending] of this.pending) { 264 + pending.reject(new Error("Worker terminated")); 265 + } 266 + this.pending.clear(); 267 + for (const [, controller] of this.streams) { 268 + controller.error(new Error("Worker terminated")); 269 + } 270 + this.streams.clear(); 271 + } 272 + }
+60 -36
src/client/ui/App.jsx src/client/ui/App.tsx
··· 1 - import React, { useState, useRef, useEffect, useMemo, version } from "react"; 2 - import { SAMPLES } from "../samples.js"; 1 + import React, { useState, useRef, useEffect, type ChangeEvent, type MouseEvent } from "react"; 2 + import { version } from "react"; 3 + import { SAMPLES, type Sample } from "../samples.ts"; 3 4 import REACT_VERSIONS from "../../../scripts/versions.json"; 4 5 5 6 const isDev = process.env.NODE_ENV === "development"; 6 7 7 - function BuildSwitcher() { 8 + function BuildSwitcher(): React.ReactElement { 8 9 const isDisabled = !import.meta.env.PROD; 9 10 10 - const handleVersionChange = (e) => { 11 + const handleVersionChange = (e: ChangeEvent<HTMLSelectElement>): void => { 11 12 const newVersion = e.target.value; 12 13 if (newVersion !== version) { 13 14 const modePath = isDev ? "/dev" : ""; ··· 15 16 } 16 17 }; 17 18 18 - const handleModeChange = (e) => { 19 + const handleModeChange = (e: ChangeEvent<HTMLSelectElement>): void => { 19 20 const newIsDev = e.target.value === "dev"; 20 21 if (newIsDev !== isDev) { 21 22 const modePath = newIsDev ? "/dev" : ""; ··· 27 28 <div className="build-switcher"> 28 29 <label>React</label> 29 30 <select value={version} onChange={handleVersionChange} disabled={isDisabled}> 30 - {REACT_VERSIONS.map((v) => ( 31 + {(REACT_VERSIONS as string[]).map((v) => ( 31 32 <option key={v} value={v}> 32 33 {v} 33 34 </option> ··· 46 47 ); 47 48 } 48 49 49 - function getInitialCode() { 50 + type InitialCode = { 51 + server: string; 52 + client: string; 53 + sampleKey: string | null; 54 + }; 55 + 56 + function getInitialCode(): InitialCode { 50 57 const params = new URLSearchParams(window.location.search); 51 58 const sampleKey = params.get("s"); 52 59 const encodedCode = params.get("c"); 53 60 54 61 if (encodedCode) { 55 62 try { 56 - const decoded = JSON.parse(decodeURIComponent(escape(atob(encodedCode)))); 63 + const decoded = JSON.parse(decodeURIComponent(escape(atob(encodedCode)))) as { 64 + server: string; 65 + client: string; 66 + }; 57 67 return { 58 68 server: decoded.server, 59 69 client: decoded.client, ··· 65 75 } 66 76 67 77 if (sampleKey && SAMPLES[sampleKey]) { 78 + const sample = SAMPLES[sampleKey] as Sample; 68 79 return { 69 - server: SAMPLES[sampleKey].server, 70 - client: SAMPLES[sampleKey].client, 80 + server: sample.server, 81 + client: sample.client, 71 82 sampleKey, 72 83 }; 73 84 } 74 85 86 + const defaultSample = SAMPLES.pagination as Sample; 75 87 return { 76 - server: SAMPLES.pagination.server, 77 - client: SAMPLES.pagination.client, 88 + server: defaultSample.server, 89 + client: defaultSample.client, 78 90 sampleKey: "pagination", 79 91 }; 80 92 } 81 93 82 - function saveToUrl(serverCode, clientCode) { 94 + function saveToUrl(serverCode: string, clientCode: string): void { 83 95 const json = JSON.stringify({ server: serverCode, client: clientCode }); 84 96 // btoa(unescape(encodeURIComponent(...))) is the standard way to base64 encode UTF-8 85 97 // Don't wrap in encodeURIComponent - searchParams.set() handles that ··· 90 102 window.history.pushState({}, "", url); 91 103 } 92 104 93 - function EmbedModal({ code, onClose }) { 94 - const textareaRef = useRef(null); 105 + type CodeState = { 106 + server: string; 107 + client: string; 108 + }; 109 + 110 + type EmbedModalProps = { 111 + code: CodeState; 112 + onClose: () => void; 113 + }; 114 + 115 + function EmbedModal({ code, onClose }: EmbedModalProps): React.ReactElement { 116 + const textareaRef = useRef<HTMLTextAreaElement>(null); 95 117 const [copied, setCopied] = useState(false); 96 118 97 119 const [embedCode] = useState(() => { ··· 110 132 \` 111 133 }); 112 134 </script>`; 113 - }, [code]); 135 + }); 114 136 115 - const handleCopy = () => { 137 + const handleCopy = (): void => { 116 138 navigator.clipboard.writeText(embedCode); 117 139 setCopied(true); 118 140 setTimeout(() => setCopied(false), 2000); ··· 120 142 121 143 return ( 122 144 <div className="modal-overlay" onClick={onClose}> 123 - <div className="modal" onClick={(e) => e.stopPropagation()}> 145 + <div className="modal" onClick={(e: MouseEvent) => e.stopPropagation()}> 124 146 <div className="modal-header"> 125 147 <h2>Embed this example</h2> 126 148 <button className="modal-close" onClick={onClose}> ··· 133 155 ref={textareaRef} 134 156 readOnly 135 157 value={embedCode} 136 - onClick={(e) => e.target.select()} 158 + onClick={(e) => (e.target as HTMLTextAreaElement).select()} 137 159 /> 138 160 </div> 139 161 <div className="modal-footer"> ··· 146 168 ); 147 169 } 148 170 149 - export function App() { 171 + export function App(): React.ReactElement { 150 172 const [initialCode] = useState(getInitialCode); 151 - const [currentSample, setCurrentSample] = useState(initialCode.sampleKey); 152 - const [workspaceCode, setWorkspaceCode] = useState({ 173 + const [currentSample, setCurrentSample] = useState<string | null>(initialCode.sampleKey); 174 + const [workspaceCode, setWorkspaceCode] = useState<CodeState>({ 153 175 server: initialCode.server, 154 176 client: initialCode.client, 155 177 }); 156 - const [liveCode, setLiveCode] = useState(workspaceCode); 178 + const [liveCode, setLiveCode] = useState<CodeState>(workspaceCode); 157 179 const [showEmbedModal, setShowEmbedModal] = useState(false); 158 - const iframeRef = useRef(null); 180 + const iframeRef = useRef<HTMLIFrameElement>(null); 159 181 160 182 useEffect(() => { 161 - const handleMessage = (event) => { 162 - if (event.data?.type === "rsc-embed:ready") { 183 + const handleMessage = (event: MessageEvent): void => { 184 + const data = event.data as { type?: string; code?: CodeState }; 185 + if (data?.type === "rsc-embed:ready") { 163 186 iframeRef.current?.contentWindow?.postMessage( 164 187 { 165 188 type: "rsc-embed:init", ··· 169 192 "*", 170 193 ); 171 194 } 172 - if (event.data?.type === "rsc-embed:code-changed") { 173 - setLiveCode(event.data.code); 195 + if (data?.type === "rsc-embed:code-changed" && data.code) { 196 + setLiveCode(data.code); 174 197 } 175 198 }; 176 199 ··· 193 216 } 194 217 }, [workspaceCode]); 195 218 196 - const handleSave = () => { 219 + const handleSave = (): void => { 197 220 saveToUrl(liveCode.server, liveCode.client); 198 221 setCurrentSample(null); 199 222 }; 200 223 201 224 const isDirty = currentSample 202 - ? liveCode.server !== SAMPLES[currentSample].server || 203 - liveCode.client !== SAMPLES[currentSample].client 225 + ? liveCode.server !== (SAMPLES[currentSample] as Sample).server || 226 + liveCode.client !== (SAMPLES[currentSample] as Sample).client 204 227 : liveCode.server !== initialCode.server || liveCode.client !== initialCode.client; 205 228 206 - const handleSampleChange = (e) => { 229 + const handleSampleChange = (e: ChangeEvent<HTMLSelectElement>): void => { 207 230 const key = e.target.value; 208 231 if (key && SAMPLES[key]) { 209 - const newCode = { 210 - server: SAMPLES[key].server, 211 - client: SAMPLES[key].client, 232 + const sample = SAMPLES[key] as Sample; 233 + const newCode: CodeState = { 234 + server: sample.server, 235 + client: sample.client, 212 236 }; 213 237 setWorkspaceCode(newCode); 214 238 setCurrentSample(key); ··· 225 249 <h1>RSC Explorer</h1> 226 250 <div className="example-select-wrapper"> 227 251 <label>Example</label> 228 - <select value={currentSample || ""} onChange={handleSampleChange}> 252 + <select value={currentSample ?? ""} onChange={handleSampleChange}> 229 253 {!currentSample && <option value="">Custom</option>} 230 254 {Object.entries(SAMPLES).map(([key, sample]) => ( 231 255 <option key={key} value={key}>
+10 -5
src/client/ui/CodeEditor.jsx src/client/ui/CodeEditor.tsx
··· 3 3 import { javascript } from "@codemirror/lang-javascript"; 4 4 import { syntaxHighlighting, HighlightStyle } from "@codemirror/language"; 5 5 import { tags } from "@lezer/highlight"; 6 - import { history, historyKeymap } from "@codemirror/commands"; 6 + import { history, historyKeymap, defaultKeymap } from "@codemirror/commands"; 7 7 import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete"; 8 - import { defaultKeymap } from "@codemirror/commands"; 9 8 10 9 const highlightStyle = HighlightStyle.define([ 11 10 { tag: tags.keyword, color: "#c678dd" }, ··· 40 39 { dark: true }, 41 40 ); 42 41 43 - export function CodeEditor({ defaultValue, onChange, label }) { 42 + type CodeEditorProps = { 43 + defaultValue: string; 44 + onChange: (code: string) => void; 45 + label: string; 46 + }; 47 + 48 + export function CodeEditor({ defaultValue, onChange, label }: CodeEditorProps): React.ReactElement { 44 49 const [initialDefaultValue] = useState(defaultValue); 45 - const containerRef = useRef(null); 50 + const containerRef = useRef<HTMLDivElement>(null); 46 51 47 - const onEditorChange = useEffectEvent((doc) => { 52 + const onEditorChange = useEffectEvent((doc: string) => { 48 53 onChange(doc); 49 54 }); 50 55
+53 -19
src/client/ui/FlightLog.jsx src/client/ui/FlightLog.tsx
··· 1 1 import React, { useState, useRef, useEffect } from "react"; 2 - import { FlightTreeView } from "./TreeView.jsx"; 2 + import { FlightTreeView } from "./TreeView.tsx"; 3 + import type { Timeline, TimelineEntry, Thenable } from "../runtime/index.ts"; 3 4 4 - function escapeHtml(str) { 5 + function escapeHtml(str: string): string { 5 6 return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); 6 7 } 7 8 8 - function RenderLogView({ lines, chunkStart, cursor, flightPromise }) { 9 - const activeRef = useRef(null); 9 + type RenderLogViewProps = { 10 + lines: string[]; 11 + chunkStart: number; 12 + cursor: number; 13 + flightPromise: Thenable<unknown> | undefined; 14 + }; 15 + 16 + function RenderLogView({ 17 + lines, 18 + chunkStart, 19 + cursor, 20 + flightPromise, 21 + }: RenderLogViewProps): React.ReactElement | null { 22 + const activeRef = useRef<HTMLSpanElement>(null); 10 23 const nextLineIndex = 11 24 cursor >= chunkStart && cursor < chunkStart + lines.length ? cursor - chunkStart : -1; 12 25 ··· 18 31 19 32 if (lines.length === 0) return null; 20 33 21 - const getLineClass = (i) => { 34 + const getLineClass = (i: number): string => { 22 35 const globalChunk = chunkStart + i; 23 36 if (globalChunk < cursor) return "line-done"; 24 37 if (globalChunk === cursor) return "line-next"; ··· 44 57 </pre> 45 58 </div> 46 59 <div className="log-entry-tree"> 47 - {showTree && <FlightTreeView flightPromise={flightPromise} />} 60 + {showTree && <FlightTreeView flightPromise={flightPromise ?? null} />} 48 61 </div> 49 62 </div> 50 63 </div> 51 64 ); 52 65 } 53 66 67 + type FlightLogEntryProps = { 68 + entry: TimelineEntry; 69 + entryIndex: number; 70 + chunkStart: number; 71 + cursor: number; 72 + canDelete: boolean; 73 + onDelete: (index: number) => void; 74 + getChunkCount: (entry: TimelineEntry) => number; 75 + }; 76 + 54 77 function FlightLogEntry({ 55 78 entry, 56 79 entryIndex, ··· 59 82 canDelete, 60 83 onDelete, 61 84 getChunkCount, 62 - }) { 85 + }: FlightLogEntryProps): React.ReactElement | null { 63 86 const chunkCount = getChunkCount(entry); 64 87 const entryEnd = chunkStart + chunkCount; 65 88 const isEntryActive = cursor >= chunkStart && cursor < entryEnd; ··· 68 91 const entryClass = isEntryActive ? "active" : isEntryDone ? "done-entry" : "pending-entry"; 69 92 70 93 if (entry.type === "render") { 71 - const lines = entry.stream?.rows || []; 94 + const lines = entry.stream.rows; 72 95 return ( 73 96 <div className={`log-entry ${entryClass}`}> 74 97 <div className="log-entry-header"> ··· 89 112 lines={lines} 90 113 chunkStart={chunkStart} 91 114 cursor={cursor} 92 - flightPromise={entry.stream?.flightPromise} 115 + flightPromise={entry.stream.flightPromise} 93 116 /> 94 117 </div> 95 118 ); 96 119 } 97 120 98 121 if (entry.type === "action") { 99 - const responseLines = entry.stream?.rows || []; 122 + const responseLines = entry.stream.rows; 100 123 101 124 return ( 102 125 <div className={`log-entry ${entryClass}`}> ··· 123 146 lines={responseLines} 124 147 chunkStart={chunkStart} 125 148 cursor={cursor} 126 - flightPromise={entry.stream?.flightPromise} 149 + flightPromise={entry.stream.flightPromise} 127 150 /> 128 151 </div> 129 152 ); ··· 132 155 return null; 133 156 } 134 157 158 + type FlightLogProps = { 159 + timeline: Timeline; 160 + entries: TimelineEntry[]; 161 + cursor: number; 162 + error: string | null; 163 + availableActions: string[]; 164 + onAddRawAction: (actionName: string, rawPayload: string) => void; 165 + onDeleteEntry: (index: number) => void; 166 + }; 167 + 135 168 export function FlightLog({ 136 169 timeline, 137 170 entries, ··· 140 173 availableActions, 141 174 onAddRawAction, 142 175 onDeleteEntry, 143 - }) { 144 - const logRef = useRef(null); 176 + }: FlightLogProps): React.ReactElement { 177 + const logRef = useRef<HTMLDivElement>(null); 145 178 const [showRawInput, setShowRawInput] = useState(false); 146 179 const [selectedAction, setSelectedAction] = useState(""); 147 180 const [rawPayload, setRawPayload] = useState(""); 148 181 149 - const handleAddRaw = () => { 182 + const handleAddRaw = (): void => { 150 183 if (rawPayload.trim()) { 151 184 onAddRawAction(selectedAction, rawPayload); 152 - setSelectedAction(availableActions[0] || ""); 185 + setSelectedAction(availableActions[0] ?? ""); 153 186 setRawPayload(""); 154 187 setShowRawInput(false); 155 188 } 156 189 }; 157 190 158 - const handleShowRawInput = () => { 159 - setSelectedAction(availableActions[0] || ""); 191 + const handleShowRawInput = (): void => { 192 + setSelectedAction(availableActions[0] ?? ""); 160 193 setShowRawInput(true); 161 194 }; 162 195 ··· 172 205 ); 173 206 } 174 207 175 - const getChunkCount = (entry) => timeline.getChunkCount(entry); 208 + const getChunkCount = (entry: TimelineEntry): number => timeline.getChunkCount(entry); 176 209 177 - const entryElements = []; 210 + const entryElements: React.ReactElement[] = []; 178 211 let chunkOffset = 0; 179 212 for (let i = 0; i < entries.length; i++) { 180 213 const entry = entries[i]; 214 + if (!entry) continue; 181 215 const chunkStart = chunkOffset; 182 216 chunkOffset += getChunkCount(entry); 183 217 entryElements.push(
+45 -13
src/client/ui/LivePreview.jsx src/client/ui/LivePreview.tsx
··· 1 - import React, { Suspense, Component, useState, useEffect, useSyncExternalStore } from "react"; 1 + import React, { 2 + Suspense, 3 + Component, 4 + useState, 5 + useEffect, 6 + useSyncExternalStore, 7 + type ReactNode, 8 + } from "react"; 9 + import type { Timeline, Thenable } from "../runtime/index.ts"; 10 + 11 + type PreviewErrorBoundaryProps = { 12 + children: ReactNode; 13 + }; 14 + 15 + type PreviewErrorBoundaryState = { 16 + error: Error | null; 17 + }; 2 18 3 - class PreviewErrorBoundary extends Component { 4 - constructor(props) { 19 + class PreviewErrorBoundary extends Component<PreviewErrorBoundaryProps, PreviewErrorBoundaryState> { 20 + constructor(props: PreviewErrorBoundaryProps) { 5 21 super(props); 6 22 this.state = { error: null }; 7 23 } 8 24 9 - static getDerivedStateFromError(error) { 25 + static getDerivedStateFromError(error: Error): PreviewErrorBoundaryState { 10 26 return { error }; 11 27 } 12 28 13 - render() { 29 + render(): ReactNode { 14 30 if (this.state.error) { 15 31 return ( 16 32 <span className="empty error"> ··· 22 38 } 23 39 } 24 40 25 - function StreamingContent({ streamPromise }) { 26 - return React.use(streamPromise); 41 + type StreamingContentProps = { 42 + streamPromise: Thenable<unknown>; 43 + }; 44 + 45 + function StreamingContent({ streamPromise }: StreamingContentProps): ReactNode { 46 + return React.use(streamPromise) as ReactNode; 27 47 } 28 48 49 + type LivePreviewProps = { 50 + timeline: Timeline; 51 + clientModuleReady: boolean; 52 + totalChunks: number; 53 + cursor: number; 54 + isAtStart: boolean; 55 + isAtEnd: boolean; 56 + onStep: () => void; 57 + onSkip: () => void; 58 + onReset: () => void; 59 + }; 60 + 29 61 export function LivePreview({ 30 62 timeline, 31 63 clientModuleReady, ··· 36 68 onStep, 37 69 onSkip, 38 70 onReset, 39 - }) { 71 + }: LivePreviewProps): React.ReactElement { 40 72 const snapshot = useSyncExternalStore(timeline.subscribe, timeline.getSnapshot); 41 73 const { entries } = snapshot; 42 74 const renderEntry = entries[0]; 43 - const flightPromise = renderEntry?.stream?.flightPromise; 75 + const flightPromise = renderEntry?.stream.flightPromise; 44 76 45 77 const [isPlaying, setIsPlaying] = useState(false); 46 78 ··· 61 93 62 94 const showPlaceholder = !clientModuleReady || cursor === 0; 63 95 64 - const handlePlayPause = () => setIsPlaying(!isPlaying); 65 - const handleStep = () => { 96 + const handlePlayPause = (): void => setIsPlaying(!isPlaying); 97 + const handleStep = (): void => { 66 98 setIsPlaying(false); 67 99 onStep(); 68 100 }; 69 - const handleSkip = () => { 101 + const handleSkip = (): void => { 70 102 setIsPlaying(false); 71 103 onSkip(); 72 104 }; 73 - const handleReset = () => { 105 + const handleReset = (): void => { 74 106 setIsPlaying(false); 75 107 onReset(); 76 108 };
+133 -39
src/client/ui/TreeView.jsx src/client/ui/TreeView.tsx
··· 1 - import React, { Suspense, Component } from "react"; 1 + import React, { Suspense, Component, type ReactNode } from "react"; 2 + import type { Thenable } from "../runtime/index.ts"; 2 3 3 - function isReactElement(value) { 4 + // Internal React element type with $$typeof 5 + type ReactElementInternal = { 6 + $$typeof: symbol; 7 + type: unknown; 8 + key: string | null; 9 + ref: unknown; 10 + props: Record<string, unknown>; 11 + }; 12 + 13 + // Lazy element type 14 + type ReactLazy = { 15 + $$typeof: symbol; 16 + _payload: unknown; 17 + _init: (payload: unknown) => unknown; 18 + }; 19 + 20 + function isReactElement(value: unknown): value is ReactElementInternal { 4 21 if (!value || typeof value !== "object") return false; 5 - const typeofSymbol = value.$$typeof; 22 + const typeofSymbol = (value as { $$typeof?: symbol }).$$typeof; 6 23 return typeofSymbol === Symbol.for("react.transitional.element"); 7 24 } 8 25 9 - function escapeHtml(str) { 26 + function escapeHtml(str: string): string { 10 27 return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); 11 28 } 12 29 13 - function PendingFallback() { 30 + function PendingFallback(): React.ReactElement { 14 31 return <span className="tree-pending">Pending</span>; 15 32 } 16 33 17 - function ErrorFallback({ error }) { 18 - const message = error?.message || String(error); 34 + type ErrorFallbackProps = { 35 + error: unknown; 36 + }; 37 + 38 + function ErrorFallback({ error }: ErrorFallbackProps): React.ReactElement { 39 + const message = error instanceof Error ? error.message : String(error); 19 40 return <span className="tree-error">Error: {message}</span>; 20 41 } 21 42 22 - class ErrorBoundary extends Component { 23 - constructor(props) { 43 + type ErrorBoundaryProps = { 44 + children: ReactNode; 45 + }; 46 + 47 + type ErrorBoundaryState = { 48 + error: Error | null; 49 + }; 50 + 51 + class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { 52 + constructor(props: ErrorBoundaryProps) { 24 53 super(props); 25 54 this.state = { error: null }; 26 55 } 27 56 28 - static getDerivedStateFromError(error) { 57 + static getDerivedStateFromError(error: Error): ErrorBoundaryState { 29 58 return { error }; 30 59 } 31 60 32 - render() { 61 + render(): ReactNode { 33 62 if (this.state.error) { 34 63 return <ErrorFallback error={this.state.error} />; 35 64 } ··· 37 66 } 38 67 } 39 68 40 - function Await({ promise, children }) { 69 + type AwaitProps<T> = { 70 + promise: Thenable<T>; 71 + children: (value: T) => ReactNode; 72 + }; 73 + 74 + function Await<T>({ promise, children }: AwaitProps<T>): ReactNode { 41 75 const value = React.use(promise); 42 76 return children(value); 43 77 } 44 78 45 - function LazyValue({ value, indent, ancestors = [] }) { 79 + type LazyValueProps = { 80 + value: ReactLazy; 81 + indent: number; 82 + ancestors?: unknown[]; 83 + }; 84 + 85 + function LazyValue({ value, indent, ancestors = [] }: LazyValueProps): React.ReactElement { 46 86 const resolved = value._init(value._payload); 47 87 return <JSXValue value={resolved} indent={indent} ancestors={ancestors} />; 48 88 } 49 89 50 - function LazyType({ element, indent, ancestors = [] }) { 90 + type LazyTypeProps = { 91 + element: ReactElementInternal & { type: ReactLazy }; 92 + indent: number; 93 + ancestors?: unknown[]; 94 + }; 95 + 96 + function LazyType({ element, indent, ancestors = [] }: LazyTypeProps): React.ReactElement { 51 97 const resolvedType = element.type._init(element.type._payload); 52 98 const newElement = { ...element, type: resolvedType }; 53 - return <JSXElement element={newElement} indent={indent} ancestors={ancestors} />; 99 + return ( 100 + <JSXElement 101 + element={newElement as ReactElementInternal} 102 + indent={indent} 103 + ancestors={ancestors} 104 + /> 105 + ); 54 106 } 55 107 108 + type JSXValueProps = { 109 + value: unknown; 110 + indent?: number; 111 + ancestors?: unknown[]; 112 + }; 113 + 56 114 // `ancestors` tracks the current path for cycle detection 57 - function JSXValue({ value, indent = 0, ancestors = [] }) { 115 + function JSXValue({ value, indent = 0, ancestors = [] }: JSXValueProps): React.ReactElement { 58 116 if (value === null) return <span className="tree-null">null</span>; 59 117 if (value === undefined) return <span className="tree-undefined">undefined</span>; 60 118 ··· 74 132 return <span className="tree-symbol">{value.toString()}</span>; 75 133 } 76 134 if (typeof value === "function") { 77 - return <span className="tree-function">[Function: {value.name || "anonymous"}]</span>; 135 + return ( 136 + <span className="tree-function"> 137 + [Function: {(value as { name?: string }).name || "anonymous"}] 138 + </span> 139 + ); 78 140 } 79 141 80 142 if (typeof value === "object" && value !== null) { ··· 170 232 171 233 if (ArrayBuffer.isView(value)) { 172 234 const name = value.constructor.name; 173 - const preview = Array.from(value.slice(0, 5)).join(", "); 174 - const suffix = value.length > 5 ? ", ..." : ""; 235 + const arr = value as Uint8Array; 236 + const preview = Array.from(arr.slice(0, 5)).join(", "); 237 + const suffix = arr.length > 5 ? ", ..." : ""; 175 238 return ( 176 239 <span className="tree-collection"> 177 - {name}({value.length}) [{preview} 240 + {name}({arr.length}) [{preview} 178 241 {suffix}] 179 242 </span> 180 243 ); ··· 187 250 if (value.length === 0) return <>[]</>; 188 251 const hasElements = value.some((v, i) => i in value && isReactElement(v)); 189 252 190 - const renderItem = (i) => { 253 + const renderItem = (i: number): React.ReactElement => { 191 254 if (!(i in value)) { 192 255 return <span className="tree-empty">empty</span>; 193 256 } ··· 231 294 } 232 295 233 296 if (typeof value === "object") { 234 - if (typeof value.next === "function" && typeof value[Symbol.iterator] === "function") { 297 + const obj = value as Record<string | symbol, unknown>; 298 + 299 + if (typeof obj.next === "function" && typeof obj[Symbol.iterator] === "function") { 235 300 return <span className="tree-iterator">Iterator {"{}"}</span>; 236 301 } 237 302 238 - if (typeof value[Symbol.asyncIterator] === "function") { 303 + if (typeof obj[Symbol.asyncIterator] === "function") { 239 304 return <span className="tree-iterator">AsyncIterator {"{}"}</span>; 240 305 } 241 306 ··· 243 308 return <span className="tree-stream">ReadableStream {"{}"}</span>; 244 309 } 245 310 246 - if (typeof value.then === "function") { 311 + if (typeof obj.then === "function") { 247 312 return ( 248 313 <ErrorBoundary> 249 314 <Suspense fallback={<PendingFallback />}> 250 - <Await promise={value}> 315 + <Await promise={value as Thenable<unknown>}> 251 316 {(resolved) => ( 252 317 <JSXValue value={resolved} indent={indent} ancestors={nextAncestors} /> 253 318 )} ··· 257 322 ); 258 323 } 259 324 260 - if (value.$$typeof === Symbol.for("react.lazy")) { 325 + if (obj.$$typeof === Symbol.for("react.lazy")) { 261 326 return ( 262 327 <ErrorBoundary> 263 328 <Suspense fallback={<PendingFallback />}> 264 - <LazyValue value={value} indent={indent} ancestors={nextAncestors} /> 329 + <LazyValue value={value as ReactLazy} indent={indent} ancestors={nextAncestors} /> 265 330 </Suspense> 266 331 </ErrorBoundary> 267 332 ); 268 333 } 269 334 270 - const entries = Object.entries(value); 335 + const entries = Object.entries(obj); 271 336 if (entries.length === 0) return <>{"{}"}</>; 272 337 if (entries.length <= 2 && entries.every(([, v]) => typeof v !== "object" || v === null)) { 273 338 return ( ··· 307 372 return <span className="tree-unknown">{String(value)}</span>; 308 373 } 309 374 310 - function JSXElement({ element, indent, ancestors = [] }) { 375 + type JSXElementProps = { 376 + element: ReactElementInternal; 377 + indent: number; 378 + ancestors?: unknown[]; 379 + }; 380 + 381 + function JSXElement({ element, indent, ancestors = [] }: JSXElementProps): React.ReactElement { 311 382 const { type, props, key } = element; 312 383 const pad = " ".repeat(indent); 313 384 const padInner = " ".repeat(indent + 1); 314 385 315 - let tagName, 316 - tagClass = "tree-tag"; 386 + let tagName: string; 387 + let tagClass = "tree-tag"; 317 388 if (typeof type === "string") { 318 389 tagName = type; 319 390 } else if (typeof type === "function") { 320 - tagName = type.displayName || type.name || "Component"; 391 + const funcType = type as { displayName?: string; name?: string }; 392 + tagName = funcType.displayName || funcType.name || "Component"; 321 393 tagClass = "tree-client-tag"; 322 394 } else if (typeof type === "symbol") { 323 395 switch (type) { ··· 346 418 tagName = "Unknown"; 347 419 } 348 420 tagClass = "tree-react-tag"; 349 - } else if (type && typeof type === "object" && type.$$typeof) { 350 - if (type.$$typeof === Symbol.for("react.lazy")) { 421 + } else if (type && typeof type === "object" && (type as { $$typeof?: symbol }).$$typeof) { 422 + const lazyType = type as ReactLazy; 423 + if (lazyType.$$typeof === Symbol.for("react.lazy")) { 351 424 return ( 352 425 <ErrorBoundary> 353 426 <Suspense fallback={<PendingFallback />}> 354 - <LazyType element={element} indent={indent} ancestors={ancestors} /> 427 + <LazyType 428 + element={element as ReactElementInternal & { type: ReactLazy }} 429 + indent={indent} 430 + ancestors={ancestors} 431 + /> 355 432 </Suspense> 356 433 </ErrorBoundary> 357 434 ); ··· 362 439 tagName = "Unknown"; 363 440 } 364 441 365 - const { children, ...otherProps } = props || {}; 442 + const { children, ...otherProps } = props; 366 443 const propEntries = Object.entries(otherProps).filter( 367 444 ([k]) => !["key", "ref", "__self", "__source"].includes(k), 368 445 ); ··· 419 496 ); 420 497 } 421 498 422 - function JSXProp({ name, value, indent, ancestors = [] }) { 499 + type JSXPropProps = { 500 + name: string; 501 + value: unknown; 502 + indent: number; 503 + ancestors?: unknown[]; 504 + }; 505 + 506 + function JSXProp({ name, value, indent, ancestors = [] }: JSXPropProps): React.ReactElement { 423 507 if (typeof value === "string") { 424 508 return ( 425 509 <> ··· 476 560 ); 477 561 } 478 562 479 - function JSXChildren({ value, indent, ancestors = [] }) { 563 + type JSXChildrenProps = { 564 + value: unknown; 565 + indent: number; 566 + ancestors?: unknown[]; 567 + }; 568 + 569 + function JSXChildren({ value, indent, ancestors = [] }: JSXChildrenProps): React.ReactElement { 480 570 if (typeof value === "string") return <>{escapeHtml(value)}</>; 481 571 if (typeof value === "number") return <>{"{" + value + "}"}</>; 482 572 if (Array.isArray(value)) { ··· 507 597 return <JSXValue value={value} indent={indent} ancestors={ancestors} />; 508 598 } 509 599 510 - export function FlightTreeView({ flightPromise }) { 600 + type FlightTreeViewProps = { 601 + flightPromise: Thenable<unknown> | null; 602 + }; 603 + 604 + export function FlightTreeView({ flightPromise }: FlightTreeViewProps): React.ReactElement { 511 605 if (!flightPromise) { 512 606 return ( 513 607 <div className="flight-tree">
+47 -28
src/client/ui/Workspace.jsx src/client/ui/Workspace.tsx
··· 5 5 SteppableStream, 6 6 registerClientModule, 7 7 evaluateClientModule, 8 - } from "../runtime/index.js"; 9 - import { ServerWorker } from "../server-worker.js"; 8 + type CallServerCallback, 9 + } from "../runtime/index.ts"; 10 + import { ServerWorker } from "../server-worker.ts"; 10 11 import { 11 12 parseClientModule, 12 13 parseServerActions, 13 14 compileToCommonJS, 14 15 buildManifest, 15 - } from "../../shared/compiler.js"; 16 - import { CodeEditor } from "./CodeEditor.jsx"; 17 - import { FlightLog } from "./FlightLog.jsx"; 18 - import { LivePreview } from "./LivePreview.jsx"; 16 + } from "../../shared/compiler.ts"; 17 + import { CodeEditor } from "./CodeEditor.tsx"; 18 + import { FlightLog } from "./FlightLog.tsx"; 19 + import { LivePreview } from "./LivePreview.tsx"; 19 20 20 - export function Workspace({ initialServerCode, initialClientCode, onCodeChange }) { 21 + type WorkspaceProps = { 22 + initialServerCode: string; 23 + initialClientCode: string; 24 + onCodeChange?: (server: string, client: string) => void; 25 + }; 26 + 27 + type CallServerRef = { 28 + current: ((actionId: string, args: unknown[]) => Promise<unknown>) | null; 29 + }; 30 + 31 + export function Workspace({ 32 + initialServerCode, 33 + initialClientCode, 34 + onCodeChange, 35 + }: WorkspaceProps): React.ReactElement { 21 36 const [serverCode, setServerCode] = useState(initialServerCode); 22 37 const [clientCode, setClientCode] = useState(initialClientCode); 23 38 const [serverWorker] = useState(() => new ServerWorker()); 24 39 const [timeline] = useState(() => new Timeline()); 25 - const [callServerRef] = useState({ current: null }); 40 + const [callServerRef] = useState<CallServerRef>({ current: null }); 26 41 27 42 const snapshot = useSyncExternalStore(timeline.subscribe, timeline.getSnapshot); 28 43 const { entries, cursor, totalChunks, isAtStart, isAtEnd } = snapshot; 29 44 30 45 const [clientModuleReady, setClientModuleReady] = useState(false); 31 - const [error, setError] = useState(null); 32 - const [availableActions, setAvailableActions] = useState([]); 33 - const compileTimeoutRef = useRef(null); 46 + const [error, setError] = useState<string | null>(null); 47 + const [availableActions, setAvailableActions] = useState<string[]>([]); 48 + const compileTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); 34 49 35 - useEffect(() => { 36 - window.__DEBUG_TIMELINE__ = timeline; 37 - }, [timeline]); 38 - 39 - const handleServerChange = (code) => { 50 + const handleServerChange = (code: string): void => { 40 51 setServerCode(code); 41 52 onCodeChange?.(code, clientCode); 42 53 }; 43 54 44 - const handleClientChange = (code) => { 55 + const handleClientChange = (code: string): void => { 45 56 setClientCode(code); 46 57 onCodeChange?.(serverCode, code); 47 58 }; ··· 55 66 }, [timeline]); 56 67 57 68 const handleAddRawAction = useCallback( 58 - async (actionName, rawPayload) => { 69 + async (actionName: string, rawPayload: string) => { 59 70 try { 60 71 const responseRaw = await serverWorker.callActionRaw(actionName, rawPayload); 61 - const stream = new SteppableStream(responseRaw, { callServer: callServerRef.current }); 72 + const streamOptions = callServerRef.current ? { callServer: callServerRef.current } : {}; 73 + const stream = new SteppableStream(responseRaw, streamOptions); 62 74 await stream.waitForBuffer(); 63 75 timeline.addAction(actionName, rawPayload, stream); 64 76 } catch (err) { ··· 69 81 ); 70 82 71 83 const compile = useCallback( 72 - async (sCode, cCode) => { 84 + async (sCode: string, cCode: string) => { 73 85 try { 74 86 setError(null); 75 87 timeline.clear(); ··· 90 102 actionNames, 91 103 }); 92 104 93 - const callServer = 105 + const callServer: CallServerCallback | null = 94 106 actionNames.length > 0 95 - ? async (actionId, args) => { 96 - const actionName = actionId.split("#")[0]; 107 + ? async (actionId: string, args: unknown[]): Promise<unknown> => { 108 + const actionName = actionId.split("#")[0] ?? actionId; 97 109 const encodedArgs = await encodeReply(args); 98 110 const argsDisplay = 99 111 typeof encodedArgs === "string" 100 112 ? `0=${encodedArgs}` 101 - : new URLSearchParams(encodedArgs).toString(); 113 + : new URLSearchParams( 114 + encodedArgs as unknown as Record<string, string>, 115 + ).toString(); 102 116 103 117 const responseRaw = await serverWorker.callAction(actionName, encodedArgs); 104 - const stream = new SteppableStream(responseRaw, { callServer }); 118 + const stream = new SteppableStream(responseRaw, { 119 + callServer: callServer as CallServerCallback, 120 + }); 105 121 await stream.waitForBuffer(); 106 122 timeline.addAction(actionName, argsDisplay, stream); 107 123 return stream.flightPromise; ··· 111 127 callServerRef.current = callServer; 112 128 113 129 const renderRaw = await serverWorker.render(); 114 - const renderStream = new SteppableStream(renderRaw, { callServer }); 130 + const renderStreamOptions = callServer ? { callServer } : {}; 131 + const renderStream = new SteppableStream(renderRaw, renderStreamOptions); 115 132 await renderStream.waitForBuffer(); 116 133 117 134 timeline.setRender(renderStream); 118 135 setClientModuleReady(true); 119 136 } catch (err) { 120 137 console.error("[compile] Error:", err); 121 - setError(err.message || String(err)); 138 + setError(err instanceof Error ? err.message : String(err)); 122 139 timeline.clear(); 123 140 setClientModuleReady(false); 124 141 } ··· 131 148 }, [compile, serverCode, clientCode]); 132 149 133 150 useEffect(() => { 134 - clearTimeout(compileTimeoutRef.current); 151 + if (compileTimeoutRef.current) { 152 + clearTimeout(compileTimeoutRef.current); 153 + } 135 154 compileTimeoutRef.current = setTimeout(() => { 136 155 compile(serverCode, clientCode); 137 156 }, 300);
-50
src/client/webpack-shim.js
··· 1 - const moduleCache = {}; 2 - const moduleFactories = {}; 3 - 4 - window.__webpack_module_cache__ = moduleCache; 5 - window.__webpack_modules__ = moduleFactories; 6 - 7 - window.__webpack_require__ = function (moduleId) { 8 - if (moduleCache[moduleId]) { 9 - return moduleCache[moduleId].exports || moduleCache[moduleId]; 10 - } 11 - if (moduleFactories[moduleId]) { 12 - const module = { exports: {} }; 13 - moduleFactories[moduleId](module); 14 - moduleCache[moduleId] = module; 15 - return module.exports; 16 - } 17 - throw new Error(`Module ${moduleId} not found in webpack shim`); 18 - }; 19 - 20 - window.__webpack_require__.m = moduleFactories; 21 - window.__webpack_require__.c = moduleCache; 22 - window.__webpack_require__.d = function (exports, definition) { 23 - for (const key in definition) { 24 - if ( 25 - Object.prototype.hasOwnProperty.call(definition, key) && 26 - !Object.prototype.hasOwnProperty.call(exports, key) 27 - ) { 28 - Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 29 - } 30 - } 31 - }; 32 - window.__webpack_require__.r = function (exports) { 33 - if (typeof Symbol !== "undefined" && Symbol.toStringTag) { 34 - Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); 35 - } 36 - Object.defineProperty(exports, "__esModule", { value: true }); 37 - }; 38 - window.__webpack_require__.o = function (obj, prop) { 39 - return Object.prototype.hasOwnProperty.call(obj, prop); 40 - }; 41 - 42 - window.__webpack_chunk_load__ = function (chunkId) { 43 - return Promise.resolve(); 44 - }; 45 - 46 - window.__webpack_require__.e = function (chunkId) { 47 - return Promise.resolve(); 48 - }; 49 - 50 - window.__webpack_require__.p = "/";
+76
src/client/webpack-shim.ts
··· 1 + // Shim webpack globals for react-server-dom-webpack/client in browser context 2 + 3 + type ModuleFactory = { 4 + (module: { exports: unknown }): void; 5 + }; 6 + 7 + type WebpackRequire = { 8 + (moduleId: string): unknown; 9 + m: Record<string, ModuleFactory>; 10 + c: Record<string, { exports: unknown } | unknown>; 11 + d: (exports: object, definition: Record<string, () => unknown>) => void; 12 + r: (exports: object) => void; 13 + o: (obj: object, prop: string) => boolean; 14 + e: (chunkId: string) => Promise<void>; 15 + p: string; 16 + }; 17 + 18 + const clientModuleCache: Record<string, { exports: unknown }> = {}; 19 + const clientModuleFactories: Record<string, ModuleFactory> = {}; 20 + 21 + window.__webpack_module_cache__ = clientModuleCache; 22 + window.__webpack_modules__ = clientModuleFactories; 23 + 24 + const clientWebpackRequire: WebpackRequire = function (moduleId: string): unknown { 25 + const cached = clientModuleCache[moduleId]; 26 + if (cached) { 27 + return cached.exports ?? cached; 28 + } 29 + const factory = clientModuleFactories[moduleId]; 30 + if (factory) { 31 + const module: { exports: unknown } = { exports: {} }; 32 + factory(module); 33 + clientModuleCache[moduleId] = module; 34 + return module.exports; 35 + } 36 + throw new Error(`Module ${moduleId} not found in webpack shim`); 37 + } as WebpackRequire; 38 + 39 + clientWebpackRequire.m = clientModuleFactories; 40 + clientWebpackRequire.c = clientModuleCache; 41 + clientWebpackRequire.d = function ( 42 + exports: object, 43 + definition: Record<string, () => unknown>, 44 + ): void { 45 + for (const key in definition) { 46 + const getter = definition[key]; 47 + if ( 48 + getter && 49 + Object.prototype.hasOwnProperty.call(definition, key) && 50 + !Object.prototype.hasOwnProperty.call(exports, key) 51 + ) { 52 + Object.defineProperty(exports, key, { enumerable: true, get: getter }); 53 + } 54 + } 55 + }; 56 + clientWebpackRequire.r = function (exports: object): void { 57 + if (typeof Symbol !== "undefined" && Symbol.toStringTag) { 58 + Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); 59 + } 60 + Object.defineProperty(exports, "__esModule", { value: true }); 61 + }; 62 + clientWebpackRequire.o = function (obj: object, prop: string): boolean { 63 + return Object.prototype.hasOwnProperty.call(obj, prop); 64 + }; 65 + clientWebpackRequire.e = function (_chunkId: string): Promise<void> { 66 + return Promise.resolve(); 67 + }; 68 + clientWebpackRequire.p = "/"; 69 + 70 + window.__webpack_require__ = clientWebpackRequire; 71 + 72 + window.__webpack_chunk_load__ = function (_chunkId: string): Promise<void> { 73 + return Promise.resolve(); 74 + }; 75 + 76 + export {};
-82
src/embed.js
··· 1 - /** 2 - * RSC Explorer Embed API 3 - * 4 - * Usage: 5 - * ```html 6 - * <div id="demo" style="height: 500px;"></div> 7 - * <script type="module"> 8 - * import { mount } from 'https://rscexplorer.dev/embed.js'; 9 - * 10 - * mount('#demo', { 11 - * server: ` 12 - * export default function App() { 13 - * return <h1>Hello RSC</h1>; 14 - * } 15 - * `, 16 - * client: ` 17 - * 'use client' 18 - * export function Button() { 19 - * return <button>Click</button>; 20 - * } 21 - * ` 22 - * }); 23 - * </script> 24 - * ``` 25 - */ 26 - 27 - // Get the embed URL relative to this script's location 28 - const getEmbedUrl = () => { 29 - return new URL("embed.html", import.meta.url).href; 30 - }; 31 - 32 - /** 33 - * Mount an RSC Explorer embed into a container element 34 - * @param {string|HTMLElement} container - CSS selector or DOM element 35 - * @param {Object} options - Configuration options 36 - * @param {string} options.server - Server component code 37 - * @param {string} options.client - Client component code 38 - * @returns {Object} - Control object with methods to interact with the embed 39 - */ 40 - export function mount(container, { server, client }) { 41 - const el = typeof container === "string" ? document.querySelector(container) : container; 42 - 43 - if (!el) { 44 - throw new Error(`RSC Explorer: Container not found: ${container}`); 45 - } 46 - 47 - // Create iframe 48 - const iframe = document.createElement("iframe"); 49 - iframe.src = getEmbedUrl(); 50 - iframe.style.cssText = 51 - "width: 100%; height: 100%; border: 1px solid #e0e0e0; border-radius: 8px;"; 52 - 53 - // Wait for iframe to be ready, then send code 54 - const handleMessage = (event) => { 55 - if (event.source !== iframe.contentWindow) return; 56 - 57 - if (event.data?.type === "rsc-embed:ready") { 58 - iframe.contentWindow.postMessage( 59 - { 60 - type: "rsc-embed:init", 61 - code: { server: server.trim(), client: client.trim() }, 62 - }, 63 - "*", 64 - ); 65 - } 66 - }; 67 - 68 - window.addEventListener("message", handleMessage); 69 - 70 - // Clear container and add iframe 71 - el.innerHTML = ""; 72 - el.appendChild(iframe); 73 - 74 - // Return control object 75 - return { 76 - iframe, 77 - destroy: () => { 78 - window.removeEventListener("message", handleMessage); 79 - el.innerHTML = ""; 80 - }, 81 - }; 82 - }
+107
src/embed.ts
··· 1 + /** 2 + * RSC Explorer Embed API 3 + * 4 + * Usage: 5 + * ```html 6 + * <div id="demo" style="height: 500px;"></div> 7 + * <script type="module"> 8 + * import { mount } from 'https://rscexplorer.dev/embed.js'; 9 + * 10 + * mount('#demo', { 11 + * server: ` 12 + * export default function App() { 13 + * return <h1>Hello RSC</h1>; 14 + * } 15 + * `, 16 + * client: ` 17 + * 'use client' 18 + * export function Button() { 19 + * return <button>Click</button>; 20 + * } 21 + * ` 22 + * }); 23 + * </script> 24 + * ``` 25 + */ 26 + 27 + type EmbedOptions = { 28 + server: string; 29 + client: string; 30 + }; 31 + 32 + type EmbedControl = { 33 + iframe: HTMLIFrameElement; 34 + destroy: () => void; 35 + }; 36 + 37 + type EmbedReadyMessage = { 38 + type: "rsc-embed:ready"; 39 + }; 40 + 41 + type EmbedInitMessage = { 42 + type: "rsc-embed:init"; 43 + code: { 44 + server: string; 45 + client: string; 46 + }; 47 + }; 48 + 49 + function isEmbedReadyMessage(data: unknown): data is EmbedReadyMessage { 50 + return ( 51 + typeof data === "object" && 52 + data !== null && 53 + (data as { type?: string }).type === "rsc-embed:ready" 54 + ); 55 + } 56 + 57 + const getEmbedUrl = (): string => { 58 + return new URL("embed.html", import.meta.url).href; 59 + }; 60 + 61 + /** 62 + * Mount an RSC Explorer embed into a container element 63 + * @param container - CSS selector or DOM element 64 + * @param options - Configuration options 65 + * @returns Control object with methods to interact with the embed 66 + */ 67 + export function mount( 68 + container: string | HTMLElement, 69 + { server, client }: EmbedOptions, 70 + ): EmbedControl { 71 + const el = 72 + typeof container === "string" ? document.querySelector<HTMLElement>(container) : container; 73 + 74 + if (!el) { 75 + throw new Error(`RSC Explorer: Container not found: ${container}`); 76 + } 77 + 78 + const iframe = document.createElement("iframe"); 79 + iframe.src = getEmbedUrl(); 80 + iframe.style.cssText = 81 + "width: 100%; height: 100%; border: 1px solid #e0e0e0; border-radius: 8px;"; 82 + 83 + const handleMessage = (event: MessageEvent<unknown>): void => { 84 + if (event.source !== iframe.contentWindow) return; 85 + 86 + if (isEmbedReadyMessage(event.data)) { 87 + const initMessage: EmbedInitMessage = { 88 + type: "rsc-embed:init", 89 + code: { server: server.trim(), client: client.trim() }, 90 + }; 91 + iframe.contentWindow?.postMessage(initMessage, "*"); 92 + } 93 + }; 94 + 95 + window.addEventListener("message", handleMessage); 96 + 97 + el.innerHTML = ""; 98 + el.appendChild(iframe); 99 + 100 + return { 101 + iframe, 102 + destroy: (): void => { 103 + window.removeEventListener("message", handleMessage); 104 + el.innerHTML = ""; 105 + }, 106 + }; 107 + }
-43
src/server/webpack-shim.js
··· 1 - // Shim webpack globals for react-server-dom-webpack/server in worker context 2 - // Uses self instead of window since this runs in a Web Worker 3 - 4 - const moduleCache = {}; 5 - 6 - self.__webpack_require__ = function (moduleId) { 7 - if (moduleCache[moduleId]) { 8 - return moduleCache[moduleId]; 9 - } 10 - throw new Error(`Module ${moduleId} not found in webpack shim`); 11 - }; 12 - 13 - self.__webpack_require__.m = {}; 14 - self.__webpack_require__.c = moduleCache; 15 - self.__webpack_require__.d = function (exports, definition) { 16 - for (const key in definition) { 17 - if ( 18 - Object.prototype.hasOwnProperty.call(definition, key) && 19 - !Object.prototype.hasOwnProperty.call(exports, key) 20 - ) { 21 - Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 22 - } 23 - } 24 - }; 25 - self.__webpack_require__.r = function (exports) { 26 - if (typeof Symbol !== "undefined" && Symbol.toStringTag) { 27 - Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); 28 - } 29 - Object.defineProperty(exports, "__esModule", { value: true }); 30 - }; 31 - self.__webpack_require__.o = function (obj, prop) { 32 - return Object.prototype.hasOwnProperty.call(obj, prop); 33 - }; 34 - 35 - self.__webpack_chunk_load__ = function (chunkId) { 36 - return Promise.resolve(); 37 - }; 38 - 39 - self.__webpack_require__.e = function (chunkId) { 40 - return Promise.resolve(); 41 - }; 42 - 43 - self.__webpack_require__.p = "/";
+68
src/server/webpack-shim.ts
··· 1 + // Shim webpack globals for react-server-dom-webpack/server in worker context 2 + // Uses self instead of window since this runs in a Web Worker 3 + 4 + type WebpackRequire = { 5 + (moduleId: string): unknown; 6 + m: Record<string, (module: { exports: unknown }) => void>; 7 + c: Record<string, unknown>; 8 + d: (exports: object, definition: Record<string, () => unknown>) => void; 9 + r: (exports: object) => void; 10 + o: (obj: object, prop: string) => boolean; 11 + e: (chunkId: string) => Promise<void>; 12 + p: string; 13 + }; 14 + 15 + type WorkerSelf = DedicatedWorkerGlobalScope & { 16 + __webpack_require__: WebpackRequire; 17 + __webpack_chunk_load__: (chunkId: string) => Promise<void>; 18 + }; 19 + 20 + const workerSelf = self as unknown as WorkerSelf; 21 + 22 + const moduleCache: Record<string, unknown> = {}; 23 + 24 + const webpackRequire: WebpackRequire = function (moduleId: string): unknown { 25 + if (moduleCache[moduleId]) { 26 + return moduleCache[moduleId]; 27 + } 28 + throw new Error(`Module ${moduleId} not found in webpack shim`); 29 + } as WebpackRequire; 30 + 31 + webpackRequire.m = {}; 32 + webpackRequire.c = moduleCache; 33 + webpackRequire.d = function (exports: object, definition: Record<string, () => unknown>): void { 34 + for (const key in definition) { 35 + const getter = definition[key]; 36 + if ( 37 + getter && 38 + Object.prototype.hasOwnProperty.call(definition, key) && 39 + !Object.prototype.hasOwnProperty.call(exports, key) 40 + ) { 41 + Object.defineProperty(exports, key, { 42 + enumerable: true, 43 + get: getter, 44 + }); 45 + } 46 + } 47 + }; 48 + webpackRequire.r = function (exports: object): void { 49 + if (typeof Symbol !== "undefined" && Symbol.toStringTag) { 50 + Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); 51 + } 52 + Object.defineProperty(exports, "__esModule", { value: true }); 53 + }; 54 + webpackRequire.o = function (obj: object, prop: string): boolean { 55 + return Object.prototype.hasOwnProperty.call(obj, prop); 56 + }; 57 + webpackRequire.e = function (_chunkId: string): Promise<void> { 58 + return Promise.resolve(); 59 + }; 60 + webpackRequire.p = "/"; 61 + 62 + workerSelf.__webpack_require__ = webpackRequire; 63 + 64 + workerSelf.__webpack_chunk_load__ = function (_chunkId: string): Promise<void> { 65 + return Promise.resolve(); 66 + }; 67 + 68 + export {};
-154
src/server/worker.js
··· 1 - // Server Worker - RSC server simulation 2 - // 3 - // Models a real server: deploy code once, then handle requests against it. 4 - // - `deploy`: Store compiled code, manifest, etc. (like deploying to production) 5 - // - `render`/`action`: Execute against deployed code 6 - 7 - import "./webpack-shim.js"; 8 - import "../client/byte-stream-polyfill.js"; 9 - import "text-encoding"; 10 - 11 - import { 12 - renderToReadableStream, 13 - registerServerReference, 14 - createClientModuleProxy, 15 - decodeReply, 16 - } from "react-server-dom-webpack/server"; 17 - import React from "react"; 18 - 19 - let deployed = null; 20 - 21 - // Safari doesn't support transferable streams 22 - async function streamToMain(stream, requestId) { 23 - const reader = stream.getReader(); 24 - try { 25 - while (true) { 26 - const { done, value } = await reader.read(); 27 - if (done) { 28 - self.postMessage({ type: "stream-end", requestId }); 29 - break; 30 - } 31 - self.postMessage({ type: "stream-chunk", requestId, chunk: value }); 32 - } 33 - } catch (err) { 34 - self.postMessage({ type: "stream-error", requestId, error: { message: err.message } }); 35 - } 36 - } 37 - 38 - self.onmessage = async (event) => { 39 - const { type, requestId } = event.data; 40 - 41 - try { 42 - switch (type) { 43 - case "deploy": 44 - handleDeploy(event.data); 45 - break; 46 - case "render": 47 - await handleRender(event.data); 48 - break; 49 - case "action": 50 - await handleAction(event.data); 51 - break; 52 - default: 53 - throw new Error(`Unknown message type: ${type}`); 54 - } 55 - } catch (error) { 56 - self.postMessage({ 57 - type: "error", 58 - requestId, 59 - error: { message: error.message, stack: error.stack }, 60 - }); 61 - } 62 - }; 63 - 64 - function handleDeploy({ compiledCode, manifest, actionNames, requestId }) { 65 - const clientModule = createClientModuleProxy("client"); 66 - const modules = { react: React, "./client": clientModule }; 67 - const serverModule = evalModule(compiledCode, modules, actionNames); 68 - 69 - deployed = { manifest, serverModule, actionNames }; 70 - 71 - self.postMessage({ type: "deployed", requestId }); 72 - } 73 - 74 - function requireDeployed() { 75 - if (!deployed) throw new Error("No code deployed"); 76 - return deployed; 77 - } 78 - 79 - async function handleRender({ requestId }) { 80 - const { manifest, serverModule } = requireDeployed(); 81 - 82 - const App = serverModule.default || serverModule; 83 - const element = typeof App === "function" ? React.createElement(App) : App; 84 - 85 - const flightStream = renderToReadableStream(element, manifest, { 86 - onError: (error) => error.message || String(error), 87 - }); 88 - 89 - self.postMessage({ type: "stream-start", requestId }); 90 - streamToMain(flightStream, requestId); 91 - } 92 - 93 - async function handleAction({ actionId, encodedArgs, requestId }) { 94 - const { manifest, serverModule } = requireDeployed(); 95 - 96 - const actionFn = serverModule[actionId]; 97 - if (typeof actionFn !== "function") { 98 - throw new Error(`Action "${actionId}" not found`); 99 - } 100 - 101 - const toDecode = reconstructEncodedArgs(encodedArgs); 102 - const args = await decodeReply(toDecode, {}); 103 - const result = await actionFn(...(Array.isArray(args) ? args : [args])); 104 - 105 - const flightStream = renderToReadableStream(result, manifest, { 106 - onError: (error) => error.message || String(error), 107 - }); 108 - 109 - self.postMessage({ type: "stream-start", requestId }); 110 - streamToMain(flightStream, requestId); 111 - } 112 - 113 - function reconstructEncodedArgs(encodedArgs) { 114 - if (encodedArgs.type === "formdata") { 115 - const formData = new FormData(); 116 - for (const [key, value] of new URLSearchParams(encodedArgs.data)) { 117 - formData.append(key, value); 118 - } 119 - return formData; 120 - } 121 - return encodedArgs.data; 122 - } 123 - 124 - function evalModule(code, modules, actionNames) { 125 - let finalCode = code; 126 - if (actionNames?.length > 0) { 127 - finalCode += 128 - "\n" + 129 - actionNames 130 - .map( 131 - (name) => 132 - `__registerServerReference(${name}, "${name}", "${name}"); exports.${name} = ${name};`, 133 - ) 134 - .join("\n"); 135 - } 136 - 137 - const module = { exports: {} }; 138 - const require = (id) => { 139 - if (!modules[id]) throw new Error(`Module "${id}" not found`); 140 - return modules[id]; 141 - }; 142 - 143 - new Function("module", "exports", "require", "React", "__registerServerReference", finalCode)( 144 - module, 145 - module.exports, 146 - require, 147 - React, 148 - registerServerReference, 149 - ); 150 - 151 - return module.exports; 152 - } 153 - 154 - self.postMessage({ type: "ready" });
+273
src/server/worker.ts
··· 1 + // Server Worker - RSC server simulation 2 + // 3 + // Models a real server: deploy code once, then handle requests against it. 4 + // - `deploy`: Store compiled code, manifest, etc. (like deploying to production) 5 + // - `render`/`action`: Execute against deployed code 6 + 7 + import "./webpack-shim.ts"; 8 + import "../client/byte-stream-polyfill.ts"; 9 + import "text-encoding"; 10 + 11 + import { 12 + renderToReadableStream, 13 + registerServerReference, 14 + createClientModuleProxy, 15 + decodeReply, 16 + type ClientManifest, 17 + } from "react-server-dom-webpack/server"; 18 + import React from "react"; 19 + 20 + declare const self: DedicatedWorkerGlobalScope; 21 + 22 + type DeployMessage = { 23 + type: "deploy"; 24 + requestId: string; 25 + compiledCode: string; 26 + manifest: ClientManifest; 27 + actionNames: string[]; 28 + }; 29 + 30 + type RenderMessage = { 31 + type: "render"; 32 + requestId: string; 33 + }; 34 + 35 + type ActionMessage = { 36 + type: "action"; 37 + requestId: string; 38 + actionId: string; 39 + encodedArgs: EncodedArgs; 40 + }; 41 + 42 + type WorkerMessage = DeployMessage | RenderMessage | ActionMessage; 43 + 44 + type EncodedArgs = { 45 + type: "formdata" | "string"; 46 + data: string; 47 + }; 48 + 49 + type ErrorResponse = { 50 + type: "error"; 51 + requestId: string; 52 + error: { message: string; stack?: string }; 53 + }; 54 + 55 + type DeployedResponse = { 56 + type: "deployed"; 57 + requestId: string; 58 + }; 59 + 60 + type StreamStartResponse = { 61 + type: "stream-start"; 62 + requestId: string; 63 + }; 64 + 65 + type StreamChunkResponse = { 66 + type: "stream-chunk"; 67 + requestId: string; 68 + chunk: Uint8Array; 69 + }; 70 + 71 + type StreamEndResponse = { 72 + type: "stream-end"; 73 + requestId: string; 74 + }; 75 + 76 + type StreamErrorResponse = { 77 + type: "stream-error"; 78 + requestId: string; 79 + error: { message: string }; 80 + }; 81 + 82 + type ReadyResponse = { 83 + type: "ready"; 84 + }; 85 + 86 + type ServerModule = { 87 + default?: React.ComponentType | React.ReactNode; 88 + [key: string]: unknown; 89 + }; 90 + 91 + type DeployedState = { 92 + manifest: ClientManifest; 93 + serverModule: ServerModule; 94 + actionNames: string[]; 95 + }; 96 + 97 + let deployed: DeployedState | null = null; 98 + 99 + // Safari doesn't support transferable streams 100 + async function streamToMain(stream: ReadableStream<Uint8Array>, requestId: string): Promise<void> { 101 + const reader = stream.getReader(); 102 + try { 103 + while (true) { 104 + const { done, value } = await reader.read(); 105 + if (done) { 106 + self.postMessage({ type: "stream-end", requestId } satisfies StreamEndResponse); 107 + break; 108 + } 109 + self.postMessage({ 110 + type: "stream-chunk", 111 + requestId, 112 + chunk: value, 113 + } satisfies StreamChunkResponse); 114 + } 115 + } catch (err) { 116 + const error = err instanceof Error ? err : new Error(String(err)); 117 + self.postMessage({ 118 + type: "stream-error", 119 + requestId, 120 + error: { message: error.message }, 121 + } satisfies StreamErrorResponse); 122 + } 123 + } 124 + 125 + self.onmessage = async (event: MessageEvent<WorkerMessage>) => { 126 + const { type, requestId } = event.data; 127 + 128 + try { 129 + switch (type) { 130 + case "deploy": 131 + handleDeploy(event.data); 132 + break; 133 + case "render": 134 + await handleRender(event.data); 135 + break; 136 + case "action": 137 + await handleAction(event.data); 138 + break; 139 + default: { 140 + const _exhaustive: never = type; 141 + throw new Error(`Unknown message type: ${_exhaustive}`); 142 + } 143 + } 144 + } catch (error) { 145 + const err = error instanceof Error ? error : new Error(String(error)); 146 + const errorPayload: { message: string; stack?: string } = { message: err.message }; 147 + if (err.stack) { 148 + errorPayload.stack = err.stack; 149 + } 150 + self.postMessage({ 151 + type: "error", 152 + requestId, 153 + error: errorPayload, 154 + } satisfies ErrorResponse); 155 + } 156 + }; 157 + 158 + function handleDeploy({ compiledCode, manifest, actionNames, requestId }: DeployMessage): void { 159 + const clientModule = createClientModuleProxy("client"); 160 + const modules: Record<string, unknown> = { react: React, "./client": clientModule }; 161 + const serverModule = evalModule(compiledCode, modules, actionNames); 162 + 163 + deployed = { manifest, serverModule, actionNames }; 164 + 165 + self.postMessage({ type: "deployed", requestId } satisfies DeployedResponse); 166 + } 167 + 168 + function requireDeployed(): DeployedState { 169 + if (!deployed) throw new Error("No code deployed"); 170 + return deployed; 171 + } 172 + 173 + async function handleRender({ requestId }: RenderMessage): Promise<void> { 174 + const { manifest, serverModule } = requireDeployed(); 175 + 176 + const App = serverModule.default ?? serverModule; 177 + const element = 178 + typeof App === "function" 179 + ? React.createElement(App as React.ComponentType) 180 + : (App as React.ReactNode); 181 + 182 + const flightStream = renderToReadableStream(element, manifest, { 183 + onError: (error: unknown) => { 184 + if (error instanceof Error) return error.message; 185 + return String(error); 186 + }, 187 + }); 188 + 189 + self.postMessage({ type: "stream-start", requestId } satisfies StreamStartResponse); 190 + streamToMain(flightStream, requestId); 191 + } 192 + 193 + async function handleAction({ actionId, encodedArgs, requestId }: ActionMessage): Promise<void> { 194 + const { manifest, serverModule } = requireDeployed(); 195 + 196 + const actionFn = serverModule[actionId]; 197 + if (typeof actionFn !== "function") { 198 + throw new Error(`Action "${actionId}" not found`); 199 + } 200 + 201 + const toDecode = reconstructEncodedArgs(encodedArgs); 202 + const args = await decodeReply(toDecode, {}); 203 + const argsArray = Array.isArray(args) ? args : [args]; 204 + const result = (await (actionFn as (...args: unknown[]) => Promise<unknown>)( 205 + ...argsArray, 206 + )) as React.ReactNode; 207 + 208 + const flightStream = renderToReadableStream(result, manifest, { 209 + onError: (error: unknown) => { 210 + if (error instanceof Error) return error.message; 211 + return String(error); 212 + }, 213 + }); 214 + 215 + self.postMessage({ type: "stream-start", requestId } satisfies StreamStartResponse); 216 + streamToMain(flightStream, requestId); 217 + } 218 + 219 + function reconstructEncodedArgs(encodedArgs: EncodedArgs): FormData | string { 220 + if (encodedArgs.type === "formdata") { 221 + const formData = new FormData(); 222 + for (const [key, value] of new URLSearchParams(encodedArgs.data)) { 223 + formData.append(key, value); 224 + } 225 + return formData; 226 + } 227 + return encodedArgs.data; 228 + } 229 + 230 + function evalModule( 231 + code: string, 232 + modules: Record<string, unknown>, 233 + actionNames: string[] | undefined, 234 + ): ServerModule { 235 + let finalCode = code; 236 + if (actionNames && actionNames.length > 0) { 237 + finalCode += 238 + "\n" + 239 + actionNames 240 + .map( 241 + (name) => 242 + `__registerServerReference(${name}, "${name}", "${name}"); exports.${name} = ${name};`, 243 + ) 244 + .join("\n"); 245 + } 246 + 247 + const module: { exports: ServerModule } = { exports: {} }; 248 + const require = (id: string): unknown => { 249 + if (!modules[id]) throw new Error(`Module "${id}" not found`); 250 + return modules[id]; 251 + }; 252 + 253 + const fn = new Function( 254 + "module", 255 + "exports", 256 + "require", 257 + "React", 258 + "__registerServerReference", 259 + finalCode, 260 + ) as ( 261 + module: { exports: ServerModule }, 262 + exports: ServerModule, 263 + require: (id: string) => unknown, 264 + ReactLib: typeof React, 265 + registerServerRef: typeof registerServerReference, 266 + ) => void; 267 + 268 + fn(module, module.exports, require, React, registerServerReference); 269 + 270 + return module.exports; 271 + } 272 + 273 + self.postMessage({ type: "ready" } satisfies ReadyResponse);
-226
src/shared/compiler.js
··· 1 - import { transform } from "@babel/standalone"; 2 - 3 - export function parseExports(code) { 4 - const exports = []; 5 - const { ast } = transform(code, { 6 - presets: ["react"], 7 - ast: true, 8 - }); 9 - 10 - for (const node of ast.program.body) { 11 - if (node.type === "ExportNamedDeclaration") { 12 - if (node.declaration) { 13 - if (node.declaration.type === "FunctionDeclaration") { 14 - exports.push(node.declaration.id.name); 15 - } else if (node.declaration.type === "VariableDeclaration") { 16 - for (const decl of node.declaration.declarations) { 17 - if (decl.id.type === "Identifier") { 18 - exports.push(decl.id.name); 19 - } 20 - } 21 - } 22 - } 23 - } else if (node.type === "ExportDefaultDeclaration") { 24 - exports.push("default"); 25 - } 26 - } 27 - return exports; 28 - } 29 - 30 - function hasDirective(block, directive) { 31 - if (!block) return false; 32 - if (block.directives?.length > 0) { 33 - if (block.directives.some((d) => d.value?.value === directive)) { 34 - return true; 35 - } 36 - } 37 - // Fallback for some parsers 38 - if (block.body) { 39 - for (const node of block.body) { 40 - if (node.type !== "ExpressionStatement") break; 41 - if (node.directive === directive) return true; 42 - if (node.expression?.type === "StringLiteral" && node.expression.value === directive) 43 - return true; 44 - } 45 - } 46 - return false; 47 - } 48 - 49 - function hasUseServerDirective(body) { 50 - if (!body || body.type !== "BlockStatement") return false; 51 - return hasDirective(body, "use server"); 52 - } 53 - 54 - export function parseServerActions(code) { 55 - const { ast } = transform(code, { 56 - presets: ["react"], 57 - ast: true, 58 - }); 59 - 60 - // 'use client' is not supported - this REPL only handles server code 61 - if (hasDirective(ast.program, "use client")) { 62 - throw new Error('"use client" is not supported. This environment only handles server code.'); 63 - } 64 - 65 - const hasModuleUseServer = hasDirective(ast.program, "use server"); 66 - 67 - const actions = []; 68 - 69 - function checkNoUseClient(body, name) { 70 - if (hasDirective(body, "use client")) { 71 - throw new Error( 72 - `"use client" is not supported${name ? ` (found in "${name}")` : ""}. This environment only handles server code.`, 73 - ); 74 - } 75 - } 76 - 77 - function collectExportedFunction(node, name) { 78 - checkNoUseClient(node.body, name); 79 - if (hasModuleUseServer) { 80 - // Module-level 'use server': all exported functions are actions 81 - actions.push(name); 82 - } else if (hasUseServerDirective(node.body)) { 83 - // Function-level 'use server' 84 - actions.push(name); 85 - } 86 - } 87 - 88 - for (const node of ast.program.body) { 89 - if (node.type === "FunctionDeclaration" && node.id) { 90 - checkNoUseClient(node.body, node.id.name); 91 - if (!hasModuleUseServer && hasUseServerDirective(node.body)) { 92 - actions.push(node.id.name); 93 - } 94 - } else if (node.type === "ExportNamedDeclaration" && node.declaration) { 95 - if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) { 96 - collectExportedFunction(node.declaration, node.declaration.id.name); 97 - } else if (node.declaration.type === "VariableDeclaration") { 98 - for (const decl of node.declaration.declarations) { 99 - if (decl.id.type === "Identifier" && decl.init) { 100 - if ( 101 - decl.init.type === "ArrowFunctionExpression" || 102 - decl.init.type === "FunctionExpression" 103 - ) { 104 - collectExportedFunction(decl.init, decl.id.name); 105 - } 106 - } 107 - } 108 - } 109 - } else if (node.type === "VariableDeclaration") { 110 - for (const decl of node.declarations) { 111 - if (decl.id.type === "Identifier" && decl.init) { 112 - if ( 113 - decl.init.type === "ArrowFunctionExpression" || 114 - decl.init.type === "FunctionExpression" 115 - ) { 116 - checkNoUseClient(decl.init.body, decl.id.name); 117 - if (!hasModuleUseServer && hasUseServerDirective(decl.init.body)) { 118 - actions.push(decl.id.name); 119 - } 120 - } 121 - } 122 - } 123 - } 124 - } 125 - 126 - return actions; 127 - } 128 - 129 - export function parseClientModule(code) { 130 - const { ast } = transform(code, { 131 - presets: ["react"], 132 - ast: true, 133 - }); 134 - 135 - // Require 'use client' at module level 136 - if (!hasDirective(ast.program, "use client")) { 137 - throw new Error('Client code must start with "use client" directive.'); 138 - } 139 - 140 - if (hasDirective(ast.program, "use server")) { 141 - throw new Error('"use server" is not supported in client code.'); 142 - } 143 - 144 - function checkFunction(body, name) { 145 - if (!body || body.type !== "BlockStatement") return; 146 - if (hasDirective(body, "use client")) { 147 - throw new Error( 148 - `"use client" must be at module level, not inside functions${name ? ` (found in "${name}")` : ""}.`, 149 - ); 150 - } 151 - if (hasDirective(body, "use server")) { 152 - throw new Error( 153 - `"use server" is not supported in client code${name ? ` (found in "${name}")` : ""}.`, 154 - ); 155 - } 156 - } 157 - 158 - const exports = []; 159 - 160 - for (const node of ast.program.body) { 161 - if (node.type === "FunctionDeclaration" && node.id) { 162 - checkFunction(node.body, node.id.name); 163 - } else if (node.type === "ExportNamedDeclaration") { 164 - if (node.declaration) { 165 - if (node.declaration.type === "FunctionDeclaration") { 166 - checkFunction(node.declaration.body, node.declaration.id?.name); 167 - if (node.declaration.id) { 168 - exports.push(node.declaration.id.name); 169 - } 170 - } else if (node.declaration.type === "VariableDeclaration") { 171 - for (const decl of node.declaration.declarations) { 172 - if ( 173 - decl.init?.type === "ArrowFunctionExpression" || 174 - decl.init?.type === "FunctionExpression" 175 - ) { 176 - checkFunction(decl.init.body, decl.id?.name); 177 - } 178 - if (decl.id.type === "Identifier") { 179 - exports.push(decl.id.name); 180 - } 181 - } 182 - } 183 - } 184 - } else if (node.type === "ExportDefaultDeclaration") { 185 - exports.push("default"); 186 - } else if (node.type === "VariableDeclaration") { 187 - for (const decl of node.declarations) { 188 - if ( 189 - decl.init?.type === "ArrowFunctionExpression" || 190 - decl.init?.type === "FunctionExpression" 191 - ) { 192 - checkFunction(decl.init.body, decl.id?.name); 193 - } 194 - } 195 - } 196 - } 197 - 198 - return exports; 199 - } 200 - 201 - export function compileToCommonJS(code) { 202 - const { code: compiled } = transform(code, { 203 - presets: ["react"], 204 - sourceType: "module", 205 - plugins: [["transform-modules-commonjs", { loose: true }]], 206 - }); 207 - return compiled; 208 - } 209 - 210 - export function buildManifest(moduleId, exportNames) { 211 - const manifest = { 212 - [moduleId]: { 213 - id: moduleId, 214 - chunks: [], 215 - name: "*", 216 - }, 217 - }; 218 - for (const name of exportNames) { 219 - manifest[`${moduleId}#${name}`] = { 220 - id: moduleId, 221 - chunks: [], 222 - name, 223 - }; 224 - } 225 - return manifest; 226 - }
+329
src/shared/compiler.ts
··· 1 + import { transform } from "@babel/standalone"; 2 + import type * as t from "@babel/types"; 3 + 4 + type BabelProgram = t.Program & { 5 + directives?: Array<{ value?: { value?: string } }>; 6 + }; 7 + 8 + type BabelBlockStatement = t.BlockStatement & { 9 + directives?: Array<{ value?: { value?: string } }>; 10 + }; 11 + 12 + type BabelNode = 13 + | t.FunctionDeclaration 14 + | t.ExportNamedDeclaration 15 + | t.ExportDefaultDeclaration 16 + | t.VariableDeclaration 17 + | t.ExpressionStatement; 18 + 19 + type TransformResult = { 20 + ast?: { 21 + program: BabelProgram; 22 + }; 23 + code?: string; 24 + }; 25 + 26 + export function parseExports(code: string): string[] { 27 + const exports: string[] = []; 28 + const result = transform(code, { 29 + presets: ["react"], 30 + ast: true, 31 + }) as TransformResult; 32 + 33 + const ast = result.ast; 34 + if (!ast) return exports; 35 + 36 + for (const node of ast.program.body as BabelNode[]) { 37 + if (node.type === "ExportNamedDeclaration") { 38 + const exportNode = node as t.ExportNamedDeclaration; 39 + if (exportNode.declaration) { 40 + if (exportNode.declaration.type === "FunctionDeclaration") { 41 + const funcDecl = exportNode.declaration as t.FunctionDeclaration; 42 + if (funcDecl.id) { 43 + exports.push(funcDecl.id.name); 44 + } 45 + } else if (exportNode.declaration.type === "VariableDeclaration") { 46 + const varDecl = exportNode.declaration as t.VariableDeclaration; 47 + for (const decl of varDecl.declarations) { 48 + if (decl.id.type === "Identifier") { 49 + exports.push(decl.id.name); 50 + } 51 + } 52 + } 53 + } 54 + } else if (node.type === "ExportDefaultDeclaration") { 55 + exports.push("default"); 56 + } 57 + } 58 + return exports; 59 + } 60 + 61 + function hasDirective( 62 + block: BabelProgram | BabelBlockStatement | t.BlockStatement | null | undefined, 63 + directive: string, 64 + ): boolean { 65 + if (!block) return false; 66 + 67 + // Check directives array (preferred way) 68 + const blockWithDirectives = block as { directives?: Array<{ value?: { value?: string } }> }; 69 + if (blockWithDirectives.directives && blockWithDirectives.directives.length > 0) { 70 + if ( 71 + blockWithDirectives.directives.some( 72 + (d: { value?: { value?: string } }) => d.value?.value === directive, 73 + ) 74 + ) { 75 + return true; 76 + } 77 + } 78 + 79 + // Fallback for some parsers - check body for string expression statements 80 + const blockWithBody = block as { body?: BabelNode[] }; 81 + if (blockWithBody.body) { 82 + for (const node of blockWithBody.body) { 83 + if (node.type !== "ExpressionStatement") break; 84 + const exprStmt = node as t.ExpressionStatement & { directive?: string }; 85 + if (exprStmt.directive === directive) return true; 86 + if ( 87 + exprStmt.expression.type === "StringLiteral" && 88 + (exprStmt.expression as t.StringLiteral).value === directive 89 + ) { 90 + return true; 91 + } 92 + } 93 + } 94 + return false; 95 + } 96 + 97 + function hasUseServerDirective(body: t.BlockStatement | null | undefined): boolean { 98 + if (!body || body.type !== "BlockStatement") return false; 99 + return hasDirective(body, "use server"); 100 + } 101 + 102 + export function parseServerActions(code: string): string[] { 103 + const result = transform(code, { 104 + presets: ["react"], 105 + ast: true, 106 + }) as TransformResult; 107 + 108 + const ast = result.ast; 109 + if (!ast) return []; 110 + 111 + // 'use client' is not supported - this REPL only handles server code 112 + if (hasDirective(ast.program, "use client")) { 113 + throw new Error('"use client" is not supported. This environment only handles server code.'); 114 + } 115 + 116 + const hasModuleUseServer = hasDirective(ast.program, "use server"); 117 + 118 + const actions: string[] = []; 119 + 120 + function checkNoUseClient( 121 + body: t.BlockStatement | t.Expression | null | undefined, 122 + name: string | undefined, 123 + ): void { 124 + if (body && body.type === "BlockStatement" && hasDirective(body, "use client")) { 125 + throw new Error( 126 + `"use client" is not supported${name ? ` (found in "${name}")` : ""}. This environment only handles server code.`, 127 + ); 128 + } 129 + } 130 + 131 + function collectExportedFunction( 132 + node: t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression, 133 + name: string, 134 + ): void { 135 + const body = node.body; 136 + if (body.type === "BlockStatement") { 137 + checkNoUseClient(body, name); 138 + } 139 + if (hasModuleUseServer) { 140 + // Module-level 'use server': all exported functions are actions 141 + actions.push(name); 142 + } else if (body.type === "BlockStatement" && hasUseServerDirective(body)) { 143 + // Function-level 'use server' 144 + actions.push(name); 145 + } 146 + } 147 + 148 + for (const node of ast.program.body as BabelNode[]) { 149 + if (node.type === "FunctionDeclaration") { 150 + const funcDecl = node as t.FunctionDeclaration; 151 + if (funcDecl.id) { 152 + checkNoUseClient(funcDecl.body, funcDecl.id.name); 153 + if (!hasModuleUseServer && hasUseServerDirective(funcDecl.body)) { 154 + actions.push(funcDecl.id.name); 155 + } 156 + } 157 + } else if (node.type === "ExportNamedDeclaration") { 158 + const exportNode = node as t.ExportNamedDeclaration; 159 + if (exportNode.declaration) { 160 + if (exportNode.declaration.type === "FunctionDeclaration") { 161 + const funcDecl = exportNode.declaration as t.FunctionDeclaration; 162 + if (funcDecl.id) { 163 + collectExportedFunction(funcDecl, funcDecl.id.name); 164 + } 165 + } else if (exportNode.declaration.type === "VariableDeclaration") { 166 + const varDecl = exportNode.declaration as t.VariableDeclaration; 167 + for (const decl of varDecl.declarations) { 168 + if (decl.id.type === "Identifier" && decl.init) { 169 + if ( 170 + decl.init.type === "ArrowFunctionExpression" || 171 + decl.init.type === "FunctionExpression" 172 + ) { 173 + collectExportedFunction( 174 + decl.init as t.ArrowFunctionExpression | t.FunctionExpression, 175 + decl.id.name, 176 + ); 177 + } 178 + } 179 + } 180 + } 181 + } 182 + } else if (node.type === "VariableDeclaration") { 183 + const varDecl = node as t.VariableDeclaration; 184 + for (const decl of varDecl.declarations) { 185 + if (decl.id.type === "Identifier" && decl.init) { 186 + if ( 187 + decl.init.type === "ArrowFunctionExpression" || 188 + decl.init.type === "FunctionExpression" 189 + ) { 190 + const funcInit = decl.init as t.ArrowFunctionExpression | t.FunctionExpression; 191 + const body = funcInit.body; 192 + if (body.type === "BlockStatement") { 193 + checkNoUseClient(body, decl.id.name); 194 + if (!hasModuleUseServer && hasUseServerDirective(body)) { 195 + actions.push(decl.id.name); 196 + } 197 + } 198 + } 199 + } 200 + } 201 + } 202 + } 203 + 204 + return actions; 205 + } 206 + 207 + export function parseClientModule(code: string): string[] { 208 + const result = transform(code, { 209 + presets: ["react"], 210 + ast: true, 211 + }) as TransformResult; 212 + 213 + const ast = result.ast; 214 + if (!ast) return []; 215 + 216 + // Require 'use client' at module level 217 + if (!hasDirective(ast.program, "use client")) { 218 + throw new Error('Client code must start with "use client" directive.'); 219 + } 220 + 221 + if (hasDirective(ast.program, "use server")) { 222 + throw new Error('"use server" is not supported in client code.'); 223 + } 224 + 225 + function checkFunction( 226 + body: t.BlockStatement | t.Expression | null | undefined, 227 + name?: string, 228 + ): void { 229 + if (!body || body.type !== "BlockStatement") return; 230 + if (hasDirective(body, "use client")) { 231 + throw new Error( 232 + `"use client" must be at module level, not inside functions${name ? ` (found in "${name}")` : ""}.`, 233 + ); 234 + } 235 + if (hasDirective(body, "use server")) { 236 + throw new Error( 237 + `"use server" is not supported in client code${name ? ` (found in "${name}")` : ""}.`, 238 + ); 239 + } 240 + } 241 + 242 + const exports: string[] = []; 243 + 244 + for (const node of ast.program.body as BabelNode[]) { 245 + if (node.type === "FunctionDeclaration") { 246 + const funcDecl = node as t.FunctionDeclaration; 247 + if (funcDecl.id) { 248 + checkFunction(funcDecl.body, funcDecl.id.name); 249 + } 250 + } else if (node.type === "ExportNamedDeclaration") { 251 + const exportNode = node as t.ExportNamedDeclaration; 252 + if (exportNode.declaration) { 253 + if (exportNode.declaration.type === "FunctionDeclaration") { 254 + const funcDecl = exportNode.declaration as t.FunctionDeclaration; 255 + checkFunction(funcDecl.body, funcDecl.id?.name); 256 + if (funcDecl.id) { 257 + exports.push(funcDecl.id.name); 258 + } 259 + } else if (exportNode.declaration.type === "VariableDeclaration") { 260 + const varDecl = exportNode.declaration as t.VariableDeclaration; 261 + for (const decl of varDecl.declarations) { 262 + if ( 263 + decl.init?.type === "ArrowFunctionExpression" || 264 + decl.init?.type === "FunctionExpression" 265 + ) { 266 + const funcInit = decl.init as t.ArrowFunctionExpression | t.FunctionExpression; 267 + const name = decl.id.type === "Identifier" ? decl.id.name : undefined; 268 + checkFunction(funcInit.body, name); 269 + } 270 + if (decl.id.type === "Identifier") { 271 + exports.push(decl.id.name); 272 + } 273 + } 274 + } 275 + } 276 + } else if (node.type === "ExportDefaultDeclaration") { 277 + exports.push("default"); 278 + } else if (node.type === "VariableDeclaration") { 279 + const varDecl = node as t.VariableDeclaration; 280 + for (const decl of varDecl.declarations) { 281 + if ( 282 + decl.init?.type === "ArrowFunctionExpression" || 283 + decl.init?.type === "FunctionExpression" 284 + ) { 285 + const funcInit = decl.init as t.ArrowFunctionExpression | t.FunctionExpression; 286 + const name = decl.id.type === "Identifier" ? decl.id.name : undefined; 287 + checkFunction(funcInit.body, name); 288 + } 289 + } 290 + } 291 + } 292 + 293 + return exports; 294 + } 295 + 296 + export function compileToCommonJS(code: string): string { 297 + const result = transform(code, { 298 + presets: ["react"], 299 + sourceType: "module", 300 + plugins: [["transform-modules-commonjs", { loose: true }]], 301 + }) as TransformResult; 302 + return result.code ?? ""; 303 + } 304 + 305 + export interface ManifestEntry { 306 + id: string; 307 + chunks: string[]; 308 + name: string; 309 + } 310 + 311 + export type ClientManifest = Record<string, ManifestEntry>; 312 + 313 + export function buildManifest(moduleId: string, exportNames: string[]): ClientManifest { 314 + const manifest: ClientManifest = { 315 + [moduleId]: { 316 + id: moduleId, 317 + chunks: [], 318 + name: "*", 319 + }, 320 + }; 321 + for (const name of exportNames) { 322 + manifest[`${moduleId}#${name}`] = { 323 + id: moduleId, 324 + chunks: [], 325 + name, 326 + }; 327 + } 328 + return manifest; 329 + }
+5 -2
tests/async.spec.js tests/async.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+5 -2
tests/bound.spec.js tests/bound.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+6 -3
tests/clientref.spec.js tests/clientref.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser(); ··· 22 25 23 26 // Check flight rows include client references for themes 24 27 const rows = await h.getRows(); 25 - expect(rows.some((r) => r.text.includes("darkTheme") || r.text.includes("lightTheme"))).toBe( 28 + expect(rows.some((r) => r.text?.includes("darkTheme") || r.text?.includes("lightTheme"))).toBe( 26 29 true, 27 30 ); 28 31
+5 -2
tests/counter.spec.js tests/counter.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+5 -2
tests/errors.spec.js tests/errors.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+5 -2
tests/form.spec.js tests/form.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+5 -5
tests/globalSetup.js tests/globalSetup.ts
··· 1 - import { spawn } from "child_process"; 1 + import { spawn, type ChildProcess } from "child_process"; 2 2 3 - let server; 3 + let server: ChildProcess | null = null; 4 4 5 - async function waitForServer(url, timeout = 30000) { 5 + async function waitForServer(url: string, timeout = 30000): Promise<void> { 6 6 const start = Date.now(); 7 7 while (Date.now() - start < timeout) { 8 8 try { ··· 16 16 throw new Error("Test server start timeout"); 17 17 } 18 18 19 - export async function setup() { 19 + export async function setup(): Promise<void> { 20 20 server = spawn("npx", ["vite", "--port", "5599", "--strictPort"], { 21 21 stdio: "inherit", 22 22 shell: true, ··· 32 32 await waitForServer("http://localhost:5599"); 33 33 } 34 34 35 - export async function teardown() { 35 + export async function teardown(): Promise<void> { 36 36 if (server) { 37 37 server.kill(); 38 38 }
+5 -2
tests/hello.spec.js tests/hello.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+61 -28
tests/helpers.js tests/helpers.ts
··· 1 1 import { expect } from "vitest"; 2 - import { chromium } from "playwright"; 2 + import { chromium, type Browser, type Page, type FrameLocator } from "playwright"; 3 3 4 - export async function launchBrowser() { 4 + export async function launchBrowser(): Promise<Browser> { 5 5 const executablePath = process.env.CHROMIUM_PATH; 6 6 return chromium.launch(executablePath ? { executablePath } : undefined); 7 7 } 8 8 9 - let prevRowTexts = []; 10 - let prevStatuses = []; 9 + type RowData = { 10 + text: string | null; 11 + status: "done" | "next" | "pending"; 12 + }; 13 + 14 + type WaitForOptions = { 15 + timeout?: number; 16 + }; 17 + 18 + export type TestHelpers = { 19 + load: (sample: string) => Promise<void>; 20 + step: () => Promise<string | null>; 21 + stepAll: () => Promise<string | null>; 22 + stepInfo: () => Promise<string>; 23 + getRows: () => Promise<RowData[]>; 24 + preview: (waitFor?: string) => Promise<string>; 25 + tree: () => Promise<string | null>; 26 + checkNoRemainingSteps: () => Promise<void>; 27 + frame: () => FrameLocator; 28 + waitFor: (predicate: () => boolean, options?: WaitForOptions) => Promise<void>; 29 + }; 30 + 31 + let prevRowTexts: (string | null)[] = []; 32 + let prevStatuses: ("done" | "next" | "pending")[] = []; 11 33 let prevPreview = ""; 12 34 let previewAsserted = true; 13 - let pageRef = null; 14 - let frameRef = null; 35 + let pageRef: Page | null = null; 36 + let frameRef: FrameLocator | null = null; 15 37 16 - export function createHelpers(page) { 38 + export function createHelpers(page: Page): TestHelpers { 17 39 pageRef = page; 18 40 19 - async function load(sample) { 41 + async function load(sample: string): Promise<void> { 20 42 await page.goto(`http://localhost:5599/?s=${sample}`); 21 43 // Wait for iframe to load and get frame reference 22 44 const iframe = page.frameLocator("iframe"); ··· 30 52 previewAsserted = true; 31 53 } 32 54 33 - async function getPreviewText() { 55 + async function getPreviewText(): Promise<string> { 56 + if (!frameRef) throw new Error("frameRef not initialized"); 34 57 return (await frameRef.locator(".preview-container").innerText()).trim(); 35 58 } 36 59 37 - async function doStep() { 60 + async function doStep(): Promise<string | null> { 61 + if (!frameRef || !pageRef) throw new Error("refs not initialized"); 38 62 const btn = frameRef.locator(".control-btn").nth(2); 39 63 if (await btn.isDisabled()) return null; 40 64 await btn.click(); ··· 69 93 return await tree(); 70 94 } 71 95 72 - async function step() { 96 + async function step(): Promise<string | null> { 73 97 // Check for unasserted preview changes before stepping 74 98 const currentPreview = await getPreviewText(); 75 99 if (currentPreview !== prevPreview && !previewAsserted) { ··· 81 105 return await doStep(); 82 106 } 83 107 84 - async function waitForStepButton() { 108 + async function waitForStepButton(): Promise<void> { 109 + if (!frameRef || !pageRef) throw new Error("refs not initialized"); 85 110 const btn = frameRef.locator(".control-btn").nth(2); 86 111 // Wait for button to be enabled 87 112 await expect ··· 95 120 await pageRef.waitForTimeout(50); 96 121 } 97 122 98 - async function stepAll() { 123 + async function stepAll(): Promise<string | null> { 99 124 // Check for unasserted preview changes before stepping 100 125 const currentPreview = await getPreviewText(); 101 126 if (currentPreview !== prevPreview && !previewAsserted) { ··· 136 161 return await tree(); 137 162 } 138 163 139 - async function preview(waitFor) { 164 + async function preview(waitFor?: string): Promise<string> { 140 165 if (waitFor) { 141 166 // Wait for preview to contain the marker 142 167 await expect.poll(() => getPreviewText(), { timeout: 10000 }).toContain(waitFor); ··· 149 174 return current; 150 175 } 151 176 152 - async function stepInfo() { 177 + async function stepInfo(): Promise<string> { 178 + if (!frameRef) throw new Error("frameRef not initialized"); 153 179 return (await frameRef.locator(".step-info").innerText()).trim(); 154 180 } 155 181 156 - async function getRows() { 182 + async function getRows(): Promise<RowData[]> { 183 + if (!frameRef) throw new Error("frameRef not initialized"); 157 184 return frameRef.locator(".flight-line").evaluateAll((els) => 158 - els 185 + (els as HTMLElement[]) 159 186 .map((el) => ({ 160 187 text: el.textContent, 161 188 status: el.classList.contains("line-done") 162 - ? "done" 189 + ? ("done" as const) 163 190 : el.classList.contains("line-next") 164 - ? "next" 165 - : "pending", 191 + ? ("next" as const) 192 + : ("pending" as const), 166 193 })) 167 194 .filter( 168 195 ({ text }) => 196 + text !== null && 169 197 !text.startsWith(":N") && 170 198 !/^\w+:D/.test(text) && 171 199 !/^\w+:\{.*"name"/.test(text) && ··· 174 202 ); 175 203 } 176 204 177 - async function tree() { 205 + async function tree(): Promise<string | null> { 206 + if (!frameRef) throw new Error("frameRef not initialized"); 178 207 // Find the log entry containing the "next" line, or the last done entry 179 208 const treeText = await frameRef.locator(".log-entry").evaluateAll((entries) => { 180 209 const nextLine = document.querySelector(".line-next"); 181 210 if (nextLine) { 182 211 const entry = nextLine.closest(".log-entry"); 183 - const tree = entry?.querySelector(".log-entry-tree"); 212 + const tree = entry?.querySelector(".log-entry-tree") as HTMLElement | null; 184 213 return tree?.innerText?.trim() || null; 185 214 } 186 215 // No next line - get the last entry's tree 187 216 if (entries.length === 0) return null; 188 - const lastEntry = entries[entries.length - 1]; 189 - const tree = lastEntry.querySelector(".log-entry-tree"); 217 + const lastEntry = entries[entries.length - 1] as HTMLElement | undefined; 218 + if (!lastEntry) return null; 219 + const tree = lastEntry.querySelector(".log-entry-tree") as HTMLElement | null; 190 220 return tree?.innerText?.trim() || null; 191 221 }); 192 222 return treeText; 193 223 } 194 224 195 - async function checkNoRemainingSteps() { 225 + async function checkNoRemainingSteps(): Promise<void> { 226 + if (!frameRef || !pageRef) throw new Error("refs not initialized"); 196 227 const initialTree = await tree(); 197 228 const initialPreview = await getPreviewText(); 198 229 ··· 220 251 } 221 252 } 222 253 223 - function frame() { 254 + function frame(): FrameLocator { 255 + if (!frameRef) throw new Error("frameRef not initialized"); 224 256 return frameRef; 225 257 } 226 258 227 - async function waitFor(predicate, options = {}) { 228 - const timeout = options.timeout || 10000; 259 + async function waitFor(predicate: () => boolean, options: WaitForOptions = {}): Promise<void> { 260 + if (!frameRef || !pageRef) throw new Error("refs not initialized"); 261 + const timeout = options.timeout ?? 10000; 229 262 const interval = 50; 230 263 const start = Date.now(); 231 264 while (Date.now() - start < timeout) {
+5 -2
tests/kitchensink.spec.js tests/kitchensink.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+5 -2
tests/pagination.spec.js tests/pagination.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+5 -2
tests/refresh.spec.js tests/refresh.spec.ts
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { createHelpers, launchBrowser } from "./helpers.js"; 2 + import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 + import type { Browser, Page } from "playwright"; 3 4 4 - let browser, page, h; 5 + let browser: Browser; 6 + let page: Page; 7 + let h: TestHelpers; 5 8 6 9 beforeAll(async () => { 7 10 browser = await launchBrowser();
+26
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"], 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "resolveJsonModule": true, 8 + "isolatedModules": true, 9 + "noEmit": true, 10 + "jsx": "react-jsx", 11 + "jsxImportSource": "react", 12 + "strict": true, 13 + "noUnusedLocals": true, 14 + "noUnusedParameters": true, 15 + "noFallthroughCasesInSwitch": true, 16 + "exactOptionalPropertyTypes": true, 17 + "noUncheckedIndexedAccess": true, 18 + "skipLibCheck": true, 19 + "allowImportingTsExtensions": true, 20 + "esModuleInterop": true, 21 + "allowSyntheticDefaultImports": true, 22 + "types": ["node", "vite/client"] 23 + }, 24 + "include": ["src/**/*", "tests/**/*", "types/**/*", "vite.config.ts", "vitest.config.ts"], 25 + "exclude": ["node_modules", "dist"] 26 + }
+60
types/global.d.ts
··· 1 + // Global type declarations 2 + 3 + // Rolldown worker import pattern 4 + declare module "*?rolldown-worker" { 5 + const workerUrl: string; 6 + export default workerUrl; 7 + } 8 + 9 + // Allow importing JSON modules 10 + declare module "*.json" { 11 + const value: unknown; 12 + export default value; 13 + } 14 + 15 + // Webpack shim globals for client-side (window context) 16 + // Note: interface required here for declaration merging with global Window 17 + interface Window { 18 + __webpack_module_cache__: Record<string, { exports: unknown }>; 19 + __webpack_modules__: Record<string, (module: { exports: unknown }) => void>; 20 + __webpack_require__: WebpackRequire; 21 + __webpack_chunk_load__: (chunkId: string) => Promise<void>; 22 + } 23 + 24 + // Webpack shim globals for worker context (self/globalThis) 25 + declare const __webpack_module_cache__: Record<string, { exports: unknown }>; 26 + 27 + type WebpackRequire = { 28 + (moduleId: string): unknown; 29 + m: Record<string, (module: { exports: unknown }) => void>; 30 + c: Record<string, { exports: unknown } | unknown>; 31 + d: (exports: object, definition: Record<string, () => unknown>) => void; 32 + r: (exports: object) => void; 33 + o: (obj: object, prop: string) => boolean; 34 + e: (chunkId: string) => Promise<void>; 35 + p: string; 36 + }; 37 + 38 + // Worker global context extensions 39 + type WorkerGlobalScope = { 40 + __webpack_require__: WebpackRequire; 41 + __webpack_chunk_load__: (chunkId: string) => Promise<void>; 42 + }; 43 + 44 + // Extend globalThis for worker context 45 + declare namespace globalThis { 46 + let ReadableStream: typeof globalThis.ReadableStream; 47 + let ReadableByteStreamController: typeof globalThis.ReadableByteStreamController; 48 + } 49 + 50 + // Vite environment 51 + type ImportMeta = { 52 + readonly env: { 53 + readonly PROD: boolean; 54 + readonly DEV: boolean; 55 + readonly MODE: string; 56 + }; 57 + readonly hot?: { 58 + accept: (callback?: () => void) => void; 59 + }; 60 + };
+27
types/react-internals.d.ts
··· 1 + // Internal React types adapted from React source (Flow -> TypeScript) 2 + 3 + import type { ReactNode, ReactElement as ReactElementPublic } from "react"; 4 + 5 + export type ReactKey = string | null; 6 + 7 + /** Internal React element structure with $$typeof */ 8 + export interface ReactElementInternal { 9 + $$typeof: symbol; 10 + type: unknown; 11 + key: ReactKey; 12 + ref: unknown; 13 + props: Record<string, unknown>; 14 + } 15 + 16 + /** Lazy element type */ 17 + export interface ReactLazy<T = unknown> { 18 + $$typeof: symbol; 19 + _payload: unknown; 20 + _init: (payload: unknown) => T; 21 + } 22 + 23 + /** Check if value is a React element (internal) */ 24 + export function isReactElement(value: unknown): value is ReactElementInternal; 25 + 26 + // Re-export React types we use 27 + export type { ReactNode, ReactElementPublic };
+135
types/react-server-dom-webpack.d.ts
··· 1 + // Type declarations for react-server-dom-webpack 2 + // Based on Flow types from React source 3 + 4 + declare module "react-server-dom-webpack/server" { 5 + import type { Thenable, ReactNode } from "react"; 6 + 7 + export type TemporaryReferenceSet = Set<unknown>; 8 + 9 + export type ClientManifest = { 10 + [moduleId: string]: { 11 + id: string; 12 + chunks: string[]; 13 + name: string; 14 + }; 15 + }; 16 + 17 + export type ServerManifest = { 18 + [id: string]: { 19 + id: string; 20 + chunks: string[]; 21 + name: string; 22 + }; 23 + }; 24 + 25 + export type RenderOptions = { 26 + debugChannel?: { readable?: ReadableStream; writable?: WritableStream }; 27 + environmentName?: string | (() => string); 28 + filterStackFrame?: (url: string, functionName: string) => boolean; 29 + identifierPrefix?: string; 30 + signal?: AbortSignal; 31 + temporaryReferences?: TemporaryReferenceSet; 32 + onError?: (error: unknown) => void; 33 + }; 34 + 35 + export type StaticResult = { 36 + prelude: ReadableStream; 37 + }; 38 + 39 + export function renderToReadableStream( 40 + model: ReactNode, 41 + webpackMap: ClientManifest, 42 + options?: RenderOptions, 43 + ): ReadableStream; 44 + 45 + export function prerender( 46 + model: ReactNode, 47 + webpackMap: ClientManifest, 48 + options?: RenderOptions, 49 + ): Promise<StaticResult>; 50 + 51 + export function decodeReply<T = unknown>( 52 + body: string | FormData, 53 + webpackMap: ServerManifest, 54 + options?: { temporaryReferences?: TemporaryReferenceSet }, 55 + ): Thenable<T>; 56 + 57 + export function decodeAction<T = unknown>( 58 + body: FormData, 59 + serverManifest: ServerManifest, 60 + ): Promise<() => T> | null; 61 + 62 + export function decodeFormState<S>( 63 + actionResult: S, 64 + body: FormData, 65 + serverManifest: ServerManifest, 66 + ): Promise<unknown>; 67 + 68 + export function registerServerReference<T extends Function>( 69 + reference: T, 70 + id: string, 71 + exportName: string | null, 72 + ): T; 73 + 74 + export function registerClientReference<T>( 75 + proxyImplementation: T, 76 + id: string, 77 + exportName: string, 78 + ): T; 79 + 80 + export function createClientModuleProxy<T = Record<string, unknown>>(moduleId: string): T; 81 + 82 + export function createTemporaryReferenceSet(): TemporaryReferenceSet; 83 + } 84 + 85 + declare module "react-server-dom-webpack/client" { 86 + import type { Thenable } from "react"; 87 + 88 + export type TemporaryReferenceSet = Set<unknown>; 89 + 90 + export type CallServerCallback = (id: string, args: unknown[]) => Promise<unknown>; 91 + 92 + export type FindSourceMapURLCallback = ( 93 + fileName: string, 94 + environmentName: string, 95 + ) => string | null | undefined; 96 + 97 + export type Options = { 98 + callServer?: CallServerCallback; 99 + debugChannel?: { writable?: WritableStream; readable?: ReadableStream }; 100 + temporaryReferences?: TemporaryReferenceSet; 101 + findSourceMapURL?: FindSourceMapURLCallback; 102 + replayConsoleLogs?: boolean; 103 + environmentName?: string; 104 + startTime?: number; 105 + endTime?: number; 106 + }; 107 + 108 + export function createFromReadableStream<T = unknown>( 109 + stream: ReadableStream, 110 + options?: Options, 111 + ): Thenable<T>; 112 + 113 + export function createFromFetch<T = unknown>( 114 + promiseForResponse: Promise<Response>, 115 + options?: Options, 116 + ): Thenable<T>; 117 + 118 + export function encodeReply( 119 + value: unknown, 120 + options?: { temporaryReferences?: TemporaryReferenceSet; signal?: AbortSignal }, 121 + ): Promise<string | FormData>; 122 + 123 + export function createServerReference<T extends Function>( 124 + id: string, 125 + callServer: CallServerCallback, 126 + ): T; 127 + 128 + export function registerServerReference<T extends Function>( 129 + reference: T, 130 + id: string, 131 + exportName: string | null, 132 + ): T; 133 + 134 + export function createTemporaryReferenceSet(): TemporaryReferenceSet; 135 + }
+19
types/web-streams-polyfill.d.ts
··· 1 + // Type declarations for web-streams-polyfill specific exports 2 + 3 + declare module "web-streams-polyfill" { 4 + export const ReadableStream: { 5 + new <R = unknown>( 6 + underlyingSource?: UnderlyingSource<R>, 7 + strategy?: QueuingStrategy<R>, 8 + ): ReadableStream<R>; 9 + prototype: ReadableStream; 10 + }; 11 + // ReadableByteStreamController is an internal class, we type it loosely 12 + export const ReadableByteStreamController: unknown; 13 + export const WritableStream: typeof globalThis.WritableStream; 14 + export const TransformStream: typeof globalThis.TransformStream; 15 + } 16 + 17 + declare module "web-streams-polyfill/polyfill" { 18 + // Side-effect only import that polyfills globals 19 + }
+2 -2
vite.config.js
··· 60 60 configureServer(server) { 61 61 server.middlewares.use((req, res, next) => { 62 62 if (req.url === "/embed.js") { 63 - req.url = "/src/embed.js"; 63 + req.url = "/src/embed.ts"; 64 64 } 65 65 next(); 66 66 }); ··· 85 85 input: { 86 86 main: resolve(__dirname, "index.html"), 87 87 embed: resolve(__dirname, "embed.html"), 88 - "embed-js": resolve(__dirname, "src/embed.js"), 88 + "embed-js": resolve(__dirname, "src/embed.ts"), 89 89 }, 90 90 output: { 91 91 entryFileNames: (chunkInfo) => {
+1 -1
vitest.config.js
··· 4 4 test: { 5 5 testTimeout: 30000, 6 6 fileParallelism: true, 7 - globalSetup: "./tests/globalSetup.js", 7 + globalSetup: "./tests/globalSetup.ts", 8 8 }, 9 9 });