@jorgepirelarivas/clark
v0.1.1
Published
El validador que posees — un núcleo de validación diminuto, sin dependencias y auditable. El héroe frente al villano (Zod).
Maintainers
Readme
Clark
El validador que posees.
Clark es un núcleo de validación para TypeScript con tres obsesiones: cero dependencias de runtime, auditable en una tarde y nacido hablando tu contrato. No compite con Zod en superficie — opera una capa más abajo, porque sirve a datos estructurados (descriptores), no a humanos escribiendo esquemas a mano.
Por qué existe
La validación es tu perímetro de seguridad: es el punto exacto donde entra el dato no confiable. Delegar ese borde a una dependencia de terceros es, si lo piensas, una contradicción. Clark parte de un principio: el borde de seguridad debería ser código que tú lees, entiendes y posees por completo.
Eso solo vale la pena si Clark se mantiene pequeño. El trato es claro: cambias el riesgo de cadena de suministro por riesgo de corrección — y ese trato solo gana si el núcleo es tan diminuto que se audita de un vistazo y se testea exhaustivamente. Por eso Clark no tiene API encadenada, no infiere tipos por magia, no importa nada. Predicados puros + un motor que los compone. Nada más.
El nombre
Si Zod toma su nombre del villano de Superman, Clark es el verdadero yo detrás de la capa — el héroe sin disfraz. Y hay un guiño escondido: Clark es la forma de apellido de clerk, el escribano que lee, registra y verifica los documentos. Una librería de validación llamada Clark lleva su oficio en el nombre.
Codename del alma del proyecto: Kal-El.
Estado
0.x — temprano, pero ya funcional. Modelo decidido: intérprete descriptor-native (valida interpretando descriptores; "compilar a código" será una salida opcional futura).
Hoy incluye:
- 18 tipos lógicos —
text,longtext,email,url,uuid,number,integer,money,percent,date,datetime,boolean,enum,id,ref,file,object,array. - Validación anidada — objetos y arrays, con rutas de error (
user.email,items[1].qty). file— valida metadatos ({ filename, size, mime }), nunca bytes:maxBytes+acceptcon comodín (image/*).- Mensajes e i18n — cada
issuellevacode+params; el texto se resuelve por locale (autodetectado del entorno, con override). - Reglas cross-field — declarativas (datos):
eq/neq/lt/lte/gt/gte,requiredIf,atLeastOne,mutuallyExclusive, + escape hatchcustom. - Predicados atómicos + combinadores (
or), todo compuesto sin dependencias.
No usar todavía en producción — la superficie aún crece y la API puede cambiar.
Mensajes e i18n
validate(schema, value); // idioma autodetectado del entorno
validate(schema, value, { locale: "en" }); // forzado (p. ej. desde Accept-Language)
validate(schema, value, { messages: { required: () => "obligatorio!" } }); // override global
// override por campo (descriptor-native):
{ name: "email", type: "email", required: true, messages: { required: "El correo es obligatorio" } }Orden de resolución: override del campo → diccionario de opciones → locale → código crudo.
Reglas cross-field
Reglas a nivel de registro, declarativas (datos serializables). Una regla de relación se omite si alguno de sus campos referenciados es inválido o ausente — primero arreglas los errores de campo, luego ves los cross-field, limpios.
validate(schema, value, {
rules: [
{ op: "eq", code: "passwordMatch", fields: ["password", "confirm"], field: "confirm" },
{ op: "lte", code: "dateOrder", fields: ["start", "end"] }, // fechas ISO ordenan solas
{ op: "requiredIf", code: "vatReq", field: "vatId", when: { field: "country", in: ["ES"] } },
{ op: "atLeastOne", code: "contact", fields: ["email", "phone"] },
],
});En objetos anidados, rules vive en el descriptor object (cada objeto tiene su propio ámbito). Para lógica arbitraria existe { op: "custom", test: (record) => boolean } — ⚠️ vuelve el descriptor no serializable.
Vistazo
Clark valida un objeto contra un esquema de descriptores — el mismo formato que produce el CLI:
import { validate, type FieldDescriptor } from "@jorgepirelarivas/clark";
const schema: FieldDescriptor[] = [
{ name: "email", type: "email", required: true },
{ name: "amount", type: "money", required: true },
{ name: "country", type: "enum", required: true, enum: ["ES", "MX"] },
];
validate(schema, { email: "[email protected]", amount: 100, country: "ES" });
// → { ok: true, issues: [] }
validate(schema, { email: "nope", amount: -5, country: "FR" });
// → { ok: false, issues: [ { field: "email", code: "email", ... }, ... ] }Los problemas (issues) nacen en tu formato de envelope — sin capa de traducción.
Principios (no negociables)
- Cero dependencias de runtime. Clark no
importa nada de npm. (Las dev-dependencies — solotypescript— no se envían.) - Auditable. El conjunto de tipos es cerrado, así que el conjunto de predicados es finito y la matriz de tests es acotada.
- Habla tu contrato. Los errores nacen en tu formato de envelope, sin capa de traducción.
Desarrollo
npm install # solo instala typescript (devDependency)
npm test # tests con el runner nativo de Node (sin dependencias)
npm run build # genera dist/esm y dist/cjs con tscRequiere Node ≥ 22.6 (los tests corren TypeScript directo vía strip-types).
Licencia
MIT © 2026 Jorge Pirela Rivas
Publicado con scope:
@jorgepirelarivas/clark(publishConfig.access: public).
