mithril-lynx 0.0.9 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.omo/plans/m-request-fetch-lynx.md +306 -0
- package/.omo/plans/m-route-en-memoria.md +397 -0
- package/.omo/plans/mithril-lynx-v2-desde-cero.md +548 -0
- package/FETCH_INVESTIGATION.md +307 -0
- package/README.md +32 -302
- package/REQUEST.md +71 -0
- package/ROUTE.md +71 -0
- package/package.json +24 -80
- package/plugin.d.ts +2 -31
- package/plugin.js +107 -436
- package/rstest.config.ts +27 -0
- package/src/apply-patch.js +179 -0
- package/src/backends/virtual-backend.js +80 -0
- package/src/background.d.ts +11 -0
- package/src/background.js +79 -0
- package/src/channel.js +41 -0
- package/src/commit.js +67 -0
- package/src/dev-reload-client.js +168 -184
- package/src/dev-transport-noop.js +10 -0
- package/src/fake-dom.js +374 -0
- package/src/main-thread.d.ts +1 -0
- package/src/main-thread.js +68 -0
- package/src/mount-redraw.js +67 -0
- package/src/patch-protocol.js +40 -0
- package/src/reload/version.js +28 -0
- package/src/request.d.ts +37 -0
- package/src/request.js +181 -0
- package/src/route.d.ts +33 -0
- package/src/route.js +207 -0
- package/test/end-to-end.test.ts +86 -0
- package/test/reload-version.test.ts +17 -0
- package/test/request.test.ts +182 -0
- package/test/route-hot-reload.test.ts +40 -0
- package/test/route.test.ts +152 -0
- package/test/setup.ts +25 -0
- package/test/structural-reload.test.ts +95 -0
- package/CONTRACT.md +0 -151
- package/LICENSE +0 -21
- package/background.d.ts +0 -54
- package/background.js +0 -169
- package/element.d.ts +0 -34
- package/element.js +0 -83
- package/gesture.d.ts +0 -40
- package/gesture.js +0 -117
- package/internal/constants.js +0 -26
- package/internal/virtual-node.js +0 -388
- package/list.d.ts +0 -31
- package/list.js +0 -185
- package/main-thread.d.ts +0 -43
- package/main-thread.js +0 -165
- package/navigation.d.ts +0 -35
- package/navigation.js +0 -76
- package/renderer/background.d.ts +0 -21
- package/renderer/background.js +0 -84
- package/renderer/main-thread.d.ts +0 -12
- package/renderer/main-thread.js +0 -175
- package/src/lynx-mithril-shim.d.ts +0 -16
- package/src/lynx-mithril-shim.js +0 -1505
- package/src/worklet-runtime.js +0 -82
- package/testing.d.ts +0 -10
- package/testing.js +0 -91
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# Plan — `m.request` para mithril-lynx-v2: ¿alcanza el `fetch` de Lynx?
|
|
2
|
+
|
|
3
|
+
> Estado (2026-09-18): **Investigación + spike en device COMPLETOS,
|
|
4
|
+
> implementación COMPLETA y verificada en device real.** `src/request.js`
|
|
5
|
+
> + `src/mount-redraw.js` implementados, 24 tests unitarios (fake fetch)
|
|
6
|
+
> pasando, y verificación end-to-end contra `lynx.fetch` real en
|
|
7
|
+
> `mithril-lynx-v2-app` (`src/screens/fetch-demo.ts`) confirmando GET real
|
|
8
|
+
> a `httpbin.org/json` con redraw automático. Investigación completa
|
|
9
|
+
> consolidada en [`FETCH_INVESTIGATION.md`](../../FETCH_INVESTIGATION.md)
|
|
10
|
+
> (incluye un bug de plataforma nuevo, no anticipado en el spike inicial:
|
|
11
|
+
> `lynx.setTimeout`/`requestAnimationFrame` no esperan a que la cola de
|
|
12
|
+
> microtasks drene — §4.6 de ese documento).
|
|
13
|
+
> Idioma: español. Insumo de las 2 tareas solicitadas: (1) cómo se hace
|
|
14
|
+
> fetch en Lynx, (2) si el `fetch` de Lynx cumple lo que `m.request`
|
|
15
|
+
> (spec: <https://mithril.js.org/request.html>) espera. Fuentes: doc
|
|
16
|
+
> oficial de Mithril, doc oficial de Lynx
|
|
17
|
+
> (`lynxjs.org/api/lynx-api/global/fetch.html`), los `.d.ts` instalados de
|
|
18
|
+
> `@lynx-js/types`, el código fuente real de `m.request`
|
|
19
|
+
> (`node_modules/mithril/request/request.js`, 199 líneas), **y las 4
|
|
20
|
+
> preguntas del spike respondidas en un device real conectado** (ver §4 —
|
|
21
|
+
> **los tipos mentían en dos de los cuatro puntos**: `AbortController`/
|
|
22
|
+
> `AbortSignal`/`Headers` SÍ existen y funcionan en runtime pese a no
|
|
23
|
+
> estar declarados en `@lynx-js/types`).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Veredicto (primero, para no enterrarlo)
|
|
28
|
+
|
|
29
|
+
**El gap es más chico de lo que sugerían los tipos instalados —
|
|
30
|
+
cancelación y timeout reales SÍ son posibles.** El spike en device
|
|
31
|
+
(§4) corrigió dos de las cuatro preguntas abiertas en la dirección
|
|
32
|
+
optimista: `@lynx-js/types` no declara `AbortController`, pero el device
|
|
33
|
+
real lo tiene, funciona spec-compliant, y **cancela la conexión de
|
|
34
|
+
verdad** (probado: abort a los 800ms de una respuesta que tarda 5000ms →
|
|
35
|
+
la promesa rechazó a los 805ms, no a los 5000ms — no es un timeout de
|
|
36
|
+
mentira que espera igual). Eso cambia la lista de "imposible" a una lista
|
|
37
|
+
más corta:
|
|
38
|
+
|
|
39
|
+
- `config(xhr) => xhr` (el escape hatch de la API real) — sigue sin
|
|
40
|
+
traducción posible: `fetch` no expone un objeto vivo para mutar a mitad
|
|
41
|
+
de vuelo. Esto no lo arregla el spike.
|
|
42
|
+
- `FormData` como body — confirmado ausente en device (§4). **Corrección**:
|
|
43
|
+
`URLSearchParams` sí está soportado como body — se probó en device
|
|
44
|
+
específicamente porque la doc los agrupaba juntos y ya habíamos visto a
|
|
45
|
+
los tipos/doc equivocarse antes en este mismo documento (no había
|
|
46
|
+
motivo para confiar en la doc sin probar, dado el patrón).
|
|
47
|
+
- `responseType: "blob"` — `Body` en Lynx solo tiene `arrayBuffer()`,
|
|
48
|
+
`json()`, `text()`. Sin `.blob()`.
|
|
49
|
+
- `withCredentials`, `user`/`password` (auth básica vía `xhr.open`),
|
|
50
|
+
`async: false` (modo síncrono) — conceptos exclusivos de
|
|
51
|
+
XMLHttpRequest/browser, sin sentido o sin equivalente en Lynx. Además,
|
|
52
|
+
**`btoa`/`atob` confirmados ausentes en runtime** (spike §4) — armar el
|
|
53
|
+
header `Authorization: Basic` a mano necesitaría una implementación
|
|
54
|
+
propia de base64, no solo un one-liner.
|
|
55
|
+
- **Cancelación y timeout**: YA NO están en la lista de imposibles —
|
|
56
|
+
`AbortController` funciona de verdad (§4). Un `m.request` de v2 puede
|
|
57
|
+
ofrecer cancelación real y un `timeout` que efectivamente corta la
|
|
58
|
+
conexión, no una promesa que se rinde mientras la red sigue trabajando
|
|
59
|
+
de fondo (que era el peor escenario que planteaba la versión anterior de
|
|
60
|
+
este documento).
|
|
61
|
+
|
|
62
|
+
**Recomendación**: implementar un `m.request` de v2 que cubra el caso
|
|
63
|
+
común (lo que de verdad usa el 90% de las apps: JSON in/out, params,
|
|
64
|
+
headers, redraw automático) y **documentar en voz alta, no esconder**,
|
|
65
|
+
que `config`/abort/FormData/blob/timeout/credentials/auth básica no están
|
|
66
|
+
soportados — con una sugerencia explícita de usar el `fetch` nativo de
|
|
67
|
+
Lynx directo para esos casos. No es un "abort total" de la feature, pero
|
|
68
|
+
tampoco es un wrapper que un "mithril fan" pueda usar a ciegas asumiendo
|
|
69
|
+
paridad completa — hay que ser honesto en el primer párrafo del README de
|
|
70
|
+
esta pieza.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 1. Cómo se hace fetch en Lynx
|
|
75
|
+
|
|
76
|
+
- **API real**: `fetch(input, init?)`, descrita como *"subset of Fetch
|
|
77
|
+
API"* — **es la propia doc de tipos oficial la que dice "subconjunto"**,
|
|
78
|
+
no una inferencia mía (`@lynx-js/types/types/background-thread/fetch.d.ts`,
|
|
79
|
+
línea 185).
|
|
80
|
+
- **Solo en el hilo background** — el archivo vive en
|
|
81
|
+
`types/background-thread/`, no en `types/common/` ni `types/main-thread/`.
|
|
82
|
+
Encaja perfecto con la arquitectura de v2: todo el código de vista (y
|
|
83
|
+
por lo tanto cualquier `m.request`) ya corre exclusivamente en
|
|
84
|
+
background (plan `mithril-lynx-v2-desde-cero.md` §3.1) — no hace falta
|
|
85
|
+
ningún puente cross-thread para esto, a diferencia de casi todo lo demás
|
|
86
|
+
en este proyecto.
|
|
87
|
+
- **`RequestInit` real (tipos instalados), completo, sin recortar**:
|
|
88
|
+
```ts
|
|
89
|
+
export interface RequestInit {
|
|
90
|
+
body?: BodyInit | null;
|
|
91
|
+
headers?: HeadersInit;
|
|
92
|
+
method?: string;
|
|
93
|
+
lynxExtension?: { useStreaming?: boolean };
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
Eso es TODO. Compárese con el `RequestInit` real del navegador (que
|
|
97
|
+
tiene además `mode`, `credentials`, `cache`, `redirect`, `referrer`,
|
|
98
|
+
`referrerPolicy`, `integrity`, `keepalive`, `signal`, `window`, ...) —
|
|
99
|
+
Lynx implementa 3 campos más una extensión propia.
|
|
100
|
+
- **`Response`/`Request extends Body`**: `arrayBuffer()`, `json()`,
|
|
101
|
+
`text()`. **No `blob()`.** `Response` tiene `headers`, `ok`, `status`,
|
|
102
|
+
`statusText`, `url`, `body` (ReadableStream), `clone()` — esa parte SÍ
|
|
103
|
+
es fiel al spec real de `Response`.
|
|
104
|
+
- **Ni `Headers` ni `AbortController` están declarados en
|
|
105
|
+
`@lynx-js/types`** — se usan como tipos referenciados
|
|
106
|
+
(`HeadersInit`, futuro `signal`) pero no existen como constructor/clase
|
|
107
|
+
en ningún `.d.ts` del paquete. **Confirmado en device (§4): los tipos
|
|
108
|
+
mienten acá — ambos existen y funcionan en runtime.** Los tipos de
|
|
109
|
+
`@lynx-js/types` están incompletos/desactualizados en este punto, no son
|
|
110
|
+
la fuente de verdad final — un recordatorio general para el resto de
|
|
111
|
+
este proyecto, no solo para `m.request`.
|
|
112
|
+
- Confirmado por la doc oficial en texto plano: *"Lynx does not support
|
|
113
|
+
Web-only features like: CORS, redirect, keepalive related APIs.
|
|
114
|
+
FormData/Blob related APIs are not supported."*
|
|
115
|
+
- **`fetch` NO es un global usable — hay que llamar `lynx.fetch(...)`
|
|
116
|
+
explícito.** Corrección respecto a una suposición anterior de este
|
|
117
|
+
documento: aunque `RuntimeWrapperWebpackPlugin` incluye `"fetch"` en su
|
|
118
|
+
lista `defaultInjectVars`, el spike en device (§4) confirmó
|
|
119
|
+
`typeof fetch === "undefined"` mientras `typeof lynx.fetch ===
|
|
120
|
+
"function"` en el mismo contexto. `indicadores-app` (la app de
|
|
121
|
+
referencia que el usuario señaló) ya usa `lynx.fetch(...)` por esta
|
|
122
|
+
misma razón — coincide con la evidencia del spike, no es una casualidad
|
|
123
|
+
de esa app.
|
|
124
|
+
|
|
125
|
+
## 2. El contrato real de `m.request` (lo que hay que igualar o declarar no soportado)
|
|
126
|
+
|
|
127
|
+
Extraído de `node_modules/mithril/request/request.js` (199 líneas, no la
|
|
128
|
+
doc — el código real):
|
|
129
|
+
|
|
130
|
+
| Opción | Mecanismo real (XHR) | ¿Traducible a `fetch` de Lynx? |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `method`, `url`, `params` | `xhr.open(method, url)` + interpolación de `:params` en la URL | **Sí** — `fetch(url, {method})`; interpolación reusa `mithril-runtime/pathname/build.js` (ya vendorizado para `m.route`, cero código nuevo) |
|
|
133
|
+
| `body` (objeto plano → JSON) | `xhr.send(JSON.stringify(body))` | **Sí** — `body: JSON.stringify(body)` en `RequestInit`, mismo `Content-Type` header |
|
|
134
|
+
| `body` (`FormData`) | `xhr.send(body)` directo | **No** — confirmado ausente en runtime (§4) |
|
|
135
|
+
| `body` (`URLSearchParams`) | `xhr.send(body)` directo | **Sí** — confirmado en device (§4): funciona igual que en un browser real, `Content-Type: application/x-www-form-urlencoded` automático |
|
|
136
|
+
| `headers` | `xhr.setRequestHeader(k, v)` por cada key | **Sí** — `RequestInit.headers` acepta un objeto plano (`HeadersInit`) |
|
|
137
|
+
| `responseType` (`json`/`text`) | `xhr.responseType` + `xhr.response` | **Sí** — `response.json()` / `response.text()` |
|
|
138
|
+
| `responseType: "blob"`/`"document"` | `xhr.responseType = "blob"` | **No** — sin `.blob()` en `Body`; `"document"` no tiene sentido fuera de un DOM de browser |
|
|
139
|
+
| `deserialize` | función sobre `xhr.response` | **Sí** — misma función, aplicada al resultado de `.json()`/`.text()` |
|
|
140
|
+
| `extract` | `(xhr, options) => any`, salta el flujo normal | **Parcial** — hay que cambiar la firma a `(response, options) => any` (recibe el `Response` de fetch, no un XHR) — **rompe código portado literal**, aunque el propósito (post-proceso custom) se preserva |
|
|
141
|
+
| `type` | constructor aplicado al resultado | **Sí**, sin cambios |
|
|
142
|
+
| `background` | si es `true`, salta el redraw automático | **Sí** — es lógica pura nuestra (mount-redraw), no toca XHR/fetch para nada |
|
|
143
|
+
| Forma del error (`error.code`, `.message`, `.response`) | de `xhr.status`/`xhr.responseText` | **Sí** — `response.status`, `response.statusText`, mismo body ya extraído |
|
|
144
|
+
| `config(xhr) => xhr` | mutar el XHR vivo antes de `.send()` | **No hay traducción real** — lo más cercano es un `config(requestInit) => requestInit` que mute el `RequestInit` ANTES de llamar `fetch()`, pero es una firma y un momento distintos; código que use `config` para engancharse a `xhr.onprogress`, reemplazar el XHR, etc. **no tiene forma de portarse** |
|
|
145
|
+
| `timeout` | `xhr.timeout` nativo, aborta la conexión real | **Sí, de verdad** — `AbortController` + `lynx.setTimeout(() => ctrl.abort(), ms)` **cancela la conexión real**, confirmado en device (§4): abort a los 800ms de una respuesta de 5000ms rechazó a los 805ms, no a los 5000ms. Firma distinta a `xhr.timeout` (hay que armar el controller nosotros) pero el resultado observable es el mismo |
|
|
146
|
+
| Cancelación (`.abort()` vía `config`) | `xhr.abort()` | **Sí** — `AbortController`/`AbortSignal` existen y funcionan en runtime pese a no estar en `@lynx-js/types` (confirmado en device, §4). `m.request` de v2 puede exponer su propio `.abort()`/aceptar un `signal` propio |
|
|
147
|
+
| `withCredentials` | `xhr.withCredentials = true` (cookies cross-origin) | **No aplica** — Lynx no tiene modelo de origen/CORS; la opción quedaría como no-op silencioso si se acepta tal cual |
|
|
148
|
+
| `user`/`password` | pasados a `xhr.open(...)`, auth básica HTTP | **No hay equivalente directo** — habría que armar el header `Authorization: Basic ...` a mano, y eso requiere `btoa()`, cuya disponibilidad en Lynx no está confirmada (no se investigó en esta sesión) |
|
|
149
|
+
| `async: false` (modo síncrono) | `xhr.open(..., false, ...)` | **Imposible** — no existe un `fetch` síncrono en ningún entorno, browser o Lynx |
|
|
150
|
+
|
|
151
|
+
## 3. No-objetivos explícitos (documentados, no escondidos)
|
|
152
|
+
|
|
153
|
+
Si se implementa (fase futura, no esta sesión), el `README`/`.d.ts` de
|
|
154
|
+
`mithril-lynx-v2/request` debe decir, en la primera pantalla, sin que haga
|
|
155
|
+
falta buscarlo:
|
|
156
|
+
|
|
157
|
+
- `config` cambia de firma (`RequestInit`, no `XMLHttpRequest`) — código
|
|
158
|
+
portado de un `m.request` real que use `config` para algo más que setear
|
|
159
|
+
un header necesita revisión manual, no es un cambio de import.
|
|
160
|
+
- Sin soporte de `FormData`/`Blob` — cualquier caso de subida de archivos
|
|
161
|
+
o multipart queda fuera de alcance por completo. (`URLSearchParams` SÍ
|
|
162
|
+
está soportado — confirmado en device, §4 — así que un body tipo
|
|
163
|
+
formulario simple `key=value` no cae en esta lista.)
|
|
164
|
+
- Sin `withCredentials` (no aplica, Lynx no tiene modelo de CORS/origen) y
|
|
165
|
+
sin `user`/`password` inline — si hace falta auth básica, armar el
|
|
166
|
+
header `Authorization` a mano (y una función de base64 propia, ya que
|
|
167
|
+
`btoa` no existe — confirmado en device, §4).
|
|
168
|
+
- Sin modo síncrono (`async: false`) — no existe en ningún fetch, browser
|
|
169
|
+
o Lynx.
|
|
170
|
+
- `response.url`/`response.redirected` no reflejan la URL final tras un
|
|
171
|
+
redirect (confirmado en device, §4) — si el código de la app depende de
|
|
172
|
+
saber a qué URL terminó yendo la request tras redirects, esto no
|
|
173
|
+
funciona igual que en un browser real.
|
|
174
|
+
- `Headers.get()` no confirmado como confiable para LEER de vuelta
|
|
175
|
+
(ver §4, hallazgo extra) — construir/enviar headers con `new Headers()`
|
|
176
|
+
sí funciona.
|
|
177
|
+
|
|
178
|
+
**Ya NO son no-objetivos** (el spike de §4 los movió a la columna de "sí
|
|
179
|
+
se puede"): cancelación de requests en vuelo, y `timeout` con corte real
|
|
180
|
+
de la conexión — ambos funcionan de verdad vía `AbortController`.
|
|
181
|
+
|
|
182
|
+
## 4. Spike en device — las 4 preguntas, respondidas (2026-09-18, adb R8YYC0VV0PV)
|
|
183
|
+
|
|
184
|
+
Corrido con `agent-lynx evaluate` contra el background thread de
|
|
185
|
+
`mithril-lynx-v2-app` en el device conectado — no contra
|
|
186
|
+
`indicadores-app` (el usuario señaló esa app como referencia de que
|
|
187
|
+
"fetch funciona ahí", y es cierto, pero solo ejercita el camino feliz
|
|
188
|
+
básico: `lynx.fetch(url)` + `.ok` + `.json()`, sin tocar ninguna de las
|
|
189
|
+
4 preguntas de este spike).
|
|
190
|
+
|
|
191
|
+
**Nota de herramienta**: `agent-lynx evaluate` espera una única
|
|
192
|
+
EXPRESIÓN, no una secuencia de sentencias con `;` — expresiones con
|
|
193
|
+
`;` al nivel superior tiran `SyntaxError: expecting ')'` desde el wrapper
|
|
194
|
+
interno de la herramienta. Solución: envolver todo en un IIFE
|
|
195
|
+
`(function(){ ...sentencias...; return valor; })()`, que sí es una sola
|
|
196
|
+
expresión. Documentado acá para la próxima sesión que use este spike.
|
|
197
|
+
|
|
198
|
+
1. **¿Existe `Headers`/`AbortController`/`AbortSignal` en runtime pese a
|
|
199
|
+
no estar en `@lynx-js/types`?** `typeof Headers` → `"function"`,
|
|
200
|
+
`typeof AbortController` → `"function"`, `typeof AbortSignal` →
|
|
201
|
+
`"function"`. **Los tres existen.** Los tipos instalados están
|
|
202
|
+
incompletos en este punto, no son la fuente de verdad final.
|
|
203
|
+
2. **¿Qué pasa con un 3xx?** `lynx.fetch("https://httpbin.org/redirect-to?url=https://example.com")`
|
|
204
|
+
devolvió `status: 200`, `ok: true`, y el body fue el HTML real de
|
|
205
|
+
`example.com` — **Lynx sigue el redirect solo**, transparente, como un
|
|
206
|
+
browser real. Pero `response.url` quedó con la URL ORIGINAL
|
|
207
|
+
(`httpbin.org/redirect-to?...`), no la final — y `response.redirected`
|
|
208
|
+
vino `undefined`. El redirect en sí funciona; los metadatos sobre el
|
|
209
|
+
redirect no son confiables.
|
|
210
|
+
3. **¿Existen `btoa`/`atob`?** `typeof btoa` → `"undefined"`, `typeof atob`
|
|
211
|
+
→ `"undefined"`. **Confirmado ausentes.** Un helper de auth básica
|
|
212
|
+
necesitaría una implementación propia de base64.
|
|
213
|
+
4. **¿`AbortController` funciona de verdad (no solo existe)?** Sí,
|
|
214
|
+
spec-compliant: `ctrl.abort()` inmediato → la promesa rechaza con
|
|
215
|
+
`{name: "AbortError", message: "This operation was aborted"}`.
|
|
216
|
+
Prueba más fuerte — abort a mitad de vuelo: `lynx.fetch(".../delay/5",
|
|
217
|
+
{signal: ctrl.signal})` + `lynx.setTimeout(() => ctrl.abort(), 800)` →
|
|
218
|
+
la promesa rechazó a los **805ms**, no a los 5000ms del delay real del
|
|
219
|
+
servidor — **la conexión se cortó de verdad**, no fue una promesa que
|
|
220
|
+
se rindió mientras la red seguía trabajando.
|
|
221
|
+
|
|
222
|
+
**Hallazgo extra, no una de las 4 preguntas originales pero relevante**:
|
|
223
|
+
`fetch` global (bare, sin `lynx.`) es `undefined` — hay que llamar
|
|
224
|
+
`lynx.fetch(...)` siempre. Se había asumido lo contrario en una versión
|
|
225
|
+
anterior de este documento (por estar `"fetch"` en la lista
|
|
226
|
+
`defaultInjectVars` de `RuntimeWrapperWebpackPlugin`) — el spike lo
|
|
227
|
+
corrigió. Coincide con que `indicadores-app` (la app que el usuario
|
|
228
|
+
señaló) ya usa `lynx.fetch(...)` explícito, no por casualidad.
|
|
229
|
+
|
|
230
|
+
Segundo hallazgo extra, **re-confirmado con un test limpio y deliberado**
|
|
231
|
+
(el primer intento coincidió con un corte de sesión — pantalla del device
|
|
232
|
+
bloqueada — y no era confiable por sí solo, así que se repitió antes de
|
|
233
|
+
darlo por bueno): `new Headers({...})` pasado a `fetch(url, {headers})`
|
|
234
|
+
**sí llega bien al servidor** — pero **`Headers.get()`/`.has()` son
|
|
235
|
+
case-*sensitive*** en Lynx: `h.set("X-Test", "abc"); h.get("x-test")` →
|
|
236
|
+
`null`; `h.has("x-test")` → `false`. El spec real de `Headers` es
|
|
237
|
+
explícitamente case-**in**sensitive (`Content-Type` y `content-type` son
|
|
238
|
+
la misma clave) — esta es una desviación real y confirmada, no un
|
|
239
|
+
artefacto de sesión. Construir/enviar headers funciona perfecto; leer de
|
|
240
|
+
vuelta con la clave en otra capitalización que la usada para escribir,
|
|
241
|
+
no.
|
|
242
|
+
|
|
243
|
+
**Tercer hallazgo extra**: aunque `FormData` está confirmado ausente
|
|
244
|
+
(`typeof FormData` → `"undefined"`), **`URLSearchParams` SÍ existe y
|
|
245
|
+
funciona como body real** — `lynx.fetch(url, {method:"POST", body: new
|
|
246
|
+
URLSearchParams({foo:"bar"})})` llegó al servidor como
|
|
247
|
+
`application/x-www-form-urlencoded` con el `Content-Type` seteado
|
|
248
|
+
automático y los campos bien parseados (`{foo:"bar"}`). Esto **mejora**
|
|
249
|
+
la fila de la tabla §2 sobre `body` no-JSON: la mitad de esa opción real
|
|
250
|
+
de `m.request` (`FormData`) sigue sin soporte, pero la otra mitad
|
|
251
|
+
(`URLSearchParams`) **sí se puede replicar fiel**, sin necesitar
|
|
252
|
+
`JSON.stringify` ni tocar el `Content-Type` a mano.
|
|
253
|
+
|
|
254
|
+
## 5. Si se decide implementar: forma concreta (fase futura, no esta sesión)
|
|
255
|
+
|
|
256
|
+
Mismo patrón que `mithril-lynx-v2/route`: un subpath export nuevo
|
|
257
|
+
(`mithril-lynx-v2/request`), NO agregado al core (`renderApp`) — es
|
|
258
|
+
opcional, un consumidor que no lo importe no paga nada por él.
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
// forma esperada, análoga a m.request real
|
|
262
|
+
import request from "mithril-lynx-v2/request";
|
|
263
|
+
|
|
264
|
+
request("/api/users/:id", { params: { id: 42 } })
|
|
265
|
+
.then((user) => { ... });
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Reutiliza `mithril-runtime/pathname/build.js` (ya vendorizado para
|
|
269
|
+
`m.route`) para la interpolación de `:params` en la URL — mismo motor,
|
|
270
|
+
cero código nuevo ahí. El resto (armar `RequestInit`, llamar `fetch`,
|
|
271
|
+
aplicar `deserialize`/`extract`/`type`, construir el error con
|
|
272
|
+
`response.status`) es código nuevo pero acotado — el propio
|
|
273
|
+
`request/request.js` real (199 líneas) da la plantilla exacta de qué
|
|
274
|
+
casos cubrir, con las opciones de la tabla de §2 marcadas como
|
|
275
|
+
"soportado"/"no soportado" desde el día uno.
|
|
276
|
+
|
|
277
|
+
**Integración con redraw**: igual que `m.route`, el flag `background`
|
|
278
|
+
decide si se llama al único `performRender`/commit de la app
|
|
279
|
+
(`background.js`) al resolver la promesa — reusa el mismo mecanismo,
|
|
280
|
+
no inventa un segundo camino de redraw.
|
|
281
|
+
|
|
282
|
+
**Cancelación/timeout** (nuevo respecto a la versión anterior de este
|
|
283
|
+
plan, gracias al spike de §4): cada llamada arma su propio
|
|
284
|
+
`AbortController` internamente. `timeout` (si se pasa) hace
|
|
285
|
+
`lynx.setTimeout(() => ctrl.abort(), timeout)` y limpia el timer si la
|
|
286
|
+
promesa ya resolvió. La función devuelta por `request(...)` puede colgar
|
|
287
|
+
un `.abort()` propio (no existe en la promesa real de mithril, pero es
|
|
288
|
+
gratis tenerlo acá y es justo lo que un "mithril fan" pediría después de
|
|
289
|
+
la falta de `config`) que llama `ctrl.abort()` directo — capacidad nueva
|
|
290
|
+
que ni el `m.request` real ofrece de forma tan directa (ahí hay que pasar
|
|
291
|
+
por `config` para llegar al `xhr.abort()`).
|
|
292
|
+
|
|
293
|
+
## 6. Referencias
|
|
294
|
+
|
|
295
|
+
- Spec real de `m.request`: <https://mithril.js.org/request.html>
|
|
296
|
+
- `fetch` de Lynx (doc): <https://lynxjs.org/api/lynx-api/global/fetch.html>
|
|
297
|
+
- Tipos reales instalados (evidencia, no doc):
|
|
298
|
+
`@lynx-js/types/types/background-thread/fetch.d.ts`,
|
|
299
|
+
`@lynx-js/types/types/background-thread/lynx.d.ts`
|
|
300
|
+
- Código fuente real de `m.request`:
|
|
301
|
+
`node_modules/mithril/request/request.js` (mithril 2.3.8, mismo
|
|
302
|
+
checkout que ya usa `mithril-runtime`)
|
|
303
|
+
- Precedente directo de esta sesión: `m-route-en-memoria.md` — mismo
|
|
304
|
+
patrón de "vendorizar la parte pura de Mithril, reimplementar la parte
|
|
305
|
+
atada a browser sobre la primitiva de Lynx equivalente, documentar la
|
|
306
|
+
brecha sin esconderla".
|