Skip to content

@agentaily/aml

Agentaily Modeling Language(AML) —— 我们自家的声明式数据建模 DSL。自包含、 零依赖。Schema 就是纯 .aml 文本:model / enum / datasource / generator 块。

表层语法参考自 Prisma 的 schema 语言(一种熟悉、紧凑、易解析的形态)—— 但 AML 是我们自家的语言,不是 Prisma、也不绑定它。

它做四件事:

函数是什么为什么
parse(src)手写 lexer + 递归下降 parser → AST语法
validate(ast)语义检查 → 带行 / 列的 diagnostics语义
print(ast)规范化、幂等的格式化器(可 round-trip)输出
emit(ast)AST → 物理表(DDL)+ 一个自描述的数据 API descriptor派生

它为什么存在: 给大模型一种声明式、基于文本的建模语言,它是被解析的、绝不 eval。模型吐出 AML 文本;我们把它解析成纯数据 AST,回递精确的错误 (line:col error: …),让它自修 —— 经典的 generate → check → fix 循环,且毫无执行 模型自撰代码的风险(那是你让它吐 zod/TS 会遇到的)。

内部包(private: true),不发布。零运行时依赖。

API

ts
import { parse, validate, print, check, format, formatDiagnostic } from '@agentaily/aml';

const src = `
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  posts Post[]
}`;

const { ast, diagnostics } = parse(src);   // syntax only
const semantic = validate(ast);            // names resolve, no dupes, relations point home
const all = check(src).diagnostics;        // parse + validate in one pass
const pretty = format(src);                // parse → canonical text (column-aligned)

for (const d of all) console.log(formatDiagnostic(d)); // "5:3 error: Unknown type 'Pst' …"
  • parse尽力而为(best-effort):语法错误会被记成一条 diagnostic、parser 随后恢复,所以你仍能一次拿到一个(部分的)AST 外加每一个错误。
  • check(src){ ast, diagnostics },把语法 + 语义的发现合在一起。
  • format(src) → 规范化文本;幂等(format(format(x)) === format(x))。

Emit —— AML → 表 + 一个自描述的数据 API

AML schema 是一个 vertical 后端数据的单一真相源。emit 从一个解析好的 AST 派生出 两样东西:物理表(于是 schema 能直接生成 DDL),以及一个类型化、可内省的 API descriptor(「GraphQL 精神」),运行时把它开成查询 / 写入端点 —— LLM 撰写的 index.html 则 READ 它来决定调哪些端点。

ts
import { check, emit, emitTables, emitApiDescriptor } from '@agentaily/aml';

const { ast } = check(src);              // emit assumes a validated AST (throws on an unknown type)

const { tables, sql, api } = emit(ast);  // one shot; or call the two halves on their own:
emitTables(ast);        // → { tables: TableDef[], sql }   physical structure + DDL
emitApiDescriptor(ast); // → { entities, enums }           the introspectable contract
  • emitTables(ast){ tables, sql } —— 每个 model 一个 TableDef(namemodelcolumnsprimaryKeyuniquesindexes,以及它自己的 CREATE TABLEsql)。结果层级的 sql 是完整 DDL:每个 CREATE TABLE IF NOT EXISTS 加每个 CREATE INDEX,用 SQLite / D1 方言。
  • emitApiDescriptor(ast){ entities, enums } —— 每个 model 一个 EntityDescriptor(逻辑字段名、类型、required/unique/isId、背后的 column、 enum 值、关系目标),于是客户端能内省「有哪些 entity/字段、我怎么读 / 写它们」。

类型映射(SQLite/D1): StringTEXT · Int/BooleanINTEGER(bool 存为 0/1FloatREAL · DateTimeINTEGER(epoch ms;descriptor 标 format: 'epoch-ms'JsonTEXT(form 的「答卷」全答案列)· Type[]TEXT (JSON 数组)· enum 引用 → TEXT(descriptor 带上允许的值)。Type? → 可空; @id/@@id → PRIMARY KEY;@unique/@@unique → UNIQUE;@@indexCREATE INDEX; @map/@@map → 物理列 / 表名。

什么是、什么不是列: 类型为标量或 enum 的字段成为一个物理列;类型为另一个 model 的字段是一个关系(导航)—— 它没有列(只有那个标量 FK 字段才有),但它仍以 可导航关系的身份出现在 API descriptor 里。系统列被强加:AML model 声明它们自己的 键,所以列恰好就是被建模的那些字段。

默认值: 字面量 @default(...)(string/number/bool/enum)→ 一个 SQL DEFAULT; Int @id 上的 autoincrement()INTEGER PRIMARY KEY AUTOINCREMENT; now()/cuid()/uuid() 会在 TableDef/descriptor 上被结构化地捕获,但吐出 SQL DEFAULT(它们由运行时 / app 填)。

支持的子集

块: datasourcegeneratormodelenum

标量类型: StringIntFloatBooleanDateTimeJson。任何其它类型名 都必须解析到一个 model 或 enum,否则就是一个 Unknown type 错误。

字段修饰符: 可选 Type?、列表 Type[]

字段属性: @id@unique@default(…)@map(…)@updatedAt@relation(…) (含命名参数,如 @relation(fields: [authorId], references: [id]))。文法接受任意 @name(args…),所以未知属性也能解析 —— 由 validate 来裁决它们。

块属性: @@id@@unique@@index@@map

值: 字符串、数字、布尔、标识符 / enum 引用、函数调用(now()autoincrement()env("…")cuid()),以及数组([a, b])。

注释: /// 文档注释附着到下一个声明 / 字段上(以 documentation 留在 AST 上); // 注释被丢弃。

validate 检查什么

  • 顶层名重复(model + enum 共用一个命名空间)。
  • 一个 model 内字段重复;一个 enum 内值重复。
  • 字段类型解析到一个标量、model 或 enum。
  • @relation(fields: […]) 列的字段在该 model 上存在。
  • @@id / @@unique / @@index 引用的字段存在。
  • 警告: 一个有字段但没有 @id / @@id 主键的 model。

刻意不在范围内(「medium」子集)

原生类型映射(@db.VarChar、…)、BigInt / Decimal / Bytes、复合 type 块、 多 schema / 多 datasource 的边角情况。这些按设计会报 Unknown type / 意外语法的错 —— 这里的 LLM 驱动数据建模不需要它们。

AST 形状

parse 返回 { ast: SchemaAst, diagnostics: Diagnostic[] }:

ts
interface SchemaAst {
  datasources: DatasourceDecl[]; // { name, assignments: { key, value }[] }
  generators:  GeneratorDecl[];  // same shape as datasource
  models:      ModelDecl[];      // { name, fields, attributes(@@…), documentation? }
  enums:       EnumDecl[];       // { name, values, documentation? }
}

每个节点都带一个 Span({ start, end },含 0 起始的 offset + 1 起始的 line/column),所以 diagnostics —— 以及你自己的工具 —— 都能指向精确的源位置。 完整集合见 src/types.ts

开发

bash
pnpm --filter @agentaily/aml typecheck   # or: pnpm exec tsc --noEmit -p aml/tsconfig.json
pnpm --filter @agentaily/aml test        # or: pnpm exec vitest run --root aml

已在 monorepo workspace 里注册(@agentaily/aml),并被根 vitest run 收录。