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
| Package | npm | Installer | Utilisation quand |
|---|
@ytrynot/dna | @ytrynot/dna | npm install @ytrynot/dna | Vous avez besoin d'une validation de schéma de type Zod avec bytecode sérialisable ou validateurs compilés autonomes |
@ytrynot/schvalid | @ytrynot/schvalid | npm install @ytrynot/schvalid | Vous avez un JSON Schema 2020-12 et souhaitez une validation ultra-rapide |
@ytrynot/qb | @ytrynot/qb | npm install @ytrynot/qb | Vous devez construire des chaînes SQL SQLite à partir d'une API fluide ou d'un schéma Zod/DNA |
@ytrynot/cli | @ytrynot/cli | npm install @ytrynot/cli | Vous 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>;
Valider / analyser
user.validate({ name: "John", email: "[email protected]" });
const result = user.safeParse({ name: "J", email: "bad" });
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();
const validate = validator(bytecode);
validate({ name: "John", age: 30 });
const parse = parser(bytecode);
parse({ name: "John", age: 30 });
Modes d'objet
dna.object({ name: dna.string() })
dna.strictObject({ name: dna.string() })
dna.looseObject({ name: dna.string() })
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.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">()
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");
Récursion
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); },
});
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);
const validate = validator(dna);
validate({ name: "John", age: 30 });
validate({ name: "Jo" });
const parse = parser(dna);
parse({ name: "John", age: 30 });
parse({ name: "Jo" });
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 });
schvalid("validation");
schvalid("parser");
schvalid("fast");
schvalid("all");
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
npm install zod
npm install @ytrynot/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);
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());
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: true | PRIMARY KEY |
pkauto: true | PRIMARY KEY AUTOINCREMENT |
unique: true | UNIQUE |
default: value | DEFAULT 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"]);
execute(contract, ["deploy", "--target", "prod"]);
execute(contract, ["unknown"]);
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();
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)
Pour aller plus loin