linqFR/ytn

Herramientas portátiles y compilables a JS: validación de esquemas, enrutamiento de flujos de trabajo, generador de consultas SQLite y próximamente más.

¿Qué es ytn?

ytn (por sus siglas en inglés 'WhY Try Not') es una colección de paquetes TypeScript portátiles y compilables a JavaScript que proporciona herramientas esenciales para el desarrollo backend y de línea de comandos. Incluye `@ytrynot/dna` para la definición y validación de esquemas con un API fluida similar a Zod, capaz de compilar validadores JavaScript independientes sin dependencias en tiempo de ejecución. `@ytrynot/schvalid` permite compilar esquemas JSON Schema 2020-12 en validadores DNA ultrarrápidos, ofreciendo soporte para referencias internas, lógica booleana y discriminadores de OpenAPI 3.1. `@ytrynot/qb` es un generador de consultas SQLite con API fluida que puede crear tablas y operaciones CRUD completas a partir de esquemas Zod o DNA, incluyendo DDL, DML y consultas avanzadas como JOINs, subconsultas y funciones de ventana. Finalmente, `@ytrynot/cli` proporciona un enrutador CLI con esquemas validados mediante DNA, capaz de generar ayuda automática y compilar rutas en tiempo de compilación. Todos los paquetes son ESM puro, TypeScript estricto y evitan dependencias externas siempre que sea posible, lo que los hace ideales para entornos Node.js, navegadores y aplicaciones Edge.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add linqFR/ytn

Installed? Explore more Investigación y análisis de datos skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

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

PaquetenpmInstalarÚsalo cuando
@ytrynot/dna@ytrynot/dnanpm install @ytrynot/dnaNecesites validación de esquemas similar a Zod con bytecode serializable o validadores independientes compilados
@ytrynot/schvalid@ytrynot/schvalidnpm install @ytrynot/schvalidTengas un JSON Schema 2020-12 y necesites validación ultrarrápida
@ytrynot/qb@ytrynot/qbnpm install @ytrynot/qbNecesites construir cadenas SQL para SQLite desde una API fluida o desde un esquema Zod/DNA
@ytrynot/cli@ytrynot/clinpm install @ytrynot/cliNecesites 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>;
// { name: string; email: string; age?: number; role: "admin" | "user" }

Validar / analizar

// Validación booleana (rápida, falla rápido)
user.validate({ name: "John", email: "[email protected]" }); // true

// Análisis seguro (sin lanzar excepción)
const result = user.safeParse({ name: "J", email: "bad" });
// { success: false, errors: [...] }

// Análisis (lanza excepción en error)
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(); // bytecode DNA serializable

// Compila una vez, reutiliza la función
const validate = validator(bytecode);
validate({ name: "John", age: 30 }); // true

const parse = parser(bytecode);
parse({ name: "John", age: 30 }); // { success: true, data: { name: "John", age: 30 } }

Modos de objeto

dna.object({ name: dna.string() })               // estándar — elimina claves desconocidas
dna.strictObject({ name: dna.string() })          // estricto — rechaza claves desconocidas
dna.looseObject({ name: dna.string() })            // permisivo — pasa las claves desconocidas tal cual

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.string().optional()
dna.nullable()    // dna.string().nullable()
dna.default("fallback")
dna.catch("fallback")
dna.preprocess((v) => Number(v), dna.number())
dna.coerce.number().parse("123")  // 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"); // 5

Recursión

// Patrón 1: dna.lazy()
const TreeSchema: DnaLazy<Tree> = dna.lazy(() =>
  dna.object({ value: dna.number(), children: dna.array(TreeSchema) })
);

// Patrón 2: getters de objeto (autorreferencia)
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);

// Validación booleana rápida
const validate = validator(dna);
validate({ name: "John", age: 30 }); // true
validate({ name: "Jo" });            // false (minLength 3)

// Análisis completo con recolección de errores
const parse = parser(dna);
parse({ name: "John", age: 30 }); // { success: true, data: { name: "John", age: 30 } }
parse({ name: "Jo" });            // { success: false, errors: [...] }

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 }); // true

// Cuatro modos:
schvalid("validation"); // validador booleano (falla rápido)
schvalid("parser");     // analizador con recolección de errores + objeto de salida fresco
schvalid("fast");       // híbrido: primero valida, analiza solo en caso de fallo (data === input en éxito)
schvalid("all");        // { validate, parse, parseFast } — todo compilado una vez, instancias compartidas

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
# Dependencias opcionales para DDL basado en esquemas:
npm install zod        # para introspección de esquemas Zod v4
npm install @ytrynot/dna  # para introspección de esquemas 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();

// SELECT u.id, u.name, u.email FROM users AS u WHERE u.active = @active ORDER BY u.created_at DESC LIMIT 10

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;
// CREATE TABLE IF NOT EXISTS users (
//   id TEXT PRIMARY KEY,
//   email TEXT UNIQUE NOT NULL,
//   name TEXT NOT NULL,
//   age INTEGER NOT NULL,
//   created_at DATETIME
// );

users.getById;  // SELECT * FROM users WHERE id = @id
users.insert;   // INSERT INTO users (id, email, name, age, created_at) VALUES (@id, @email, @name, @age, @created_at)
users.update;   // UPDATE users SET email = @email, name = @name, age = @age, created_at = @created_at WHERE id = @id
users.delete;   // DELETE FROM users WHERE id = @id
users.upsert;   // INSERT INTO users (...) VALUES (...) ON CONFLICT(id) DO UPDATE SET ...

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);
// Misma salida SQL que la versión Zod

Consultas personalizadas mediante .req / .q

users.req.select("id", "name").where("id").toSQL();
// SELECT id, name FROM users WHERE id = @id

users.req.upsert("email", "name").toSQL();
// INSERT INTO users (email, name) VALUES (@email, @name)
// ON CONFLICT(id, email) DO UPDATE SET name = excluded.name

Ejecutar con un controlador

import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("app.db");
db.exec(QB.enableForeignKeys()); // PRAGMA foreign_keys = ON
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

ClaveEfecto
pk: truePRIMARY KEY
pkauto: truePRIMARY KEY AUTOINCREMENT
unique: trueUNIQUE
default: valueDEFAULT 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"]);
// { success: true, route: "build", payload: { cmd: "build", files: ["a.ts", "b.ts"] } }

execute(contract, ["deploy", "--target", "prod"]);
// { success: true, route: "deploy", payload: { cmd: "deploy", target: "prod" } }

execute(contract, ["unknown"]);
// { success: false, errors: [...] }

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(); // lee process.argv.slice(2), imprime, sale

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"]); // enrutamiento compilado en tiempo de compilación

Lecturas adicionales


Recursos Comunes

RecursoEnlace
Repositorio principalgithub.com/linqFR/ytn
Documentacióndocs
Registro de cambiosCHANGELOG.md
LicenciaMIT

Skills relacionados