Community研究&データ分析github.com

linqFR/ytn

ポータブルでJSコンパイル可能なツール:スキーマバリデーション、ワークフロールーティング、SQLiteクエリビルダーなど

ytn とは?

ytnは、TypeScriptで書かれたポータブルな軽量パッケージ群で、スキーマバリデーション、SQL生成、CLIルーティングを提供します。ZodライクなAPIを持つDNAスキーマバリデーターは、シリアライズ可能なバイトコードにコンパイルでき、ランタイム不要のスタンドアロンバリデーターを生成できます。また、JSON Schema 2020-12をサポートする高速バリデーター、ZodまたはDNAスキーマからSQLite DDLを生成するクエリビルダー、バリデーションとルーティングを統合したCLIルーターを含みます。すべてのパッケージは純粋なESM、厳格なTypeScriptで動作し、可能な限り外部依存関係ゼロです。CursorやClaude Code、CodexなどのAIエージェントが、データバリデーション、データベース操作、CLIツールの構築をタイプセーフかつ効率的に行いたい場合に最適です。

対応~Claude Code~Codex CLI~Cursor
npx skills add linqFR/ytn

Installed? Explore more 研究&データ分析 skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

ytn — パッケージ利用ガイド

ytn(Why Try Not)は、スキーマバリデーション、SQL生成、CLIルーティングのための、ポータブルでJSコンパイル可能なTypeScriptパッケージを提供します。すべてのパッケージは純粋なESM、厳格なTypeScript、可能な限り外部依存関係ゼロです。

パッケージ一覧

パッケージnpmインストール使用場面
@ytrynot/dna@ytrynot/dnanpm install @ytrynot/dnaZodライクなスキーマバリデーションで、シリアライズ可能なバイトコードやスタンドアロンコンパイルバリデーターが必要な場合
@ytrynot/schvalid@ytrynot/schvalidnpm install @ytrynot/schvalidJSON Schema 2020-12を使用し、超高速バリデーションが必要な場合
@ytrynot/qb@ytrynot/qbnpm install @ytrynot/qbフルエントAPIまたはZod/DNAスキーマからSQLite SQL文字列を構築する必要がある場合
@ytrynot/cli@ytrynot/clinpm install @ytrynot/cliDNAバリデーションルート、自動ヘルプ、AOTコンパイルを備えたCLIルーターが必要な場合

@ytrynot/dna — スキーマビルダー + DNAランタイム

インストール

npm install @ytrynot/dna

スキーマ定義(Zodライクなフルエント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" }

バリデーション / パース

// 真偽値バリデーション(高速、失敗時即座に終了)
user.validate({ name: "John", email: "[email protected]" }); // true

// 安全なパース(スローしない)
const result = user.safeParse({ name: "J", email: "bad" });
// { success: false, errors: [...] }

// パース(エラー時にスロー)
const data = user.parse({ name: "John", email: "[email protected]" });

スタンドアロンJSにコンパイル(ランタイム不要)

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

const schema = dna.object({ name: dna.string().min(2), age: dna.number() });
const bytecode = schema.toDna(); // シリアライズ可能なDNAバイトコード

// 一度コンパイルすれば関数を再利用可能
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 } }

オブジェクトモード

dna.object({ name: dna.string() })               // 標準 — 不明なキーを除去
dna.strictObject({ name: dna.string() })          // 厳格 — 不明なキーを拒否
dna.looseObject({ name: dna.string() })           // 緩い — 不明なキーをそのまま通す

一般的なスキーマタイプ

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

リファインメント

dna.string().refine((s) => s.length > 3, "短すぎます");
dna.array(dna.string()).superRefine((val, ctx) => {
  if (val.length > 10) ctx.addIssue({ code: "too_big", maximum: 10, message: "多すぎます" });
});

トランスフォーム + パイプ

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

再帰

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

// パターン2: オブジェクトゲッター(自己参照)
const NodeSchema = dna.object({
  value: dna.number(),
  get children() { return dna.array(NodeSchema); },
});

メタデータ

const field = dna.string().meta({ title: "メールアドレス", description: "ユーザーのメールアドレス" });
const field2 = dna.string().describe("ユーザーのフルネーム");

さらに読む

  • README — 完全なAPI、エントリポイント、CLI Union、外部依存関係
  • レシピ — 15以上のコピペレシピ(オブジェクトモード、再帰、パイプ、リファインメント、型強制、テンプレートリテラル、関数スキーマ、レコード/マップ/セット、デフォルト値、ブランディング、メタデータ)
  • 技術ドキュメント — DNAオペコード、アーキテクチャ、コード生成内部
  • Zod比較 — DNAとZodの機能比較
  • 外部依存関係 — トランスフォーム用の外部メカニズム
  • シリアライゼーション — DNAシリアライゼーション形式
  • オペコードパターン — オペコードの使用パターン

@ytrynot/schvalid — JSON Schema to DNA コンパイラ

インストール

npm install @ytrynot/schvalid

JSON SchemaをDNAに変換してバリデーション

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 }); // true
validate({ name: "Jo" });            // false(minLength 3)

// エラー収集を含む完全なパース
const parse = parser(dna);
parse({ name: "John", age: 30 }); // { success: true, data: { name: "John", age: 30 } }
parse({ name: "Jo" });            // { success: false, errors: [...] }

一度コンパイルして複数回バリデーション(ビルダーAPI)

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

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

// 4つのモード:
schvalid("validation"); // 真偽値バリデーター(失敗時即座に終了)
schvalid("parser");     // エラー収集 + 新しい出力オブジェクトを伴うパーサー
schvalid("fast");       // ハイブリッド:最初にバリデーション、失敗時のみパース(成功時はdata === input)
schvalid("all");        // { validate, parse, parseFast } — すべて一度にコンパイル、インスタンスを共有

サポートされているJSON Schema 2020-12機能

  • 型: string, number, integer, boolean, null, object, array
  • 制約: properties, required, items, additionalProperties, minItems, maxItems, minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, format, const, enum
  • ロジック: anyOf, allOf, oneOf, if/then/else, not
  • OpenAPI 3.1: discriminator
  • 参照: 内部 $ref のみ(外部URIは不可)

制限事項

  • 外部 $ref は不可(HTTP URI、URN、外部ファイル)
  • カスタムフォーマットは不可(標準フォーマットのみ: email、uuid、uri など)
  • 非同期バリデーション、$data、型強制、デフォルト値の注入は不可

さらに読む

  • README — 完全なAPI、モード、パフォーマンス
  • AJV比較 — schvalidとAJVの機能比較

@ytrynot/qb — SQLite クエリビルダー

インストール

npm install @ytrynot/qb
# スキーマ駆動DDLのためのオプショナルpeer依存関係:
npm install zod        # Zod v4スキーマイントロスペクション用
npm install @ytrynot/dna  # DNAスキーマイントロスペクション用

フルエントDMLクエリ

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

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

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);
// Zodバージョンと同じSQL出力

.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

ドライバーによる実行

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

DDL用メタデータキー

キー効果
pk: truePRIMARY KEY
pkauto: truePRIMARY KEY AUTOINCREMENT
unique: trueUNIQUE
default: valueDEFAULT value
fk: { table, column }REFERENCES table(column)

さらに読む


@ytrynot/cli — DNAバリデーション対応CLIルーター

インストール

npm install @ytrynot/cli

ルートを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: "ビルドするファイル" }),
}).meta({ cli: { routeId: "build" }, description: "プロジェクトをビルド" });

const deployRoute = dna.object({
  cmd: dna.literal("deploy"),
  target: dna.string().optional().meta({ description: "デプロイ先" }),
}).meta({ cli: { routeId: "deploy" }, description: "プロジェクトをデプロイ" });

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

実行(レイヤー1 — 同期ルーティング + バリデーション)

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: [...] }

ハンドラー + フォーマッター + process.exit を含む完全なCLI

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} ファイルをビルドしました` }),
  deploy: (payload) => ({ success: true, data: `${payload.target ?? "デフォルト"} にデプロイしました` }),
};

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

const run = fullCli(cliFactory(executeContract(contract, handlers), formatter));
await run(); // process.argv.slice(2) を読み取り、出力して終了

--help / --version の追加

const helpRoute = dna.looseObject({ cmd: dna.literal("help") }).meta({ cli: { routeId: "help", noArgs: true, description: "ヘルプを表示" } });

const versionRoute = dna.looseObject({ cmd: dna.literal("version") }).meta({ cli: { routeId: "version", noArgs: true, description: "バージョンを表示" } });

const contract = createContract({
  targets: [buildRoute, deployRoute, helpRoute, versionRoute],
  help: { enable: true },          // --help を有効にする
  version: { pkg: { version: "1.0.0" } }, // --version を有効にする
});

AOT(事前コンパイル):オフラインで契約をコンパイル

// aot.ts
import { compileContract } from "@ytrynot/cli/aot";
import { buildRoute, deployRoute } from "./routes";

const routes = compileContract({
  name: "mycli",
  targets: [buildRoute, deployRoute],
  cli: { argv: ["cmd"], env: { ... } },
  withByteCode: true,
  serializer: { file: "./mycli.json" },
  exportDefault: "cli",
  env: { "FORCE_COLOR": "1" },
});
npx aot aot.ts
# 出力: routes.js (非同期) または routes.cjs (CommonJS)

さらに読む

  • README — 完全なAPI、サイレントモード、エラーマッピング

関連スキル