CommunityRecherche & Datenanalysegithub.com

linqFR/ytn

Portable, in JS kompilierbare Werkzeuge: Schema-Validierung, Workflow-Routing, SQLite-Query-Builder und bald mehr

Was ist ytn?

ytn (whY Try Not) ist eine Sammlung portabler, in JavaScript kompilierbarer TypeScript-Pakete, die für moderne Node.js- und Browser-Umgebungen entwickelt wurde. Die Bibliothek besteht aus vier Hauptpaketen: @ytrynot/dna für Zod-ähnliche Schema-Validierung mit serialisierbarem Bytecode, @ytrynot/schvalid für ultraschnelle Validierung basierend auf JSON Schema 2020-12, @ytrynot/qb für einen flüssigen SQLite-Query-Builder mit Schema-Introspection und @ytrynot/cli für ein CLI-Routing mit DNA-validierten Routen, Auto-Hilfe und AOT-Kompilierung. Alle Pakete sind reines ESM, striktes TypeScript und haben wo möglich null externe Abhängigkeiten. Entwickler können Schemas mit der flüssigen API definieren, sie zu eigenständigen JS-Validatoren kompilieren (kein Runtime-Bedarf) und sie sowohl für die Datenvalidierung als auch für die DDL-/DML-Generierung verwenden. Das Paket ist besonders nützlich, um TypeScript-Typen mit Laufzeit-Validierung und Datenbank-Schemata zu vereinheitlichen.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add linqFR/ytn

Installed? Explore more Recherche & Datenanalyse skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

ytn – Paket-Nutzungsleitfaden

ytn (whY Try Not) bietet portable, in JS kompilierbare TypeScript-Pakete für Schema-Validierung, SQL-Generierung und CLI-Routing. Alle Pakete sind reines ESM, striktes TypeScript und haben wo möglich null externe Abhängigkeiten.

Pakete auf einen Blick

PaketnpmInstallierenVerwende es, wenn ...
@ytrynot/dna@ytrynot/dnanpm install @ytrynot/dnaDu eine Zod-ähnliche Schema-Validierung mit serialisierbarem Bytecode oder eigenständigen kompilierten Validatoren benötigst
@ytrynot/schvalid@ytrynot/schvalidnpm install @ytrynot/schvalidDu ein JSON Schema 2020-12 hast und ultraschnelle Validierung benötigst
@ytrynot/qb@ytrynot/qbnpm install @ytrynot/qbDu SQLite-SQL-Strings aus einer flüssigen API oder einem Zod/DNA-Schema erstellen musst
@ytrynot/cli@ytrynot/clinpm install @ytrynot/cliDu einen CLI-Router mit DNA-validierten Routen, Auto-Hilfe und AOT-Kompilierung benötigst

@ytrynot/dna – Schema-Builder + DNA-Laufzeit

Installation

npm install @ytrynot/dna

Ein Schema definieren (Zod-ähnliche flüssige API)

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" }

Validieren / Parsen

// Boolesche Validierung (schnell, Fail-Fast)
user.validate({ name: "John", email: "[email protected]" }); // true

// Sicheres Parsen (kein Wurf)
const result = user.safeParse({ name: "J", email: "bad" });
// { success: false, errors: [...] }

// Parsen (wirft Fehler bei Fehler)
const data = user.parse({ name: "John", email: "[email protected]" });

In eigenständiges JS kompilieren (keine Laufzeit nötig)

import { dna, validator, parser } from "@ytrynot/dna";

const schema = dna.object({ name: dna.string().min(2), age: dna.number() });
const bytecode = schema.toDna(); // serialisierbarer DNA-Bytecode

// Einmal kompilieren, die Funktion wiederverwenden
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 } }

Objektmodi

dna.object({ name: dna.string() })               // standard — entfernt unbekannte Schlüssel
dna.strictObject({ name: dna.string() })          // strikt — lehnt unbekannte Schlüssel ab
dna.looseObject({ name: dna.string() })           // locker — lässt unbekannte Schlüssel durch

Häufige Schema-Typen

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

Verfeinerungen (Refinements)

dna.string().refine((s) => s.length > 3, "Zu kurz");
dna.array(dna.string()).superRefine((val, ctx) => {
  if (val.length > 10) ctx.addIssue({ code: "too_big", maximum: 10, message: "Zu viele" });
});

Transformationen + Pipe

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

Rekursion

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

// Muster 2: Objekt-Getter (Selbstreferenz)
const NodeSchema = dna.object({
  value: dna.number(),
  get children() { return dna.array(NodeSchema); },
});

Metadaten

const field = dna.string().meta({ title: "E-Mail", description: "Benutzer-E-Mail" });
const field2 = dna.string().describe("Vollständiger Name des Benutzers");

Weiterführende Lektüre

  • README — vollständige API, Einstiegspunkte, CLI Union, Externals
  • Rezepte — 15+ Copy-Paste-Rezepte (Objektmodi, Rekursion, Pipe, Verfeinerungen, Koerzion, Template-Literale, Funktions-Schemas, Records/Maps/Sets, Defaults, Branding, Metadaten)
  • Technische Dokumentation — DNA-Opcodes, Architektur, Codegen-Interna
  • Zod-Vergleich — DNA vs. Zod Feature-Parität
  • Externals — Externals-Mechanismus für Transformationen
  • Serialisierung — DNA-Serialisierungsformat
  • Opcode-Muster — Opcode-Nutzungsmuster

@ytrynot/schvalid – JSON-Schema-zu-DNA-Compiler

Installation

npm install @ytrynot/schvalid

JSON-Schema in DNA umwandeln + validieren

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

// Schnelle boolesche Validierung
const validate = validator(dna);
validate({ name: "John", age: 30 }); // true
validate({ name: "Jo" });            // false (minLength 3)

// Vollständiges Parsen mit Fehlersammlung
const parse = parser(dna);
parse({ name: "John", age: 30 }); // { success: true, data: { name: "John", age: 30 } }
parse({ name: "Jo" });            // { success: false, errors: [...] }

Einmal kompilieren, viele Validierungen (Builder-API)

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

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

// Vier Modi:
schvalid("validation"); // boolescher Validator (Fail-Fast)
schvalid("parser");     // Parser mit Fehlersammlung + frischem Ausgabeobjekt
schvalid("fast");       // Hybrid: zuerst validieren, nur bei Fehler parsen (data === input bei Erfolg)
schvalid("all");        // { validate, parse, parseFast } — alle einmal kompiliert, gemeinsame Instanzen

Unterstützte JSON-Schema-2020-12-Features

  • Typen: string, number, integer, boolean, null, object, array
  • Einschränkungen: properties, required, items, additionalProperties, minItems, maxItems, minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, format, const, enum
  • Logik: anyOf, allOf, oneOf, if/then/else, not
  • OpenAPI 3.1: discriminator
  • Referenzen: interne $ref nur (keine externen URIs)

Einschränkungen

  • Keine externen $ref (HTTP-URIs, URNs, externe Dateien)
  • Keine benutzerdefinierten Formate (nur Standardformate: E-Mail, UUID, URI usw.)
  • Keine asynchrone Validierung, kein $data, keine Typkoerzion, keine Standardwert-Einfügung

Weiterführende Lektüre

  • README — vollständige API, Modi, Leistung
  • AJV-Vergleich — schvalid vs. AJV Feature-für-Feature

@ytrynot/qb – SQLite-Query-Builder

Installation

npm install @ytrynot/qb
# Optionale Peer-Abhängigkeiten für schema-gesteuerte DDL:
npm install zod        # für Zod v4 Schema-Introspection
npm install @ytrynot/dna  # für DNA-Schema-Introspection

Flüssige DML-Abfragen

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

Eine Tabelle aus einem Zod-Schema definieren

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 ...

Eine Tabelle aus einem DNA-Schema definieren

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);
// Gleiche SQL-Ausgabe wie die Zod-Version

Benutzerdefinierte Abfragen mit .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

Mit einem Treiber ausführen

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

Metadatenschlüssel für DDL

SchlüsselWirkung
pk: truePRIMARY KEY
pkauto: truePRIMARY KEY AUTOINCREMENT
unique: trueUNIQUE
default: valueDEFAULT value
fk: { table, column }REFERENCES table(column)

Weiterführende Lektüre

  • Schnellstart — vollständiges Tutorial, Ende-zu-Ende-CRUD
  • How-to: Abfragen — SELECT, INSERT, UPDATE, DELETE, UPSERT, WHERE, JOINs, Sortierung, Limits, Paginierung, Textsuche, GROUP BY
  • How-to: DDL — alle Schema-Quellen, Metadatenschlüssel, Fremdschlüssel, Indizes, zusammengesetzte PK
  • How-to: Erweitert — EXISTS, CASE WHEN, Unterabfragen, Fensterfunktionen, PragmaBuilder
  • Funktionsreferenz — vollständige Methodeninventur, SQLite-Versionsmatrix

@ytrynot/cli – DNA-validierter CLI-Router

Installation

npm install @ytrynot/cli

Routen als DNA-Schemas definieren

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: "Zu erstellende Dateien" }),
}).meta({ cli: { routeId: "build" }, description: "Das Projekt erstellen" });

const deployRoute = dna.object({
  cmd: dna.literal("deploy"),
  target: dna.string().optional().meta({ description: "Bereitstellungsziel" }),
}).meta({ cli: { routeId: "deploy" }, description: "Das Projekt bereitstellen" });

const contract = createContract({
  name: "mycli",
  description: "Ein Demo-CLI",
  targets: [buildRoute, deployRoute],
  cli: { positionals: ["cmd", "files"] },
});

Ausführen (Ebene 1 — synchrones Routing + Validierung)

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, ["unbekannt"]);
// { success: false, errors: [...] }

Vollständiges CLI mit Handlern + Formatierer + process.exit

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

const handlers: ts.Handlers = {
  build: (payload) => ({ success: true, data: `${payload.files?.length ?? 0} Dateien erstellt` }),
  deploy: (payload) => ({ success: true, data: `Nach ${payload.target ?? "Standard"} bereitgestellt` }),
};

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

const run = fullCli(cliFactory(executeContract(contract, handlers), formatter));
await run(); // liest process.argv.slice(2), gibt aus, beendet sich

--help / --version hinzufügen

import { helpRoute, versionRoute } from "@ytrynot/cli";

const contract = createContract({
  name: "mycli",
  description: "Ein Demo-CLI",
  version: "1.0.0",
  targets: [buildRoute, deployRoute, helpRoute, versionRoute],
  cli: { positionals: ["cmd", "files"] },
});
// --help und --version werden automatisch zu den Routen hinzugefügt

CLI-Union (gemeinsame n-äre Flaggen)

const contract = createContract({
  name: "mycli",
  targets: [buildRoute, deployRoute],
  cli: {
    positionals: ["cmd", "...args"],
    union: dna.object({
      verbose: dna.boolean().default(false).meta({ description: "Ausführliche Ausgabe anzeigen" }),
      color: dna.boolean().default(true).meta({ description: "Farben deaktivieren" }),
      config: dna.string().optional().meta({ description: "Pfad zur Konfigurationsdatei" }),
    }),
    unionPosition: "before", // Position von Union-Flaggen: "before" | "after" Routen-Positionalen
  },
});

AOT-Kompilierung (Ahead-of-Time)

import { compileContractToFile } from "@ytrynot/cli";

compileContractToFile(contract, "./dist/cli.js");
// Erzeugt eine eigenständige JS-Datei, die ohne das CLI-Paket ausgeführt werden kann

Die AOT-Datei minimiert die Startzeit und kann in Produktions-Images ohne TypeScript-Abhängigkeiten eingebettet werden.

Weiterführende Lektüre

  • README — vollständige API, AOT, CLI-Union, benutzerdefinierte Formatierer
  • How-to: CLI — Ende-zu-Ende-CLI-Einrichtung mit AOT
  • How-to: Union — gemeinsame Flaggen, CI/CD-Integration, Umgebungsvariablen

Lizenz

ytn ist unter der MIT-Lizenz lizenziert.

Verwandte Skills