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">()
세부 규칙 (Refinement)
dna.string().refine((s) => s.length > 3, "Too short");
dna.array(dna.string()).superRefine((val, ctx) => {
if (val.length > 10) ctx.addIssue({ code: "too_big", maximum: 10, message: "Too many" });
});
변환 + 파이프
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: "Email", description: "User email" });
const field2 = dna.string().describe("User's full name");
추가 자료
- README — 전체 API, 진입점, CLI Union, 외부 종속성
- 레시피 — 15개 이상의 복사-붙여넣기 레시피 (객체 모드, 재귀, 파이프, 세부 규칙, 강제 변환, 템플릿 리터럴, 함수 스키마, 레코드/맵/셋, 기본값, 브랜딩, 메타데이터)
- 기술 문서 — DNA opcode, 아키텍처, 코드 생성 내부
- Zod 비교 — DNA 대 Zod 기능 비교
- 외부 종속성 — 변환을 위한 외부 종속성 메커니즘
- 직렬화 — DNA 직렬화 형식
- Opcode 패턴 — opcode 사용 패턴
@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: "Files to build" }),
}).meta({ cli: { routeId: "build" }, description: "Build the project" });
const deployRoute = dna.object({
cmd: dna.literal("deploy"),
target: dna.string().optional().meta({ description: "Deployment target" }),
}).meta({ cli: { routeId: "deploy" }, description: "Deploy the project" });
const contract = createContract({
name: "mycli",
description: "A demo 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: `Built ${payload.files?.length ?? 0} files` }),
deploy: (payload) => ({ success: true, data: `Deployed to ${payload.target ?? "default"}` }),
};
const formatter: ts.FormatterFn = (result) => {
if (result.success) return { exit: 0, message: String(result.data ?? "") };
return { exit: 1, message: `Error: ${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" },
description: "Show help",
});
const versionRoute = dna.looseObject({ cmd: dna.literal("version") }).meta({
cli: { routeId: "version" },
description: "Show version",
});
const contract = createContract({
name: "mycli",
description: "A demo CLI",
targets: [buildRoute, deployRoute, helpRoute, versionRoute],
cli: { positionals: ["cmd"], helpRoute: "help", versionRoute: "version" },
});
네 가지 계층
| 계층 | 함수 | 역할 |
|---|
| 1 | execute(contract, args) | 동기 라우팅 + 검증 |
| 2 | executeContract(contract, handlers) | 핸들러에 라우팅 |
| 3 | cliFactory(fn, formatter) | 출력 형식화 |
| 4 | fullCli(fn) | process.argv 읽기, 출력, process.exit |
추가 자료
- README — 전체 API, AOT, getOpts, 오류 처리
- AOT 가이드 — 종속성이 없는 독립형 CLI를 위해 계약을 독립형 빌드로 컴파일
- 고급 오류 — 계층별 오류 처리
AI 에이전트를 위한 요약
이 패키지 모음은 언어 모델 에이전트가 이식 가능하고 최적화된 도구가 필요할 때 가장 유용합니다:
- 코드 생성:
.toDna()를 호출하여 스키마를 직렬화 가능한 바이트코드로 컴파일하고, validator()/parser()를 사용하여 독립형 검증기를 생성합니다. 런타임이나 추가 패키지 없이도 사용할 수 있습니다.
- 워크플로우 라우팅:
@ytrynot/cli는 계약에서 검증된 경로를 정의합니다. 자동 생성된 JSON 스키마는 도구 호출을 안내하고, 컴파일된 코드는 빠른 실행을 보장합니다.
- SQL 생성:
@ytrynot/qb는 SQL 문자열로 직접 변환되는 스키마를 정의합니다. 스키마 변경 시 SQL을 재생성하거나 유창한 API를 사용할 수 있습니다.
- 이식성: 컴파일된 출력은 순수 JavaScript이므로 모든 런타임(Node.js, Deno, 브라우저)에서 작동합니다. 한 번 컴파일하면 어디서나 실행됩니다.