[2.0.0-rc.18] renderToStream: one value seroval cannot serialize leaves every value in its hydration batch unresolved
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 48/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- javascript, node.js, typescript
- Área
- backend
Línea de trabajo
Start by running the supplied repro.mjs with solid-js and @solidjs/web at rc.17 and rc.18, then trace renderToStream and getHydrationWriter in the @solidjs/web source to find where writes are batched and serialized. Check how a serialization failure affects neighboring keys and the nested boundary IDs. Done when an unsupported value leaves unrelated keys and boundary completion flags resolvable, with the repro confirming the behavior.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
On the server, a Loading boundary whose async memo resolves to another Loading boundary (an Element) streams a document in which none of the hydration batch's boundaries ever resolves on rc.18. On rc.17 only the outer memo's own value was left unresolved, and the boundaries themselves completed.
The outer memo's value is the inner boundary's function ($lhSkip), which seroval cannot serialize, and renderToStream logs SerovalUnsupportedTypeError on both versions. On rc.17 that error drops just that key. On rc.18 the same error drops every value written in the same $B batch, including both boundaries' completion flags (_fr) and the inner memo's value, which serialized fine. The client then waits forever on those boundaries.
seroval 1.7.1 forced through overrides behaves like rc.18's pinned 1.7.0, so the change looks like how rc.18's writer batches values, with seroval 1.7 ending a write at its first failing value.
Reproduction
In a fresh directory, install one version pair:
npm install --save-exact [email protected] @solidjs/[email protected]
# Then repeat in a separate directory with both packages at 2.0.0-rc.18.
node --conditions=node repro.mjs
repro.mjs:
import { createComponent, createMemo, Loading } from "solid-js";
import { renderToStream } from "@solidjs/web";
// A Loading boundary whose async memo resolves to a value.
function slot(read, fallback) {
return createComponent(Loading, {
fallback,
get children() {
const value = createMemo(async () => read());
return [() => value()];
}
});
}
const flat = () => slot(async () => "flat-41", "outer");
// The outer memo resolves to an inner Loading boundary (an Element).
const nested = () =>
slot(() => Promise.resolve(1).then(v => slot(async () => `nested-${v * 41}`, "inner")), "outer");
function render(app) {
return new Promise(resolve => {
let text = "";
renderToStream(app).pipe({ write: c => void (text += c), end: () => resolve(text) });
});
}
for (const [name, app] of [["flat", flat], ["nested", nested]]) {
const text = await render(app);
const keys = [...text.matchAll(/"?([\w]+)"?:\$R\[(\d+)\]=\(\$R\[(\d+)\]=/g)].map(m => [m[1], m[3]]);
const settled = new Set([...text.matchAll(/\(\$R\[(\d+)\],/g)].map(m => m[1]));
console.log(`${name}: body ${JSON.stringify(text.slice(0, text.indexOf("<script>")))}`);
for (const [key, ref] of keys) console.log(` ${key}: ${settled.has(ref) ? "resolved" : "NEVER RESOLVED"}`);
}
Output
rc.17:
flat: body "flat-41"
0_fr: resolved
0000: resolved
SerovalUnsupportedTypeError: Seroval Error (specific: 1)
value: [Function (anonymous)] { '$lhSkip': true }
nested: body "nested-41"
0_fr: resolved
0000: NEVER RESOLVED
undefined_fr: resolved
undefined000: resolved
rc.18 (and rc.18 with seroval 1.7.1):
flat: body "flat-41"
0_fr: resolved
0000: resolved
SerovalUnsupportedTypeError: Seroval Error (specific: 1)
value: [Function (anonymous)] { '$lhSkip': true }
nested: body "nested-41"
0_fr: NEVER RESOLVED
0000: NEVER RESOLVED
undefined_fr: NEVER RESOLVED
undefined000: NEVER RESOLVED
The raw rc.18 tail is ..._$HY.r["$B"]=($R[0]={"0_fr":...,"0000":...,undefined_fr:...,undefined000:...});(b=>{...})(_$HY.r["$B"]);</script> with no resolver calls after it, where rc.17 ends with ($R[9],"nested-41");$R[11]($R[2],!0);$R[11]($R[7],!0);.
The same rule reaches values an app writes itself. Two writes in one tick, one resolving to a function, drop the other on rc.18 and stream it on rc.17:
import { renderToStream, getHydrationWriter } from "@solidjs/web";
const app = () => {
const writer = getHydrationWriter();
writer.write("bad", Promise.resolve(() => 10));
writer.write("fine", Promise.resolve("still here"));
return "odd";
};
const text = await new Promise(resolve => {
let t = "";
renderToStream(app).pipe({ write: c => void (t += c), end: () => resolve(t) });
});
console.log(`"still here" streamed: ${text.includes("still here")}`);
This prints true on rc.17 and false on rc.18, after the same SerovalUnsupportedTypeError on both.
Expected
A value that cannot be serialized fails only its own key, as on rc.17, and the boundaries in the same batch still complete. Separately, the inner boundary's keys read undefined_fr and undefined000 on both versions, which looks like a missing id for a boundary created inside a resolved memo.
Environment
Node 26.10.0, solid-js and @solidjs/web 2.0.0-rc.17 and 2.0.0-rc.18 (seroval 1.6.7 and 1.7.0 as each pins), Linux.
- Lenguaje dominante
- TypeScript
- Estrellas
- 36.2k
- Forks
- 1.1k
- Merge medio
- 11 h 19 min
- PR fusionados (30 d)
- 337
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de solidjs/solid
-
`lazy()`: remounting an already-loaded component renders empty (or the Suspense fallback) since 1.9.14Posiblemente ocupada @itisrmk la tomó hace 8 días. Abierto1.x
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Los mantenedores suelen responder en 1 día
-
2.0.0-rc.18 | `await refresh(memo)` hangs in an action after an optimistic write and a dependency changePosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 57/100
Los mantenedores suelen responder en 1 día
-
[2.0] IMMUTABLE_UPDATE_IN_STORE false positive when an in-place `splice` shifts the next item into the removed indexPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 3/5 1-2 días Aptitud para principiantes 55/100
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
-
`createRoot`'s dispose function keeps `fn` alive, so `<For>`/`<Index>` rows retain removed list itemsPosiblemente ocupada @boombang la tomó hace 10 días. Abierto1.x
Dificultad 4/5 1-2 días Aptitud para principiantes 45/100
Los mantenedores suelen responder en 1 día
Todos los issues de solidjs/solid
Issues similares
-
a10y a10y-audit ai-driven-qa claude
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
linagora/twake-directory-manager#219 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día
-
type: bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 79/100
Los mantenedores suelen responder en 1 día
-
0️⃣ phase/new
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
Pulse v7: buildPreferencesFromTelos() bypasses telosSectionOrFile(), renders empty on unified-TELOS installsPosiblemente ocupada @zephyr91 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
danielmiessler/LifeOS#2393 ·
Los mantenedores suelen responder en 4 días