mithril-lynx 0.0.8 → 2.0.0
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 -284
- package/REQUEST.md +71 -0
- package/ROUTE.md +71 -0
- package/package.json +24 -80
- package/plugin.d.ts +4 -27
- package/plugin.js +142 -359
- 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 +245 -0
- 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,307 @@
|
|
|
1
|
+
# `m.request` vs el `fetch` de Lynx — investigación completa
|
|
2
|
+
|
|
3
|
+
Documento de referencia, no un plan — el plan que consumió esta
|
|
4
|
+
investigación (con su narrativa de spikes y decisiones) está en
|
|
5
|
+
[`.omo/plans/m-request-fetch-lynx.md`](.omo/plans/m-request-fetch-lynx.md).
|
|
6
|
+
Este archivo es el consolidado técnico: qué es cada API, qué dice cada
|
|
7
|
+
fuente, y qué se confirmó de verdad en un device real — para consultar
|
|
8
|
+
sin tener que reconstruir la investigación de nuevo.
|
|
9
|
+
|
|
10
|
+
**Metodología**: tres fuentes independientes, en orden de confiabilidad
|
|
11
|
+
creciente — (1) prosa de documentación oficial, (2) `.d.ts` reales
|
|
12
|
+
instalados de `@lynx-js/types` (evidencia de tipos), (3) **pruebas
|
|
13
|
+
ejecutadas en un device Android real** (`adb`, `agent-lynx evaluate`
|
|
14
|
+
contra el hilo background de una app Lynx corriendo). Las fuentes 1 y 2
|
|
15
|
+
se equivocaron dos veces cada una — ver §4 — así que ninguna afirmación
|
|
16
|
+
de este documento se da por buena solo por estar documentada o tipada:
|
|
17
|
+
donde hay una fila marcada "confirmado en device", es porque se ejecutó
|
|
18
|
+
código real y se leyó el resultado real.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. El spec: `m.request` real
|
|
23
|
+
|
|
24
|
+
Fuente: [`mithril.js.org/request.html`](https://mithril.js.org/request.html)
|
|
25
|
+
+ el código fuente real, `node_modules/mithril/request/request.js`
|
|
26
|
+
(mithril 2.3.8, 199 líneas — el mismo checkout que usa `mithril-runtime`).
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
promise = m.request(options)
|
|
30
|
+
promise = m.request(url, options)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Opción | Tipo | Qué hace |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `method` | string | GET por defecto |
|
|
36
|
+
| `url` | string | soporta interpolación `:param` |
|
|
37
|
+
| `params` | object | interpola en la URL y/o query string |
|
|
38
|
+
| `body` | object/FormData/URLSearchParams | serializado al request body |
|
|
39
|
+
| `async` | boolean | `xhr.open(..., async, ...)` — default `true` |
|
|
40
|
+
| `user`/`password` | string | HTTP Basic Auth vía `xhr.open(...)` |
|
|
41
|
+
| `withCredentials` | boolean | cookies cross-origin |
|
|
42
|
+
| `timeout` | number | ms, aborta la conexión real vía `xhr.timeout` |
|
|
43
|
+
| `responseType` | string | default `"json"` |
|
|
44
|
+
| `headers` | object | por header, `xhr.setRequestHeader` |
|
|
45
|
+
| `serialize`/`deserialize` | function | por default JSON |
|
|
46
|
+
| `type` | function | constructor aplicado al resultado |
|
|
47
|
+
| `extract` | function | `(xhr, options) => any`, se salta deserialize |
|
|
48
|
+
| `config` | function | `(xhr, options, url) => xhr\|void` — mutar el XHR vivo antes de enviarlo |
|
|
49
|
+
| `background` | boolean | si `true`, no dispara redraw al completar |
|
|
50
|
+
|
|
51
|
+
**El mecanismo real es `XMLHttpRequest`, no `fetch`.** Esto es textual en
|
|
52
|
+
la doc, y se confirma leyendo el código: `xhr.open`, `xhr.send`,
|
|
53
|
+
`xhr.abort`, `xhr.responseType`, `xhr.withCredentials`, `xhr.timeout`,
|
|
54
|
+
`xhr.onreadystatechange`. El error resultante trae `error.code` (status
|
|
55
|
+
HTTP), `error.message` (texto de la respuesta), `error.response`
|
|
56
|
+
(cuerpo ya parseado).
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. El `fetch` de Lynx
|
|
61
|
+
|
|
62
|
+
Fuentes: doc oficial
|
|
63
|
+
[`lynxjs.org/api/lynx-api/global/fetch.html`](https://lynxjs.org/api/lynx-api/global/fetch.html)
|
|
64
|
+
y los `.d.ts` reales instalados en
|
|
65
|
+
`@lynx-js/types/types/background-thread/fetch.d.ts` +
|
|
66
|
+
`.../lynx.d.ts`.
|
|
67
|
+
|
|
68
|
+
### 2.1 Dónde vive
|
|
69
|
+
|
|
70
|
+
`fetch` está declarado como método del objeto `Lynx` dentro de
|
|
71
|
+
`types/background-thread/` — **solo existe en el hilo background**, no
|
|
72
|
+
en `common/` ni `main-thread/`. Encaja con la arquitectura entera de
|
|
73
|
+
`mithril-lynx-v2` (toda la vista corre en background) — no hace falta
|
|
74
|
+
ningún puente cross-thread para esto.
|
|
75
|
+
|
|
76
|
+
### 2.2 La firma real, completa, sin recortar
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
export interface RequestInit {
|
|
80
|
+
body?: BodyInit | null;
|
|
81
|
+
headers?: HeadersInit;
|
|
82
|
+
method?: string;
|
|
83
|
+
lynxExtension?: { useStreaming?: boolean };
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Eso es todo el `RequestInit`. El comentario del propio archivo de tipos
|
|
88
|
+
dice, textual: **`"@description subset of Fetch API"`** — es la fuente
|
|
89
|
+
oficial de Lynx reconociendo que es un subconjunto, no una afirmación
|
|
90
|
+
mía.
|
|
91
|
+
|
|
92
|
+
Comparado con el `RequestInit` real de un navegador (que además tiene
|
|
93
|
+
`mode`, `credentials`, `cache`, `redirect`, `referrer`, `referrerPolicy`,
|
|
94
|
+
`integrity`, `keepalive`, `signal`, `window`) — Lynx implementa 3 campos
|
|
95
|
+
más una extensión propia.
|
|
96
|
+
|
|
97
|
+
`Body` (que `Request`/`Response` extienden): `arrayBuffer()`, `json()`,
|
|
98
|
+
`text()`. **Sin `.blob()`.**
|
|
99
|
+
|
|
100
|
+
### 2.3 Lo que la doc dice en texto plano
|
|
101
|
+
|
|
102
|
+
> "Lynx does not support Web-only features like: CORS, redirect,
|
|
103
|
+
> keepalive related APIs. FormData/Blob related APIs are not supported."
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 3. Comparación completa, opción por opción
|
|
108
|
+
|
|
109
|
+
| Opción de `m.request` | ¿Se puede replicar sobre `lynx.fetch`? | Evidencia |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `method`, `url`, `params` | **Sí** | `fetch(url, {method})`; interpolación reusa `mithril-runtime/pathname/build.js` |
|
|
112
|
+
| `body` (objeto → JSON) | **Sí** | `body: JSON.stringify(body)` |
|
|
113
|
+
| `body` (`URLSearchParams`) | **Sí** | Confirmado en device: `Content-Type: application/x-www-form-urlencoded` automático, campos bien parseados del otro lado |
|
|
114
|
+
| `body` (`FormData`) | **No** | Confirmado en device: `typeof FormData === "undefined"` |
|
|
115
|
+
| `headers` (escribir) | **Sí** | `new Headers({...})` o objeto plano, ambos llegan bien al servidor (confirmado en device contra `httpbin.org/headers`) |
|
|
116
|
+
| `headers` (leer con `.get()`/`.has()`) | **Parcial** | Confirmado en device: **case-sensitive**, no case-insensitive como el spec real (`h.set("X-Test",...); h.get("x-test")` → `null`) |
|
|
117
|
+
| `responseType: "json"/"text"` | **Sí** | `response.json()` / `.text()` |
|
|
118
|
+
| `responseType: "blob"` | **No** | Sin `.blob()` en `Body` (tipos) |
|
|
119
|
+
| `deserialize` | **Sí** | misma función sobre el resultado de `.json()`/`.text()` |
|
|
120
|
+
| `extract` | **Parcial** | firma cambia: `(response, options)` en vez de `(xhr, options)` — el propósito se preserva, el código portado literal no |
|
|
121
|
+
| `type` | **Sí** | sin cambios |
|
|
122
|
+
| `background` | **Sí** | lógica pura, no toca fetch/XHR para nada |
|
|
123
|
+
| Forma del error | **Sí** | `response.status`, `.statusText`, body ya extraído |
|
|
124
|
+
| `config(xhr)` | **No** | sin equivalente — `fetch` no da un objeto vivo para mutar a mitad de vuelo |
|
|
125
|
+
| `timeout` (corte real) | **Sí** | confirmado en device: `AbortController` + `lynx.setTimeout` corta la conexión real (ver §4.4) |
|
|
126
|
+
| Cancelación (`.abort()`) | **Sí** | `AbortController`/`AbortSignal` existen y funcionan pese a no estar tipados |
|
|
127
|
+
| Redirects (3xx) | **Sí, transparente** | confirmado en device: sigue el redirect solo, como un browser real |
|
|
128
|
+
| `response.url`/`.redirected` tras un redirect | **No confiable** | confirmado en device: quedan con la URL/estado de ANTES del redirect |
|
|
129
|
+
| `withCredentials` | **No aplica** | Lynx no tiene modelo de origen/CORS |
|
|
130
|
+
| `user`/`password` (Basic Auth) | **No hay equivalente directo** | requeriría armar el header a mano — y sin `btoa` (confirmado ausente), sin una implementación propia de base64 |
|
|
131
|
+
| `async: false` (modo síncrono) | **Imposible** | no existe un fetch síncrono en ningún entorno |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 4. Evidencia de device — el detalle de cada corrida
|
|
136
|
+
|
|
137
|
+
Device: `adb R8YYC0VV0PV` (Samsung SM-A075M), sesión de
|
|
138
|
+
`mithril-lynx-v2-app` vía `agent-lynx evaluate` (background thread).
|
|
139
|
+
Fecha: 2026-09-18.
|
|
140
|
+
|
|
141
|
+
**Nota de herramienta**: `agent-lynx evaluate` espera una única
|
|
142
|
+
EXPRESIÓN — cualquier cosa con `;` a nivel superior tira
|
|
143
|
+
`SyntaxError: expecting ')'` desde el wrapper interno de la herramienta,
|
|
144
|
+
no del código evaluado. Solución: envolver todo en un IIFE
|
|
145
|
+
`(function(){ ...; return valor; })()`.
|
|
146
|
+
|
|
147
|
+
### 4.1 Primitivas: existen o no
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
Headers: typeof Headers, AbortController: typeof AbortController,
|
|
152
|
+
AbortSignal: typeof AbortSignal, btoa: typeof btoa, atob: typeof atob,
|
|
153
|
+
fetch: typeof fetch, Request: typeof Request, Response: typeof Response,
|
|
154
|
+
})
|
|
155
|
+
// → {"Headers":"function","AbortController":"function","AbortSignal":"function",
|
|
156
|
+
// "btoa":"undefined","atob":"undefined","fetch":"undefined",
|
|
157
|
+
// "Request":"function","Response":"function"}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
JSON.stringify({FormData: typeof FormData, Blob: typeof Blob, URLSearchParams: typeof URLSearchParams})
|
|
162
|
+
// → {"FormData":"undefined","Blob":"undefined","URLSearchParams":"function"}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
JSON.stringify({lynxFetch: typeof lynx.fetch, globalThisFetch: typeof globalThis.fetch})
|
|
167
|
+
// → {"lynxFetch":"function","globalThisFetch":"undefined"}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Conclusión**: `Headers`, `AbortController`, `AbortSignal`,
|
|
171
|
+
`URLSearchParams`, `Request`, `Response` existen. `btoa`, `atob`,
|
|
172
|
+
`FormData`, `Blob`, y el `fetch` global (sin `lynx.`) NO existen. Hay que
|
|
173
|
+
llamar siempre `lynx.fetch(...)`.
|
|
174
|
+
|
|
175
|
+
### 4.2 Redirect (3xx)
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
lynx.fetch("https://httpbin.org/redirect-to?url=https://example.com")
|
|
179
|
+
// status: 200, ok: true, body: el HTML real de example.com
|
|
180
|
+
// response.url: sigue siendo la URL de httpbin (la ORIGINAL, no la final)
|
|
181
|
+
// response.redirected: undefined
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
El redirect se sigue solo, transparente. Los metadatos sobre el redirect
|
|
185
|
+
(`url`, `redirected`) no reflejan la realidad.
|
|
186
|
+
|
|
187
|
+
### 4.3 Headers: escribir funciona, leer con case distinta no
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
var h = new Headers();
|
|
191
|
+
h.set("X-Test", "abc");
|
|
192
|
+
h.get("X-Test") // "abc"
|
|
193
|
+
h.get("x-test") // null <-- debería ser "abc", Headers es case-insensitive en el spec real
|
|
194
|
+
h.has("x-test") // false
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Enviar headers a un servidor real (`httpbin.org/headers`) funciona
|
|
198
|
+
perfecto — el servidor recibe la clave y el valor correctos. El problema
|
|
199
|
+
es específico de leer de vuelta una instancia de `Headers` con una
|
|
200
|
+
capitalización de clave distinta a la usada para escribirla.
|
|
201
|
+
|
|
202
|
+
### 4.4 `AbortController`: existe y cancela de verdad
|
|
203
|
+
|
|
204
|
+
Abort inmediato:
|
|
205
|
+
```js
|
|
206
|
+
var ctrl = new AbortController();
|
|
207
|
+
lynx.fetch(url, {signal: ctrl.signal}).catch(e => ...); // e.name === "AbortError"
|
|
208
|
+
ctrl.abort();
|
|
209
|
+
// → {name: "AbortError", message: "This operation was aborted"}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Abort a mitad de vuelo (la prueba que de verdad importa — que la
|
|
213
|
+
conexión se corte, no que la promesa simplemente deje de esperar):
|
|
214
|
+
```js
|
|
215
|
+
var ctrl = new AbortController();
|
|
216
|
+
var t0 = Date.now();
|
|
217
|
+
lynx.fetch("https://httpbin.org/delay/5", {signal: ctrl.signal})
|
|
218
|
+
.catch(e => { /* elapsedMs = Date.now() - t0 */ });
|
|
219
|
+
lynx.setTimeout(() => ctrl.abort(), 800);
|
|
220
|
+
// → rechazó a los 805ms, NO a los 5000ms del delay real del servidor
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**La conexión se cortó de verdad** — no fue una promesa que se rindió
|
|
224
|
+
mientras la red seguía trabajando de fondo.
|
|
225
|
+
|
|
226
|
+
### 4.5 `URLSearchParams` como body
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
lynx.fetch("https://httpbin.org/post", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
body: new URLSearchParams({foo: "bar", baz: "42"}),
|
|
232
|
+
})
|
|
233
|
+
// el servidor recibió: form: {foo:"bar", baz:"42"}
|
|
234
|
+
// Content-Type enviado: application/x-www-form-urlencoded;charset=UTF-8 (automático)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Funciona exactamente como en un browser real.
|
|
238
|
+
|
|
239
|
+
### 4.6 `lynx.setTimeout`/`requestAnimationFrame` NO esperan a que drene la cola de microtasks
|
|
240
|
+
|
|
241
|
+
Este no es un hallazgo de `fetch` en sí, sino algo descubierto implementando
|
|
242
|
+
el redraw automático de `request.js` (`src/mount-redraw.js`) — documentado
|
|
243
|
+
acá porque cualquier wrapper sobre una promesa de `lynx.fetch` que dispare
|
|
244
|
+
un timer choca con lo mismo.
|
|
245
|
+
|
|
246
|
+
En un motor JS spec-compliant, un macrotask (`setTimeout`, `requestAnimationFrame`)
|
|
247
|
+
SIEMPRE corre después de que la cola de microtasks actual drena por
|
|
248
|
+
completo, sin importar cuántos `.then()` encadenados haya. En este runtime
|
|
249
|
+
de Lynx (hilo background) eso NO se cumple:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
Promise.resolve()
|
|
253
|
+
.then(() => console.log("microtask 1"))
|
|
254
|
+
.then(() => console.log("microtask 2"))
|
|
255
|
+
.then(() => console.log("microtask 3"));
|
|
256
|
+
lynx.setTimeout(() => console.log("timeout"), 0);
|
|
257
|
+
// orden real observado: timeout, microtask 1, microtask 2, microtask 3
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Confirmado con delays de 0, 1, 4 y 16ms — todos perdieron la carrera contra
|
|
261
|
+
el primer microtask. `lynx.requestAnimationFrame` tiene el mismo problema
|
|
262
|
+
(mismo test, mismo resultado: `raf-fired` antes que `microtask 1`).
|
|
263
|
+
|
|
264
|
+
**Por qué importa**: `request()` resuelve internamente (`bodyPromise.then(...)`)
|
|
265
|
+
y llama `sharedRedraw()` en su propio `.then(onSuccess)` — que corre UN
|
|
266
|
+
microtask antes que el `.then()` que el caller encadena sobre la promesa
|
|
267
|
+
que `request()` le devuelve. Si el redraw se dispara síncrono (o vía un
|
|
268
|
+
timer de 0-16ms), el render ocurre ANTES de que el caller haya guardado la
|
|
269
|
+
respuesta en su propio estado — la UI queda congelada mostrando el estado
|
|
270
|
+
"loading" para siempre, sin ningún error en consola. Confirmado en device
|
|
271
|
+
con la demo `fetch-demo.ts`: `status` se actualizaba a `"done"` en memoria
|
|
272
|
+
(logueado) pero la pantalla seguía mostrando `"loading"` indefinidamente.
|
|
273
|
+
|
|
274
|
+
**Mitigación aplicada**: `mount-redraw.js` usa un delay de 50ms — el primer
|
|
275
|
+
valor que ganó la carrera de forma repetible contra el caso realista (un
|
|
276
|
+
solo `.then()` encadenado). No es una garantía formal como la de un
|
|
277
|
+
macrotask real, es un margen empírico. Documentado acá en vez de
|
|
278
|
+
escondido, siguiendo el mismo criterio del resto de este archivo.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 5. Veredicto
|
|
283
|
+
|
|
284
|
+
**El gap es real pero acotado, y más chico de lo que sugerían los tipos
|
|
285
|
+
instalados.** Los puntos genuinamente irreparables son pocos y bien
|
|
286
|
+
delimitados: el hook `config(xhr)` (ningún equivalente posible con
|
|
287
|
+
`fetch`), `FormData`/`Blob`, auth básica inline (`user`/`password`, sin
|
|
288
|
+
`btoa`), y el modo síncrono (`async: false`). Todo lo demás — incluida
|
|
289
|
+
cancelación y timeout reales, que en la primera pasada de esta
|
|
290
|
+
investigación parecían imposibles — funciona.
|
|
291
|
+
|
|
292
|
+
**Recomendación**: implementar el subconjunto confirmado como
|
|
293
|
+
`mithril-lynx-v2/request`, documentando explícitamente (no escondiendo)
|
|
294
|
+
los 4-5 puntos sin equivalente, con una sugerencia directa de usar
|
|
295
|
+
`lynx.fetch` nativo para esos casos puntuales.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 6. Referencias
|
|
300
|
+
|
|
301
|
+
- Spec real: <https://mithril.js.org/request.html>
|
|
302
|
+
- `fetch` de Lynx (doc): <https://lynxjs.org/api/lynx-api/global/fetch.html>
|
|
303
|
+
- Tipos reales: `@lynx-js/types/types/background-thread/{fetch,lynx}.d.ts`
|
|
304
|
+
- Código fuente real de `m.request`: `node_modules/mithril/request/request.js`
|
|
305
|
+
- Plan que usa esta investigación: [`.omo/plans/m-request-fetch-lynx.md`](.omo/plans/m-request-fetch-lynx.md)
|
|
306
|
+
- Implementación resultante: `src/request.js` (ver ese archivo para el
|
|
307
|
+
estado actual de qué de esta tabla ya está construido)
|