ytn — Guía de Uso del Paquete
ytn (WhY Try Not) proporciona paquetes TypeScript portátiles y compilables a JS para validación de esquemas, generación SQL y enrutamiento CLI. Todos los paquetes son ESM puro, TypeScript estricto y cero dependencias externas cuando es posible.
Paquetes de un Vistazo
| Paquete | npm | Instalar | Úsalo cuando |
|---|
@ytrynot/dna | @ytrynot/dna | npm install @ytrynot/dna | Necesites validación de esquemas similar a Zod con bytecode serializable o validadores independientes compilados |
@ytrynot/schvalid | @ytrynot/schvalid | npm install @ytrynot/schvalid | Tengas un JSON Schema 2020-12 y necesites validación ultrarrápida |
@ytrynot/qb | @ytrynot/qb | npm install @ytrynot/qb | Necesites construir cadenas SQL para SQLite desde una API fluida o desde un esquema Zod/DNA |
@ytrynot/cli | @ytrynot/cli | npm install @ytrynot/cli | Necesites un enrutador CLI con rutas validadas por DNA, ayuda automática y compilación AOT |
@ytrynot/dna — Constructor de Esquemas + Motor DNA
Instalar
npm install @ytrynot/dna
Definir un esquema (API fluida similar a Zod)
import { dna } from "@ytrynot/dna";
const user = dna.object({
name: dna.string().min(2),
email: dna.string().email(),
age: dna.number().int().min(0).optional(),
role: dna.enum(["admin", "user"]).default("user"),
});
type User = dna.infer<typeof user>;
Validar / analizar
user.validate({ name: "John", email: "[email protected]" });
const result = user.safeParse({ name: "J", email: "bad" });
const data = user.parse({ name: "John", email: "[email protected]" });
Compilar a JS independiente (sin necesidad de motor en tiempo de ejecución)
import { dna, validator, parser } from "@ytrynot/dna";
const schema = dna.object({ name: dna.string().min(2), age: dna.number() });
const bytecode = schema.toDna();
const validate = validator(bytecode);
validate({ name: "John", age: 30 });
const parse = parser(bytecode);
parse({ name: "John", age: 30 });
Modos de objeto
dna.object({ name: dna.string() })
dna.strictObject({ name: dna.string() })
dna.looseObject({ name: dna.string() })
Tipos de esquema comunes
dna.string().min(1).max(100).email().uuid().url().regex(/^[a-z]+$/)
dna.number().int().positive().negative().min(0).max(100).multipleOf(2)
dna.boolean()
dna.date()
dna.bigint()
dna.literal("active")
dna.enum(["a", "b", "c"])
dna.array(dna.string()).min(1).max(10).nonempty()
dna.object({ ... })
dna.tuple([dna.string(), dna.number()])
dna.union([dna.string(), dna.number()])
dna.discriminatedUnion("type", [shapeA, shapeB])
dna.record(dna.string(), dna.boolean())
dna.map(dna.string(), dna.string())
dna.set(dna.string())
dna.optional()
dna.nullable()
dna.default("fallback")
dna.catch("fallback")
dna.preprocess((v) => Number(v), dna.number())
dna.coerce.number().parse("123")
dna.instanceof(UserClass)
dna.templateLiteral(["https://", dna.string(), ".", dna.enum(["com", "net"])])
dna.function().input([dna.string()]).output(dna.number()).implement((s) => s.length)
dna.lazy(() => dna.object({ children: dna.array(recursiveSchema) }))
dna.brand<"MyBrand">()
Refinamientos
dna.string().refine((s) => s.length > 3, "Demasiado corto");
dna.array(dna.string()).superRefine((val, ctx) => {
if (val.length > 10) ctx.addIssue({ code: "too_big", maximum: 10, message: "Demasiados elementos" });
});
Transformaciones + tubería
const schema = dna.string().transform((s) => s.length).pipe(dna.number());
schema.parse("hola");
Recursión
const TreeSchema: DnaLazy<Tree> = dna.lazy(() =>
dna.object({ value: dna.number(), children: dna.array(TreeSchema) })
);
const NodeSchema = dna.object({
value: dna.number(),
get children() { return dna.array(NodeSchema); },
});
Metadatos
const field = dna.string().meta({ title: "Email", description: "Correo electrónico del usuario" });
const field2 = dna.string().describe("Nombre completo del usuario");
Lecturas adicionales
- README — API completa, puntos de entrada, CLI Union, externos
- Recetas — Más de 15 recetas listas para copiar y pegar (modos de objeto, recursión, tubería, refinamientos, coerción, literales de plantilla, esquemas de función, registros/mapas/conjuntos, valores predeterminados, marcas, metadatos)
- Documentación técnica — Opcodes DNA, arquitectura, internos de generación de código
- Comparación con Zod — Paridad de características entre DNA y Zod
- Externos — Mecanismo de externos para transformaciones
- Serialización — Formato de serialización DNA
- Patrones de opcode — Patrones de uso de opcodes
@ytrynot/schvalid — Compilador de JSON Schema a DNA
Instalar
npm install @ytrynot/schvalid
Convertir JSON Schema a DNA y validar
import { jschemaToDna, validator, parser } from "@ytrynot/schvalid";
const jsonSchema = {
type: "object",
properties: {
name: { type: "string", minLength: 3 },
age: { type: "number", minimum: 0 },
},
required: ["name"],
};
const dna = jschemaToDna(jsonSchema);
const validate = validator(dna);
validate({ name: "John", age: 30 });
validate({ name: "Jo" });
const parse = parser(dna);
parse({ name: "John", age: 30 });
parse({ name: "Jo" });
Compilar una vez, validar muchas veces (API de constructor)
import { schvalid } from "@ytrynot/schvalid";
const compiler = schvalid("validation");
const validate = compiler.compile(jsonSchema);
validate({ name: "John", age: 30 });
schvalid("validation");
schvalid("parser");
schvalid("fast");
schvalid("all");
Características compatibles de JSON Schema 2020-12
- Tipos: string, number, integer, boolean, null, object, array
- Restricciones: properties, required, items, additionalProperties, minItems, maxItems, minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, format, const, enum
- Lógica: anyOf, allOf, oneOf, if/then/else, not
- OpenAPI 3.1: discriminator
- Referencias: solo
$ref interna (sin URIs externas)
Limitaciones
- Sin
$ref externas (URIs HTTP, URNs, archivos externos)
- Sin formatos personalizados (solo formatos estándar: email, uuid, uri, etc.)
- Sin validación asíncrona, sin
$data, sin coerción de tipos, sin inyección de valores predeterminados
Lecturas adicionales
@ytrynot/qb — Constructor de Consultas SQLite
Instalar
npm install @ytrynot/qb
npm install zod
npm install @ytrynot/dna
Consultas DML fluidas
import { QueryBuilder as QB } from "@ytrynot/qb";
const sql = QB.table("users", "u")
.select(["u.id", "u.name", "u.email"])
.where(["u.active"])
.orderBy("u.created_at", "DESC")
.limit(10)
.toSQL();
Definir una tabla a partir de un esquema Zod
import { z } from "zod";
import { QueryBuilder as QB } from "@ytrynot/qb";
const UserSchema = z.object({
id: z.string().uuid().meta({ pk: true }),
email: z.string().email().meta({ unique: true }),
name: z.string(),
age: z.number().int(),
created_at: z.date().optional(),
});
const users = QB.defTable("users", UserSchema);
users.createTable;
users.getById;
users.insert;
users.update;
users.delete;
users.upsert;
Definir una tabla a partir de un esquema DNA
import { dna } from "@ytrynot/dna";
import { QueryBuilder as QB } from "@ytrynot/qb";
const UserSchema = dna.object({
id: dna.string().uuid().meta({ pk: true }),
email: dna.string().email().meta({ unique: true }),
name: dna.string(),
age: dna.int(),
created_at: dna.date().optional(),
});
const users = QB.defTable("users", UserSchema);
Consultas personalizadas mediante .req / .q
users.req.select("id", "name").where("id").toSQL();
users.req.upsert("email", "name").toSQL();
Ejecutar con un controlador
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("app.db");
db.exec(QB.enableForeignKeys());
db.exec(users.createTable);
const stmt = db.prepare(users.insert);
stmt.run({ id: crypto.randomUUID(), email: "[email protected]", name: "Alice", age: 30, created_at: null });
Claves de metadatos para DDL
| Clave | Efecto |
|---|
pk: true | PRIMARY KEY |
pkauto: true | PRIMARY KEY AUTOINCREMENT |
unique: true | UNIQUE |
default: value | DEFAULT value |
fk: { table, column } | REFERENCES table(column) |
Lecturas adicionales
- Inicio rápido — Tutorial completo, CRUD de principio a fin
- Cómo: Consultas — SELECT, INSERT, UPDATE, DELETE, UPSERT, WHERE, JOINs, ordenación, límites, paginación, búsqueda de texto, GROUP BY
- Cómo: DDL — Todas las fuentes de esquema, claves de metadatos, claves foráneas, índices, PK compuesta
- Cómo: Avanzado — EXISTS, CASE WHEN, subconsultas, funciones de ventana, PragmaBuilder
- Referencia de características — Inventario completo de métodos, matriz de versiones SQLite
@ytrynot/cli — Enrutador CLI Validado con DNA
Instalar
npm install @ytrynot/cli
Definir rutas como esquemas DNA
import { dna } from "@ytrynot/dna";
import { createContract, execute } from "@ytrynot/cli";
const buildRoute = dna.object({
cmd: dna.literal("build"),
files: dna.array(dna.string()).optional().meta({ description: "Archivos a compilar" }),
}).meta({ cli: { routeId: "build" }, description: "Compilar el proyecto" });
const deployRoute = dna.object({
cmd: dna.literal("deploy"),
target: dna.string().optional().meta({ description: "Destino del despliegue" }),
}).meta({ cli: { routeId: "deploy" }, description: "Desplegar el proyecto" });
const contract = createContract({
name: "mycli",
description: "Un CLI de demostración",
targets: [buildRoute, deployRoute],
cli: { positionals: ["cmd", "files"] },
});
Ejecutar (capa 1 — enrutamiento sincrónico + validación)
const result = execute(contract, ["build", "a.ts", "b.ts"]);
execute(contract, ["deploy", "--target", "prod"]);
execute(contract, ["unknown"]);
CLI completo con manejadores + formateador + process.exit
import { executeContract, cliFactory, fullCli } from "@ytrynot/cli";
import type { ts } from "@ytrynot/cli";
const handlers: ts.Handlers = {
build: (payload) => ({ success: true, data: `Compilados ${payload.files?.length ?? 0} archivos` }),
deploy: (payload) => ({ success: true, data: `Desplegado en ${payload.target ?? "predeterminado"}` }),
};
const formatter: ts.FormatterFn = (result) => {
if (result.success) return { exit: 0, message: String(result.data ?? "") };
return { exit: 1, message: `Error: ${result.error}` };
};
const run = fullCli(cliFactory(executeContract(contract, handlers), formatter));
await run();
Agregar --help / --version
const helpRoute = dna.looseObject({ cmd: dna.literal("help") }).meta({ cli: { routeId: "help", hidden: true }, description: "Mostrar ayuda" });
const versionRoute = dna.looseObject({ cmd: dna.literal("version") }).meta({ cli: { routeId: "version", hidden: true }, description: "Mostrar versión" });
const contract = createContract({
name: "mycli",
description: "Un CLI de demostración",
targets: [buildRoute, deployRoute, helpRoute, versionRoute],
cli: { positionals: ["cmd", "files"], help: { options: ["--help", "-h"] }, version: { options: ["--version", "-v"], value: "1.0.0" } },
});
Compilación AOT (optimización de rendimiento)
import { createContract, execute, compiler } from "@ytrynot/cli";
const contract = createContract({ ... });
const compiled = compiler.compile(contract);
execute(compiled, ["build"]);
Lecturas adicionales
Recursos Comunes