ytn — パッケージ利用ガイド
ytn(Why Try Not)は、スキーマバリデーション、SQL生成、CLIルーティングのための、ポータブルでJSコンパイル可能なTypeScriptパッケージを提供します。すべてのパッケージは純粋なESM、厳格なTypeScript、可能な限り外部依存関係ゼロです。
パッケージ一覧
| パッケージ | npm | インストール | 使用場面 |
|---|
@ytrynot/dna | @ytrynot/dna | npm install @ytrynot/dna | Zodライクなスキーマバリデーションで、シリアライズ可能なバイトコードやスタンドアロンコンパイルバリデーターが必要な場合 |
@ytrynot/schvalid | @ytrynot/schvalid | npm install @ytrynot/schvalid | JSON Schema 2020-12を使用し、超高速バリデーションが必要な場合 |
@ytrynot/qb | @ytrynot/qb | npm install @ytrynot/qb | フルエントAPIまたはZod/DNAスキーマからSQLite SQL文字列を構築する必要がある場合 |
@ytrynot/cli | @ytrynot/cli | npm install @ytrynot/cli | DNAバリデーションルート、自動ヘルプ、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>;
バリデーション / パース
user.validate({ name: "John", email: "[email protected]" });
const result = user.safeParse({ name: "J", email: "bad" });
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();
const validate = validator(bytecode);
validate({ name: "John", age: 30 });
const parse = parser(bytecode);
parse({ 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.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">()
リファインメント
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");
再帰
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); },
});
メタデータ
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 });
validate({ name: "Jo" });
const parse = parser(dna);
parse({ name: "John", age: 30 });
parse({ name: "Jo" });
一度コンパイルして複数回バリデーション(ビルダーAPI)
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");
サポートされている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、型強制、デフォルト値の注入は不可
さらに読む
@ytrynot/qb — SQLite クエリビルダー
インストール
npm install @ytrynot/qb
npm install zod
npm install @ytrynot/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();
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;
users.getById;
users.insert;
users.update;
users.delete;
users.upsert;
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);
.req / .q によるカスタムクエリ
users.req.select("id", "name").where("id").toSQL();
users.req.upsert("email", "name").toSQL();
ドライバーによる実行
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 });
DDL用メタデータキー
| キー | 効果 |
|---|
pk: true | PRIMARY KEY |
pkauto: true | PRIMARY KEY AUTOINCREMENT |
unique: true | UNIQUE |
default: value | DEFAULT value |
fk: { table, column } | REFERENCES table(column) |
さらに読む
- クイックスタート — 完全なチュートリアル、エンドツーエンドのCRUD
- ハウツー:クエリ — SELECT, INSERT, UPDATE, DELETE, UPSERT, WHERE, JOIN, 順序付け、制限、ページネーション、テキスト検索、GROUP BY
- ハウツー:DDL — すべてのスキーマソース、メタデータキー、外部キー、インデックス、複合PK
- ハウツー:高度な機能 — EXISTS, CASE WHEN, サブクエリ、ウィンドウ関数, PragmaBuilder
- 機能リファレンス — 完全なメソッド一覧、SQLiteバージョンマトリックス
@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"]);
execute(contract, ["deploy", "--target", "prod"]);
execute(contract, ["unknown"]);
ハンドラー + フォーマッター + 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();
--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 },
version: { pkg: { version: "1.0.0" } },
});
AOT(事前コンパイル):オフラインで契約をコンパイル
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
さらに読む
- README — 完全なAPI、サイレントモード、エラーマッピング