From 4bfe9307a7f2e0c950ad18300b25a4dc27b4e68c Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Tue, 3 Nov 2026 22:06:12 +0800 Subject: [PATCH] fix(functions): show the body of server function calls that send files With enableRichArguments() in @solidjs/web 2.0.0-rc.20, a call whose arguments hold a File or Blob sends multipart form data marked X-Server-Function-Format: 0. The panel and the copied report read it as a seroval stream, which failed. Both now split the args part from the files. Also moves the dev dependencies to 2.0.0-rc.20 and @solidjs/vite-plugin 3.0.0-next.55. Co-Authored-By: Claude Opus 5.5 --- .changeset/rich-args-uploads.md | 7 + package.json | 8 +- pnpm-lock.yaml | 226 ++++++++-------------- pnpm-workspace.yaml | 20 +- src/dev-toolbar/functions/body-format.ts | 24 +++ src/dev-toolbar/functions/index.tsx | 32 ++- src/dev-toolbar/report/function-report.ts | 13 +- src/dev-toolbar/report/report.test.ts | 19 ++ tests/e2e/devtools.spec.ts | 20 ++ tests/fixture/app.tsx | 23 +++ 10 files changed, 231 insertions(+), 161 deletions(-) create mode 100644 .changeset/rich-args-uploads.md diff --git a/.changeset/rich-args-uploads.md b/.changeset/rich-args-uploads.md new file mode 100644 index 0000000..08a6e78 --- /dev/null +++ b/.changeset/rich-args-uploads.md @@ -0,0 +1,7 @@ +--- +'@solidjs/start-devtools': patch +--- + +Show the body of server function calls that send files. + +With `enableRichArguments()` in `@solidjs/web` 2.0.0-rc.20, a call whose arguments hold a `File` or `Blob` sends multipart form data marked `X-Server-Function-Format: 0`. The panel read that body as a seroval stream and showed no body. It now shows the arguments in the seroval tree and the files in a form data table. Reports copied for an agent list both. diff --git a/package.json b/package.json index 604e74c..540f304 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,9 @@ "@changesets/cli": "^2.30.0", "@jridgewell/trace-mapping": "^0.3.31", "@playwright/test": "^1.62.1", - "@solidjs/compiler": "2.0.0-rc.13", - "@solidjs/vite-plugin": "3.0.0-next.46", - "@solidjs/web": "^2.0.0-rc.13", + "@solidjs/compiler": "2.0.0-rc.20", + "@solidjs/vite-plugin": "3.0.0-next.55", + "@solidjs/web": "^2.0.0-rc.20", "@twinkleplop/annotation": "^0.1.5", "@twinkleplop/theme-github": "^0.2.2", "@twinkleplop/tsx": "^0.1.5", @@ -75,7 +75,7 @@ "rolldown": "^1.2.4", "rolldown-plugin-dts": "^0.28.2", "seroval": "^1.6.0", - "solid-js": "^2.0.0-rc.13", + "solid-js": "^2.0.0-rc.20", "terracotta": "2.0.0-next.9", "typescript": "^7.0.2", "vite": "^8.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9cb2fd..ff8929d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,14 +21,14 @@ importers: specifier: ^1.62.1 version: 1.62.1 '@solidjs/compiler': - specifier: 2.0.0-rc.13 - version: 2.0.0-rc.13 + specifier: 2.0.0-rc.20 + version: 2.0.0-rc.20 '@solidjs/vite-plugin': - specifier: 3.0.0-next.46 - version: 3.0.0-next.46(@solidjs/web@2.0.0-rc.13(solid-js@2.0.0-rc.13))(solid-js@2.0.0-rc.13)(supports-color@7.2.0)(vite@8.2.1(@types/node@24.13.3)) + specifier: 3.0.0-next.55 + version: 3.0.0-next.55(@solidjs/web@2.0.0-rc.20(solid-js@2.0.0-rc.20))(solid-js@2.0.0-rc.20)(supports-color@7.2.0)(vite@8.2.1(@types/node@24.13.3)) '@solidjs/web': - specifier: ^2.0.0-rc.13 - version: 2.0.0-rc.13(solid-js@2.0.0-rc.13) + specifier: ^2.0.0-rc.20 + version: 2.0.0-rc.20(solid-js@2.0.0-rc.20) '@twinkleplop/annotation': specifier: ^0.1.5 version: 0.1.5 @@ -66,11 +66,11 @@ importers: specifier: ^1.6.0 version: 1.6.2 solid-js: - specifier: ^2.0.0-rc.13 - version: 2.0.0-rc.13 + specifier: ^2.0.0-rc.20 + version: 2.0.0-rc.20 terracotta: specifier: 2.0.0-next.9 - version: 2.0.0-next.9(@solidjs/web@2.0.0-rc.13(solid-js@2.0.0-rc.13))(solid-js@2.0.0-rc.13) + version: 2.0.0-next.9(@solidjs/web@2.0.0-rc.20(solid-js@2.0.0-rc.20))(solid-js@2.0.0-rc.20) typescript: specifier: ^7.0.2 version: 7.0.2 @@ -539,95 +539,61 @@ packages: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} - '@solidjs/babel-plugin@2.0.0-rc.11': - resolution: {integrity: sha512-k6zozcTYMLGG5FSaUcNIFTjPvFEHHpt8vFq5pID1Se0g4eDCP+aoznbthFZ1khCSqJoR90qr/ZY8g/Uk3PmmJw==} + '@solidjs/babel-plugin@2.0.0-rc.20': + resolution: {integrity: sha512-/5GHINAOaUK+99N7qxkOgcBU2Fn9WhNfaJDg+q/M2gF9fmFqnQOiV+J8gU9gsnEHfiqZvH2vQ4WHxA0kl2OC5g==} peerDependencies: - '@babel/core': ^7.20.12 + '@babel/core': ^7.20.12 || ^8.0.0 '@tsrx/core': 0.1.63 peerDependenciesMeta: '@tsrx/core': optional: true - '@solidjs/compiler-darwin-arm64@2.0.0-rc.11': - resolution: {integrity: sha512-8CsvcsoISpYCp393opTC1GzmGZgia1rVI774oqQlzbjaRA7qi9EFP64SJT7ZIDENbNUqNEiHzIxbAPbL0hgtpg==} + '@solidjs/compiler-darwin-arm64@2.0.0-rc.20': + resolution: {integrity: sha512-3bpcQmjNkzO0sthOEypc1Rmq+tfW9zVjmSSJsoHTl3MUDnxi65L7mHsPfIdo4nQntHqAps4ZEUFy77GqqocHDA==} cpu: [arm64] os: [darwin] - '@solidjs/compiler-darwin-arm64@2.0.0-rc.13': - resolution: {integrity: sha512-uG3Sa/2jNZNxoKkcHjHw3KZEEuwDrRAk9aqUU2qqJZl9GDLLI0HVW7i/Lvq9b17f5yN+fNTdNyojO8rLgHnQuQ==} - cpu: [arm64] - os: [darwin] - - '@solidjs/compiler-darwin-x64@2.0.0-rc.11': - resolution: {integrity: sha512-NwY3vbBahhSgqKEXxDnNOTZMOTttvNoU3lB18bSNWY+CVqIMLJaJ2emVpqdWfUtBuB2+oPW2x4yTAhY1XyhPgA==} - cpu: [x64] - os: [darwin] - - '@solidjs/compiler-darwin-x64@2.0.0-rc.13': - resolution: {integrity: sha512-jIyzyn/fYHmzikOaskiqmkc8FusXMaxl1Z6huh7lEeHaZsl9NIdzzAIxq8j/EJFByvoCyfQitggRTuubGNblpA==} + '@solidjs/compiler-darwin-x64@2.0.0-rc.20': + resolution: {integrity: sha512-I2RI6oJkVxMUDPM6faIpd+rWK1hkpw+KlSfE8ZZnDeX+FKa+VlSHCk4EA16jHSCeo1IKxmW3x9cCSlHqzEZKvQ==} cpu: [x64] os: [darwin] - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.11': - resolution: {integrity: sha512-jYQWzcR7nt11ddgbjR3CY5PX6ZZaYr4sHd8evBjEGum8QP/gnMd0ygCA50vipwn4OBWAvdzMGOpHivOXZc0LJw==} + '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.20': + resolution: {integrity: sha512-41KS1kvbhzQwE3ZEIgpAghVO6LVDVS98URL3rXOARudwTpaflEehNlxSH0RGr6E0UbaeFVfadkFnBOcuSLrMJg==} cpu: [arm64] os: [linux] libc: [glibc] - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.13': - resolution: {integrity: sha512-cGIX7ZWTyw3Qdpd5ZqJtHdb2vQkAtZlkTgy919dMldjoRqDwQIRlqCmH35kaH/IUBlhuG224BoP4twqK9GYyGw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.11': - resolution: {integrity: sha512-qzvH1SUthHMmJE97K79+z0qDaMVCjlSaJzkh2cS03cyRcIYmRx6C2BShKGGCd/GRiX7e+F5S0fmNR16Htf7/lQ==} + '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.20': + resolution: {integrity: sha512-YXb8f0HQqbW/YQvRhflSW1yd2sCdi22wIe7pRAdA6YuthnbJbOhxpUw4iBX/dis59aIE5uw8cg8fh8hH4iBpmg==} cpu: [x64] os: [linux] libc: [glibc] - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.13': - resolution: {integrity: sha512-Dnft4iic4zHsemNSBGMwOMjN4fP0DEoyfeSUWbqBk1CMsXRarLTcMKhjNQmJvL0opzjOUq0/m7/17LTe6AKpvg==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.11': - resolution: {integrity: sha512-UL6iXQD1ScUXI0N63Pr01GM1L5f5H+vXoHklYJOFw8slGGiUA+ZtaNl6BRpqR4AcYfzuIHIF6N9/bowVHsgTcQ==} - engines: {node: '>=14.0.0'} - - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.13': - resolution: {integrity: sha512-nNOMOFxanzfM2wNbSXyekMtGazyOcM3ja+P7tk0NJYTAo2MYmRTY/HUDdFRDluk6Nc5aehu87I7AVMRj5l1WXw==} + '@solidjs/compiler-wasm32-wasi@2.0.0-rc.20': + resolution: {integrity: sha512-V3ffCekMXznz3iMn08lrkipK9XzTbZumF5Wb3Xd1cMj+caCidz9AMAN0ZBx5XQ3dwyrZ3UfZM/gSkAOcq8LgHw==} engines: {node: '>=14.0.0'} - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.11': - resolution: {integrity: sha512-S0xBobAcvkbwE63h5L96avb2YC2oqF74WeQZEExBrKXr9vNPBDX7goikO1Mrj8zTzu2ASZ/fXo7ojInQ4DdDcw==} + '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.20': + resolution: {integrity: sha512-GBfYKI5WtIOk0SvYn70PeN6iDpqwJ8mQ9UdT/MxUnBE2UcIvyOv7+g+5mOBqfIHODWnsbLkzf9lk+JCw5gkogg==} cpu: [x64] os: [win32] - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.13': - resolution: {integrity: sha512-1DT+5TgIFV5L8GdrGb+uD9ruHFVq6kJ4A/cb9amUU574TGM88B4nwy//dM57zmT99u7mej7JF91glQBnHOGhHQ==} - cpu: [x64] - os: [win32] - - '@solidjs/compiler@2.0.0-rc.11': - resolution: {integrity: sha512-z9a1dMIjlBJzXlHxRXjb7UoXzWDnIx+VhiokITomrjTGTG6wANLNWKcyyvB0KMRv8n2HHB1Mir7oMZQw11Mdfw==} + '@solidjs/compiler@2.0.0-rc.20': + resolution: {integrity: sha512-veV/ZXim3UeX5Klelp3DiPwseR01/EX/K3i0iVdi+9EZfoRVVzoTIP7lTXP8hTng5/MsFaiZ4mqbDt/57IWHlg==} - '@solidjs/compiler@2.0.0-rc.13': - resolution: {integrity: sha512-OveR/xhLKo8MTJO/53jgjHuBW54c6/a9B/jiSjDrBeQ3JSbODUxQ8t58WBSlHaxyowPd27TwhVkLo3zeT1YqMQ==} - - '@solidjs/signals@2.0.0-rc.13': - resolution: {integrity: sha512-4+pRdrAHtfyE9BUJWBup3TpzJojJjgW2mV1vm/Jik4tWa5epxXB/YrLkwqP1v8+S9XjyKKZu5BSLqmcpswMYeQ==} + '@solidjs/signals@2.0.0-rc.20': + resolution: {integrity: sha512-BIL5dKm0dAtPGsCEuLSNw60spDbmYG5QFK+aHUxPIgPLweA8IeJKd62DiqqqU/jqX0x39ty31GoP1n+ZINkrcg==} engines: {node: '>=22.12.0'} - '@solidjs/vite-plugin@3.0.0-next.46': - resolution: {integrity: sha512-iKVgtv5xroO6FgNkt6jYQYUsjlk6luv4EsXuSwzSP1Wslj9tKN7X9bWMHBq9EtlOtNCTRb+e579mmL+6c1xkpw==} + '@solidjs/vite-plugin@3.0.0-next.55': + resolution: {integrity: sha512-jMR8P9O+0qYW05xpQgLYRsHjgdSHyZRCXqgHhT3QpO63eXWt3H4tIwONxmm/r47Sed9Prk8N7EnMX2byro+FTA==} engines: {node: '>=22.12.0'} peerDependencies: '@solidjs/start-devtools': ^1.0.0-next.2 - '@solidjs/web': ^2.0.0-rc.10 + '@solidjs/web': ^2.0.0-rc.19 '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.0.0 || ^7.0.0 - solid-js: ^2.0.0-rc.10 + solid-js: ^2.0.0-rc.19 vite: ^8.0.0 || ^9.0.0 peerDependenciesMeta: '@solidjs/start-devtools': @@ -635,11 +601,11 @@ packages: '@testing-library/jest-dom': optional: true - '@solidjs/web@2.0.0-rc.13': - resolution: {integrity: sha512-vI/7v/XM8B/3U/oKAzCW2VjTLFkM5IArB2DFnrjxyPRYD3FX9KAXg9QuAnPIPQHC/xvg/LgxmvoVfZUfJ96gOQ==} + '@solidjs/web@2.0.0-rc.20': + resolution: {integrity: sha512-ACGUZ1XBNxiHnWW274na+1JZfuIhiS/BN5XM72wVKNYSopR/NyjVPR4O/nvshtbYlEk1lKJ9d1uBaXDaz6ddYg==} engines: {node: '>=22.12.0'} peerDependencies: - solid-js: ^2.0.0-rc.13 + solid-js: ^2.0.0-rc.20 '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1640,9 +1606,9 @@ packages: engines: {node: '>=10'} hasBin: true - seroval-plugins@1.6.8: - resolution: {integrity: sha512-N7mWAMydj89EnYTHtRpS3LBrAz3J9O6oVrqpuXuvlAHfAUG8WEKqtbAd7SBiJNY5NfCAFhSdyAh7wI4Shjqy4g==} - engines: {node: '>=10'} + seroval-plugins@1.7.0: + resolution: {integrity: sha512-WM4rArfPtLZriTuxC5DBvNulEwAaRcjNe/dkMOEesOcuvXf5nEwKf5Bw3AT92WHohqcc45+57GdtsCuBTI0mjw==} + engines: {node: '>=20'} peerDependencies: seroval: ^1.0 @@ -1650,9 +1616,9 @@ packages: resolution: {integrity: sha512-mPT+SD2TrlB6wvte1KkYOYUkubaTbd6pZ/6Kk3C9nxzrHmCZyhxOO7XGAeL7f+yLKZglzGtM9odUVvg/EhO+vQ==} engines: {node: '>=10'} - seroval@1.6.8: - resolution: {integrity: sha512-HlSgSAkTk4EqHcje1ptJjfZi1YDv5KbhVJ/d3P7T/nAXua2VmDu+AKDX5VTdFfZf48nDkWB2TKYt0DrCSa+3wg==} - engines: {node: '>=10'} + seroval@1.7.0: + resolution: {integrity: sha512-FU+LUkmDzWNsJ6oubZFN3dGPKny53DRhPJG6nftEaXDL8MIn19+kEE1Gvc+63Go+qPOwVUlKIdKhsu2HN6tksw==} + engines: {node: '>=20'} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -1677,8 +1643,8 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - solid-js@2.0.0-rc.13: - resolution: {integrity: sha512-62bYOI4JZ15KOqL5eReKyWSwAXrGb0fbX8SDnHsbJk2UX+SzSyy1gobas2cxW/0BXcobGfQ1V6ffPyQkMIBdoQ==} + solid-js@2.0.0-rc.20: + resolution: {integrity: sha512-FxaEI7LHupehFoR9j2CfJAGCoRgiKgeNlzii4szUS6lnD8RkpdtvX1k6WFG71vl5bskXRRsmEQVAXsbIemQOuw==} engines: {node: '>=22.12.0'} solid-use@1.0.0-next.3: @@ -2413,7 +2379,7 @@ snapshots: '@sindresorhus/is@4.6.0': {} - '@solidjs/babel-plugin@2.0.0-rc.11(@babel/core@7.29.7(supports-color@7.2.0))': + '@solidjs/babel-plugin@2.0.0-rc.20(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-module-imports': 7.18.6 @@ -2422,91 +2388,61 @@ snapshots: html-entities: 2.3.3 parse5: 7.3.0 - '@solidjs/compiler-darwin-arm64@2.0.0-rc.11': - optional: true - - '@solidjs/compiler-darwin-arm64@2.0.0-rc.13': - optional: true - - '@solidjs/compiler-darwin-x64@2.0.0-rc.11': - optional: true - - '@solidjs/compiler-darwin-x64@2.0.0-rc.13': - optional: true - - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.11': + '@solidjs/compiler-darwin-arm64@2.0.0-rc.20': optional: true - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.13': + '@solidjs/compiler-darwin-x64@2.0.0-rc.20': optional: true - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.11': + '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.20': optional: true - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.13': + '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.20': optional: true - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.11': + '@solidjs/compiler-wasm32-wasi@2.0.0-rc.20': dependencies: '@emnapi/core': 1.11.3 '@emnapi/runtime': 1.11.3 '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) optional: true - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.13': - dependencies: - '@emnapi/core': 1.11.3 - '@emnapi/runtime': 1.11.3 - '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - optional: true - - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.11': - optional: true - - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.13': + '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.20': optional: true - '@solidjs/compiler@2.0.0-rc.11': + '@solidjs/compiler@2.0.0-rc.20': optionalDependencies: - '@solidjs/compiler-darwin-arm64': 2.0.0-rc.11 - '@solidjs/compiler-darwin-x64': 2.0.0-rc.11 - '@solidjs/compiler-linux-arm64-gnu': 2.0.0-rc.11 - '@solidjs/compiler-linux-x64-gnu': 2.0.0-rc.11 - '@solidjs/compiler-wasm32-wasi': 2.0.0-rc.11 - '@solidjs/compiler-win32-x64-msvc': 2.0.0-rc.11 - - '@solidjs/compiler@2.0.0-rc.13': - optionalDependencies: - '@solidjs/compiler-darwin-arm64': 2.0.0-rc.13 - '@solidjs/compiler-darwin-x64': 2.0.0-rc.13 - '@solidjs/compiler-linux-arm64-gnu': 2.0.0-rc.13 - '@solidjs/compiler-linux-x64-gnu': 2.0.0-rc.13 - '@solidjs/compiler-wasm32-wasi': 2.0.0-rc.13 - '@solidjs/compiler-win32-x64-msvc': 2.0.0-rc.13 + '@solidjs/compiler-darwin-arm64': 2.0.0-rc.20 + '@solidjs/compiler-darwin-x64': 2.0.0-rc.20 + '@solidjs/compiler-linux-arm64-gnu': 2.0.0-rc.20 + '@solidjs/compiler-linux-x64-gnu': 2.0.0-rc.20 + '@solidjs/compiler-wasm32-wasi': 2.0.0-rc.20 + '@solidjs/compiler-win32-x64-msvc': 2.0.0-rc.20 - '@solidjs/signals@2.0.0-rc.13': {} + '@solidjs/signals@2.0.0-rc.20': {} - '@solidjs/vite-plugin@3.0.0-next.46(@solidjs/web@2.0.0-rc.13(solid-js@2.0.0-rc.13))(solid-js@2.0.0-rc.13)(supports-color@7.2.0)(vite@8.2.1(@types/node@24.13.3))': + '@solidjs/vite-plugin@3.0.0-next.55(@solidjs/web@2.0.0-rc.20(solid-js@2.0.0-rc.20))(solid-js@2.0.0-rc.20)(supports-color@7.2.0)(vite@8.2.1(@types/node@24.13.3))': dependencies: '@ampproject/remapping': 2.3.0 '@babel/core': 7.29.7(supports-color@7.2.0) - '@solidjs/babel-plugin': 2.0.0-rc.11(@babel/core@7.29.7(supports-color@7.2.0)) - '@solidjs/compiler': 2.0.0-rc.11 - '@solidjs/web': 2.0.0-rc.13(solid-js@2.0.0-rc.13) + '@solidjs/babel-plugin': 2.0.0-rc.20(@babel/core@7.29.7(supports-color@7.2.0)) + '@solidjs/compiler': 2.0.0-rc.20 + '@solidjs/web': 2.0.0-rc.20(solid-js@2.0.0-rc.20) '@types/babel__core': 7.20.5 + magic-string: 0.30.21 merge-anything: 5.1.7 - solid-js: 2.0.0-rc.13 + solid-js: 2.0.0-rc.20 vite: 8.2.1(@types/node@24.13.3) vitefu: 1.1.3(vite@8.2.1(@types/node@24.13.3)) transitivePeerDependencies: - '@tsrx/core' - supports-color - '@solidjs/web@2.0.0-rc.13(solid-js@2.0.0-rc.13)': + '@solidjs/web@2.0.0-rc.20(solid-js@2.0.0-rc.20)': dependencies: - seroval: 1.6.8 - seroval-plugins: 1.6.8(seroval@1.6.8) - solid-js: 2.0.0-rc.13 + seroval: 1.7.0 + seroval-plugins: 1.7.0(seroval@1.7.0) + solid-js: 2.0.0-rc.20 '@standard-schema/spec@1.1.0': {} @@ -3313,13 +3249,13 @@ snapshots: semver@7.8.5: {} - seroval-plugins@1.6.8(seroval@1.6.8): + seroval-plugins@1.7.0(seroval@1.7.0): dependencies: - seroval: 1.6.8 + seroval: 1.7.0 seroval@1.6.2: {} - seroval@1.6.8: {} + seroval@1.7.0: {} shebang-command@2.0.0: dependencies: @@ -3337,16 +3273,16 @@ snapshots: slash@3.0.0: {} - solid-js@2.0.0-rc.13: + solid-js@2.0.0-rc.20: dependencies: - '@solidjs/signals': 2.0.0-rc.13 + '@solidjs/signals': 2.0.0-rc.20 csstype: 3.2.3 - seroval: 1.6.8 - seroval-plugins: 1.6.8(seroval@1.6.8) + seroval: 1.7.0 + seroval-plugins: 1.7.0(seroval@1.7.0) - solid-use@1.0.0-next.3(solid-js@2.0.0-rc.13): + solid-use@1.0.0-next.3(solid-js@2.0.0-rc.20): dependencies: - solid-js: 2.0.0-rc.13 + solid-js: 2.0.0-rc.20 source-map-js@1.2.1: {} @@ -3384,11 +3320,11 @@ snapshots: term-size@2.2.1: {} - terracotta@2.0.0-next.9(@solidjs/web@2.0.0-rc.13(solid-js@2.0.0-rc.13))(solid-js@2.0.0-rc.13): + terracotta@2.0.0-next.9(@solidjs/web@2.0.0-rc.20(solid-js@2.0.0-rc.20))(solid-js@2.0.0-rc.20): dependencies: - '@solidjs/web': 2.0.0-rc.13(solid-js@2.0.0-rc.13) - solid-js: 2.0.0-rc.13 - solid-use: 1.0.0-next.3(solid-js@2.0.0-rc.13) + '@solidjs/web': 2.0.0-rc.20(solid-js@2.0.0-rc.20) + solid-js: 2.0.0-rc.20 + solid-use: 1.0.0-next.3(solid-js@2.0.0-rc.20) thenify-all@1.6.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bf588a4..f7af77f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,15 +1,15 @@ minimumReleaseAgeExclude: - solid-use@1.0.0-next.3 - terracotta@2.0.0-next.9 - - '@solidjs/compiler-darwin-arm64@2.0.0-rc.13' - - '@solidjs/compiler-darwin-x64@2.0.0-rc.13' - - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.13' - - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.13' - - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.13' - - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.13' - - '@solidjs/compiler@2.0.0-rc.13' - - '@solidjs/signals@2.0.0-rc.13' - - '@solidjs/web@2.0.0-rc.13' - - solid-js@2.0.0-rc.13 + - '@solidjs/compiler-darwin-arm64@2.0.0-rc.20' + - '@solidjs/compiler-darwin-x64@2.0.0-rc.20' + - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.20' + - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.20' + - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.20' + - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.20' + - '@solidjs/compiler@2.0.0-rc.20' + - '@solidjs/signals@2.0.0-rc.20' + - '@solidjs/web@2.0.0-rc.20' + - solid-js@2.0.0-rc.20 onlyBuiltDependencies: - esbuild diff --git a/src/dev-toolbar/functions/body-format.ts b/src/dev-toolbar/functions/body-format.ts index 3a68b09..4f980da 100644 --- a/src/dev-toolbar/functions/body-format.ts +++ b/src/dev-toolbar/functions/body-format.ts @@ -14,3 +14,27 @@ export const enum BodyFormat { Uint8Array = '7', Json = '8', } + +// A `Serialized` call whose arguments hold files or blobs (`enableRichArguments`) +// goes out as multipart form data: the encoded arguments in this part, each +// file in a part of its own. +export const UPLOAD_ARGS_PART = 'args'; + +/** Whether the body is a `Serialized` argument list sent with its files. */ +export function isUploadBody(headers: Headers): boolean { + return ( + headers.get(BODY_FORMAT_KEY) === BodyFormat.Serialized && + (headers.get('Content-Type') ?? '').startsWith('multipart/form-data') + ); +} + +/** The encoded arguments of an upload body, and the form without them. */ +export async function splitUploadBody( + source: Request | Response, +): Promise<{ args: string; files: FormData }> { + const form = await source.formData(); + const args = form.get(UPLOAD_ARGS_PART); + if (typeof args !== 'string') throw new Error('upload body has no arguments part'); + form.delete(UPLOAD_ARGS_PART); + return { args, files: form }; +} diff --git a/src/dev-toolbar/functions/index.tsx b/src/dev-toolbar/functions/index.tsx index 43477b2..15028da 100644 --- a/src/dev-toolbar/functions/index.tsx +++ b/src/dev-toolbar/functions/index.tsx @@ -1,6 +1,12 @@ import { createMemo, createSignal, For, Loading, Show } from 'solid-js'; import type { JSX } from '@solidjs/web'; -import { BODY_FORMAT_FILE_KEY, BODY_FORMAT_KEY, BodyFormat } from './body-format.js'; +import { + BODY_FORMAT_FILE_KEY, + BODY_FORMAT_KEY, + BodyFormat, + isUploadBody, + splitUploadBody, +} from './body-format.js'; import { Badge } from '../../ui/Badge.js'; import IconButton from '../../ui/IconButton.js'; import Placeholder from '../../ui/Placeholder.js'; @@ -63,6 +69,28 @@ async function getURLSearchParams(source: Response | Request): Promise splitUploadBody(props.source)); + + return ( + + + {(current) => ( + <> + + + + )} + + + ); +} + interface ContentViewerProps { source: ServerFunctionRequest | ServerFunctionResponse; } @@ -77,6 +105,8 @@ function ContentViewer(props: ContentViewerProps): JSX.Element { const startType = source.headers.get(BODY_FORMAT_KEY); const contentType = source.headers.get('Content-Type'); switch (true) { + case isUploadBody(source.headers): + return ; case startType === BodyFormat.Serialized: return ; case startType === BodyFormat.Json: diff --git a/src/dev-toolbar/report/function-report.ts b/src/dev-toolbar/report/function-report.ts index 1349edd..b779a9f 100644 --- a/src/dev-toolbar/report/function-report.ts +++ b/src/dev-toolbar/report/function-report.ts @@ -1,4 +1,10 @@ -import { BODY_FORMAT_FILE_KEY, BODY_FORMAT_KEY, BodyFormat } from '../functions/body-format.js'; +import { + BODY_FORMAT_FILE_KEY, + BODY_FORMAT_KEY, + BodyFormat, + isUploadBody, + splitUploadBody, +} from '../functions/body-format.js'; import type { ServerFunctionRequest, ServerFunctionResponse } from '../functions/tracker.js'; import { code, fence } from './markdown.js'; import { @@ -60,6 +66,11 @@ async function readBody(source: Request | Response): Promise { expect(body?.text).toBe('data: 1\n\n… the rest of the body was not read'); }); + it('reads the arguments and files of an upload', async () => { + // `enableRichArguments` sends arguments holding files as multipart form + // data, still marked `Serialized`. + const form = new FormData(); + form.set('args', await new Response(serovalStream([{ note: 'hi', token: 'abc' }])).text()); + form.set('file0', new Blob(['hello'], { type: 'text/plain' }), 'a.txt'); + const body = await readReportBody( + new Request('http://localhost:3000/_server', { + method: 'POST', + body: form, + headers: { [BODY_FORMAT_KEY]: BodyFormat.Serialized }, + }), + ); + expect(body?.format).toBe('seroval with files'); + expect(body?.text).toContain('[{ note: "hi", token: [redacted] }]'); + expect(body?.text).toContain('file0: '); + expect(body?.text).not.toContain('abc'); + }); + it('says when the request failed', () => { const { request } = call(undefined, {}); const report = serverFunctionReport({ diff --git a/tests/e2e/devtools.spec.ts b/tests/e2e/devtools.spec.ts index 8b02ae5..da63550 100644 --- a/tests/e2e/devtools.spec.ts +++ b/tests/e2e/devtools.spec.ts @@ -240,6 +240,26 @@ test('shows server-function calls', async ({ page }) => { expect(warnings).not.toContainEqual(expect.stringContaining('STRICT_READ_UNTRACKED')); }); +test('shows the arguments and files of an upload call', async ({ page }) => { + const errors: string[] = []; + page.on('pageerror', (error) => errors.push(error.message)); + + await page.goto('/'); + await page.locator('#emit-upload-request').click(); + await openTab(page, 'Server functions'); + await page + .locator('[data-solid-functions-instances] [data-solid-select-option]', { + hasText: 'uploadAvatar', + }) + .click(); + + const detail = page.locator('[data-solid-functions-detail]'); + await expect(detail.locator('[data-solid-seroval-viewer]')).toContainText('note'); + await expect(detail.locator('[data-solid-form-data-viewer]')).toContainText('file0'); + await expect(detail.locator('[data-solid-blob-viewer-name]')).toContainText('avatar.txt'); + expect(errors).toEqual([]); +}); + test('maps the reactivity graph', async ({ page }) => { await page.goto('/'); const nodes = page.locator('[data-solid-reactivity-node]'); diff --git a/tests/fixture/app.tsx b/tests/fixture/app.tsx index 272abab..12c4ba2 100644 --- a/tests/fixture/app.tsx +++ b/tests/fixture/app.tsx @@ -1,5 +1,6 @@ import { render } from '@solidjs/web'; import { DevToolbar, mountDevToolbar, pushServerFunctionCall } from '@solidjs/start-devtools'; +import { serializeString } from '@solidjs/web/server-functions/client'; import { createEffect, createMemo, createSignal, Show } from 'solid-js'; function Broken(): never { @@ -32,6 +33,25 @@ function emitServerFunctionResponse() { responseStatus = 500; } +// A call whose arguments hold a file, as `enableRichArguments` sends it. +async function emitUploadRequest() { + const form = new FormData(); + form.set('args', await serializeString([{ note: 'avatar' }])); + form.set('file0', new Blob(['hello'], { type: 'text/plain' }), 'avatar.txt'); + pushServerFunctionCall({ + type: 'request', + id: 'upload-avatar', + instance: 'upload-avatar-1', + source: new Request('/_server', { + method: 'POST', + body: form, + headers: { 'X-Server-Function-Format': '0' }, + }), + meta: { name: 'uploadAvatar' }, + time: 30, + }); +} + function Greeting(props: { name: string }) { return

{`hello ${props.name}`}

; } @@ -69,6 +89,9 @@ function App() { +