linqFR/ytn

Boîte à outils portable et compilable en JS : validation de schémas, routage de workflows, générateur de requêtes SQLite et bientôt d'autres.

Qu'est-ce que ytn ?

ytn est une suite d'outils TypeScript portable, compilable en JavaScript, conçue pour les développeurs d'agents IA et les ingénieurs logiciels. Elle regroupe plusieurs packages : `@ytrynot/dna` pour la validation de schémas avec une API fluide similaire à Zod, `@ytrynot/schvalid` pour la compilation de schémas JSON Schema 2020-12 en validateurs ultra-rapides, `@ytrynot/qb` pour la construction de requêtes SQLite à partir d'un schéma Zod ou DNA, et `@ytrynot/cli` pour le routage de CLI validé par DNA. Chaque package est en ESM pur, TypeScript strict, sans dépendances externes dans la mesure du possible. ytn est particulièrement utile pour les agents IA qui ont besoin de valider des entrées, de générer du SQL ou de structurer des workflows en ligne de commande de manière fiable et performante.

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

Installed? Explore more Recherche et analyse de données skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

ytn — Guide d'utilisation des packages

ytn (whY Try Not) fournit des packages TypeScript portables et compilables en JS pour la validation de schémas, la génération SQL et le routage en ligne de commande. Tous les packages sont en ESM pur, TypeScript strict, avec zéro dépendance externe lorsque c'est possible.

Packages en un coup d'œil

PackagenpmInstallerUtilisation quand
@ytrynot/dna@ytrynot/dnanpm install @ytrynot/dnaVous avez besoin d'une validation de schéma de type Zod avec bytecode sérialisable ou validateurs compilés autonomes
@ytrynot/schvalid@ytrynot/schvalidnpm install @ytrynot/schvalidVous avez un JSON Schema 2020-12 et souhaitez une validation ultra-rapide
@ytrynot/qb@ytrynot/qbnpm install @ytrynot/qbVous devez construire des chaînes SQL SQLite à partir d'une API fluide ou d'un schéma Zod/DNA
@ytrynot/cli@ytrynot/clinpm install @ytrynot/cliVous avez besoin d'un routeur CLI avec des routes validées par DNA, de l'aide automatique et de la compilation AOT

@ytrynot/dna — Constructeur de schémas + Runtime DNA

Installer

npm install @ytrynot/dna

Définir un schéma (API fluide de type 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" }

Valider / analyser

// Validation booléenne (rapide, échec rapide)
user.validate({ name: "John", email: "[email protected]" }); // true

// Analyse sécurisée (sans levée d'exception)
const result = user.safeParse({ name: "J", email: "bad" });
// { success: false, errors: [...] }

// Analyse (lève une exception en cas d'erreur)
const data = user.parse({ name: "John", email: "[email protected]" });

Compiler en JS autonome (sans runtime nécessaire)

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 sérialisable

// Compiler une fois, réutiliser la fonction
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 } }

Modes d'objet

dna.object({ name: dna.string() })               // standard — supprime les clés inconnues
dna.strictObject({ name: dna.string() })          // strict — rejette les clés inconnues
dna.looseObject({ name: dna.string() })           // souple — laisse passer les clés inconnues

Types de schémas courants

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">()

Affinements

dna.string().refine((s) => s.length > 3, "Trop court");
dna.array(dna.string()).superRefine((val, ctx) => {
  if (val.length > 10) ctx.addIssue({ code: "too_big", maximum: 10, message: "Trop d'éléments" });
});

Transformations + pipe

const schema = dna.string().transform((s) => s.length).pipe(dna.number());
schema.parse("bonjour"); // 5

Récursion

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

// Motif 2 : accesseurs d'objet (auto-référence)
const NodeSchema = dna.object({
  value: dna.number(),
  get children() { return dna.array(NodeSchema); },
});

Métadonnées

const field = dna.string().meta({ title: "Email", description: "Email de l'utilisateur" });
const field2 = dna.string().describe("Nom complet de l'utilisateur");

Pour aller plus loin

  • README — API complète, points d'entrée, CLI Union, externals
  • Recettes — 15+ recettes copier-coller (modes objet, récursion, pipe, affinements, coercion, littéraux de gabarit, schémas de fonctions, enregistrements/maps/sets, valeurs par défaut, marquage, métadonnées)
  • Documentation technique — opcodes DNA, architecture, détails du générateur de code
  • Comparaison avec Zod — DNA vs Zod, parité fonctionnelle
  • Externals — mécanisme des externals pour les transformations
  • Sérialisation — format de sérialisation DNA
  • Motifs d'opcode — motifs d'utilisation des opcodes

@ytrynot/schvalid — Compilateur JSON Schema vers DNA

Installer

npm install @ytrynot/schvalid

Convertir JSON Schema en DNA + valider

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);

// Validation booléenne rapide
const validate = validator(dna);
validate({ name: "John", age: 30 }); // true
validate({ name: "Jo" });            // false (minLength 3)

// Analyse complète avec collecte d'erreurs
const parse = parser(dna);
parse({ name: "John", age: 30 }); // { success: true, data: { name: "John", age: 30 } }
parse({ name: "Jo" });            // { success: false, errors: [...] }

Compiler une fois, valider plusieurs fois (API constructeur)

import { schvalid } from "@ytrynot/schvalid";

const compiler = schvalid("validation");
const validate = compiler.compile(jsonSchema);
validate({ name: "John", age: 30 }); // true

// Quatre modes :
schvalid("validation"); // validateur booléen (échec rapide)
schvalid("parser");     // analyseur avec collecte d'erreurs + nouvel objet de sortie
schvalid("fast");       // hybride : valide d'abord, analyse uniquement en cas d'échec (data === input en cas de succès)
schvalid("all");        // { validate, parse, parseFast } — toutes compilées une fois, instances partagées

Fonctionnalités JSON Schema 2020-12 prises en charge

  • Types : string, number, integer, boolean, null, object, array
  • Contraintes : properties, required, items, additionalProperties, minItems, maxItems, minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, format, const, enum
  • Logique : anyOf, allOf, oneOf, if/then/else, not
  • OpenAPI 3.1 : discriminator
  • Références : $ref interne uniquement (pas d'URI externes)

Limitations

  • Pas de $ref externe (URI HTTP, URN, fichiers externes)
  • Pas de formats personnalisés (formats standard seulement : email, uuid, uri, etc.)
  • Pas de validation asynchrone, pas de $data, pas de coercition de type, pas d'injection de valeurs par défaut

Pour aller plus loin


@ytrynot/qb — Constructeur de requêtes SQLite

Installer

npm install @ytrynot/qb
# Dépendances facultatives pour le DDL piloté par schéma :
npm install zod        # pour l'introspection du schéma Zod v4
npm install @ytrynot/dna  # pour l'introspection du schéma DNA

Requêtes DML fluides

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

Définir une table à partir d'un schéma 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 ...

Définir une table à partir d'un schéma 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);
// Même sortie SQL que la version Zod

Requêtes personnalisées via .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

Exécution avec un pilote

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

Clés de métadonnées pour le DDL

CléEffet
pk: truePRIMARY KEY
pkauto: truePRIMARY KEY AUTOINCREMENT
unique: trueUNIQUE
default: valueDEFAULT value
fk: { table, column }REFERENCES table(column)

Pour aller plus loin

  • Démarrage rapide — tutoriel complet, CRUD de bout en bout
  • Guide : Requêtes — SELECT, INSERT, UPDATE, DELETE, UPSERT, WHERE, JOINs, tri, limites, pagination, recherche textuelle, GROUP BY
  • Guide : DDL — toutes les sources de schémas, clés de métadonnées, clés étrangères, index, clé primaire composite
  • Guide : Avancé — EXISTS, CASE WHEN, sous-requêtes, fonctions de fenêtre, PragmaBuilder
  • Référence des fonctionnalités — inventaire complet des méthodes, matrice des versions SQLite

@ytrynot/cli — Routeur CLI validé par DNA

Installer

npm install @ytrynot/cli

Définir des routes comme des schémas 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: "Fichiers à construire" }),
}).meta({ cli: { routeId: "build" }, description: "Construire le projet" });

const deployRoute = dna.object({
  cmd: dna.literal("deploy"),
  target: dna.string().optional().meta({ description: "Cible de déploiement" }),
}).meta({ cli: { routeId: "deploy" }, description: "Déployer le projet" });

const contract = createContract({
  name: "mycli",
  description: "Une démo CLI",
  targets: [buildRoute, deployRoute],
  cli: { positionals: ["cmd", "files"] },
});

Exécution (couche 1 — routage synchrone + validation)

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 complète avec gestionnaires + formateur + process.exit

import { executeContract, cliFactory, fullCli } from "@ytrynot/cli";
import type { ts } from "@ytrynot/cli";

const handlers: ts.Handlers = {
  build: (payload) => ({ success: true, data: `Construit ${payload.files?.length ?? 0} fichiers` }),
  deploy: (payload) => ({ success: true, data: `Déployé vers ${payload.target ?? "default"}` }),
};

const formatter: ts.FormatterFn = (result) => {
  if (result.success) return { exit: 0, message: String(result.data ?? "") };
  return { exit: 1, message: `Erreur : ${result.error}` };
};

const run = fullCli(cliFactory(executeContract(contract, handlers), formatter));
await run(); // lit process.argv.slice(2), affiche, quitte

Ajouter --help / --version

const helpRoute = dna.looseObject({ cmd: dna.literal("help") }).meta({
  cli: { routeId: "help" },
  description: "Affiche l'aide",
});

const versionRoute = dna.looseObject({ cmd: dna.literal("version") }).meta({
  cli: { routeId: "version" },
  description: "Affiche la version",
});

const contract = createContract({
  name: "mycli",
  description: "Une démo CLI",
  targets: [buildRoute, deployRoute, helpRoute, versionRoute],
  cli: { positionals: ["cmd", "files"] },
});

Compilation AOT (générer du code JS autonome)

# En cours de développement (voir l'état actuel dans le README)

Pour aller plus loin

  • README — API complète
  • CLI Cache — mise en cache des routes CLI
  • Exemples — exemples CLI complets avec code source

Skills associés