Gotchas
Las trampas conocidas, con su sintoma y su salida.
Cloudflare y wrangler#
Los envs no heredan nada
Sintoma: funciona en wrangler dev y en produccion falla con el binding
undefined.
Un env sin name crea un Worker aparte
Si omites name dentro de env.production, Wrangler despliega a
<proyecto>-production en vez de a tu Worker. Sintoma: desplegaste, no hay
errores, y el sitio sigue mostrando la version vieja. Le paso a NutriLabel.
db.exec no traga SQL real
Falla con SQL multilinea o con comentarios. Usa applySql de
@jun/soul/testing.
SQLite no puede alterar un CHECK
Hay que reconstruir la tabla: respaldar las hijas, borrar, recrear el padre, recrear las hijas y
restaurar. ALTER ADD COLUMN si es seguro.
Hono#
La barra final importa
app.route("/x", sub) con sub.get("/") matchea /x, no
/x/. Es el origen de muchos 404 fantasma.
Invarianza de tipos con un Env mas ancho
Los middlewares de soul tipan Bindings: SoulEnv. Si tu Env lo extiende, TypeScript
reclama por invarianza de genericos aunque en runtime sea correcto.
// El cast es seguro: Env es un superconjunto estructural de SoulEnv.
export const requireAdmin = auth.requireAdmin as unknown as () => MiddlewareHandler<{
Bindings: Env;
Variables: AuthVariables;
}>;
Plantillas HTML#
html`` devuelve un objeto String
La regla: String(...) solo en las funciones terminales, cuyo resultado va directo
a la pagina. Los helpers intermedios devuelven el objeto sin coercionar; si devuelven string plano,
el html\`\` receptor los escapa y el markup sale como texto literal.
Distribucion#
La forma corta de npm ignora tus alias SSH
Solo se empaqueta lo que esta en files
Al instalar por git, npm aplica las mismas reglas que en npm publish: el array
files de package.json decide que viaja. Una carpeta que el paquete necesite en runtime
y no este listada desaparece silenciosamente para los consumidores reales, aunque funcione
en tu checkout local.
Git no versiona directorios vacios
Si el codigo asume que existe un directorio, hay que crearlo con
fs.mkdirSync(dir, { recursive: true }) o dejar un .gitkeep.
Instalacion local con file: rompe tsc
Un consumidor que use file: termina con dos copias de hono y TypeScript ve dos
identidades del mismo tipo. Los errores son espurios. Se arregla con paths en
tsconfig.json, y para verificar de verdad conviene fiarse del tsc de dentro de
soul.
Cache en tests: el cuelgue de miniflare#
Sintoma: un test que pasa solo pero cuelga junto a los demas, o que empieza a colgarse al agregar un fetch antes de un update que invalida.
// MAL: el fetch deja un put pendiente; el update invalida la misma clave
await SELF.fetch("http://localhost/m/hola");
await editarMemorial(...);
const res = await SELF.fetch("http://localhost/m/hola"); // cuelga
// BIEN: se verifica por base de datos y con un 404 de la URL vieja
await editarMemorial(...);
const row = await env.DB.prepare("SELECT slug FROM memoriales WHERE id = ?").bind(id).first();
expect(row.slug).toBe("nuevo");
expect((await SELF.fetch("http://localhost/m/viejo")).status).toBe(404);
El host del cache tiene que calzar#
La clave sale del host de la request; invalidateCache usa APP_URL. Si no coinciden,
invalidas algo que nadie lee.
// wrangler.jsonc de test: "APP_URL": "https://soul-test.local"
const BASE = "https://soul-test.local"; // el fetch DEBE usar este host
await SELF.fetch(`${BASE}/m/ana`);
await editar(...); // invalida con APP_URL
expect((await SELF.fetch(`${BASE}/m/ana`)).text()).toContain("nuevo");
applySql y los comentarios en linea#
-- MAL: el ";" del comentario corta la sentencia
CREATE TABLE x (
id INTEGER PRIMARY KEY -- ojo; esto la rompe
);
-- BIEN: el comentario va en su propia linea
-- ojo; aqui el punto y coma no molesta
CREATE TABLE x (
id INTEGER PRIMARY KEY
);
El cast de tipos, en su sitio#
Conviene aislarlo en un adaptador y no repetirlo por todo el proyecto:
// src/lib/auth-context.ts
import type { MiddlewareHandler } from "hono";
import type { AuthVariables } from "@jun/soul/auth";
import { auth } from "./soul";
import type { Env } from "../types";
// Env extiende SoulEnv con bindings propios (R2, vars extra), y eso provoca
// invarianza de genericos en Hono. El cast es seguro en runtime.
export const requireAdmin = auth.requireAdmin as unknown as () => MiddlewareHandler<{
Bindings: Env;
Variables: AuthVariables;
}>;
Composicion de HTML sin doble escape#
import { html } from "@jun/soul/ui";
// INTERMEDIO: devuelve el objeto de html\`\`, SIN String()
const campo = (label: string, name: string) =>
html`<div class="soul-field">
<label class="soul-label">\${label}</label>
<input class="soul-input" name="\${name}"/>
</div>`;
// TERMINAL: aqui si, porque el resultado va directo a la pagina
export const formulario = () =>
String(html`<form method="post">\${campo("Nombre", "name")}</form>`);
Sintoma de equivocarse: en vez del formulario, la pagina muestra
<div class="soul-field">… como texto.
Dependencia con alias SSH#
{
"dependencies": {
"@jun/soul": "git+ssh://git@github/jun-studio/soul.git#v4.2.0"
}
}
# ~/.ssh/config
Host github
HostName github.com
User git
IdentityFile ~/.ssh/mi_deploy_key
IdentitiesOnly yes
Verificar que el paquete lleva todo#
# Que se empaquetaria de verdad al instalar desde git
npm pack --dry-run
# Debe listar src/, bin/ y templates/ — lo que diga "files" en package.json