ytn — 套件使用指南
ytn(whY Try Not)提供可攜式、可編譯為 JavaScript 的 TypeScript 套件,用於結構驗證、SQL 生成和 CLI 路由。所有套件均為純 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 轉 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 — 所有結構來源、中繼資料鍵、外來鍵、索引、複合主鍵
- 操作指南:進階 — 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"] },
});
執行(第一層 — 同步路由 + 驗證)
const result = execute(contract, ["build", "a.ts", "b.ts"]);
execute(contract, ["deploy", "--target", "prod"]);
execute(contract, ["unknown"]);
完整的 CLI,包含處理器、格式化器和 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} 個檔案` }),
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") }