@amritk/ 适应器
将TypeBox,Zod,Valibot,Effect,或Apache Avro中编写的计划转换为Draft 2020-12 JSON Schema——单输入会塑造mjst生成器的理解.
概览
mjst的发电机——解析器,验证器,类型,docs,测试数据——全部取用 一个 a 2020-12 JSON Schema草案。 这个软件包让你在别的地方写出这个计划。 它能转换一个 类型框, (中文(简体) ), 佐德语Name, (中文(简体) ), 维利博特,或 效果 计划——或 阿帕奇·阿夫罗 .avsc 然后将结果直接交给发电机.
这个 mjst CLI 电线在后面 --input <format> 旗帜( n)typebox, (中文(简体) ), zod, (中文(简体) ), valibot, (中文(简体) ), effect, (中文(简体) ), avro) (中文(简体) ). 对于图书馆格式 --schema jS/TS的路径点 模块 出口一个计划而不是一个 .json 文件; 用于 avro 它指着 .avsc 文档本身,它被读作数据,从未被导入。 也可以直接调用适配器——每个适配器都是一种小而纯的功能.
图书馆适配器依靠源代码库自己的JSON Schema出口商(Zod 4's) toJSONSchema, (中文(简体) ), @valibot/to-json-schema,效果为 JSONSchema.make; TypeBox schemas 在运行时已经是JSON Schema了),然后使结果正常化. Avro是例外——它没有这种出口商,因此在这里完全实行转换(不需要依赖,因为Avro schema本身就是JSON). 正常化 :
- 将方言标记剥去( r)
$schema发电机不需要, - 重写构造 JSON Schema 无法表达——运行时间
Date, (中文(简体) ),bigint——进入共享x-mjst提示发电机读取,以及 - 警告和继续 因此一个没有支持的字段从不阻挡生成(通过)
{ strict: true }来做一个损失的构造抛出.
安装
npm install --save-dev @amritk/adapters
# or: pnpm add -D / yarn add -D / bun add -d
同伴依赖关系
源代码库是 可选同伴依赖关系 – 每个适配器在运行时动态导入其库,所以您只安装实际使用的库. TypeBox 根本不需要对等依赖(它的chemas是普通的 JSON Schema 对象;适配器从不导入 TypeBox ).
| 输入格式 | 安装 |
|---|---|
typebox | (无-见下文) |
zod | zod@>=4, (中文(简体) ), 或者说 zod@3 加号 zod-to-json-schema@>=3 |
valibot | valibot@>=1 财务报告和审定财务报表 @valibot/to-json-schema@>=1 |
effect | effect@>=3 |
avro | (没什么——一个Avro计划是普通的JSON) |
如果缺少所需的软件包,适配器会抛出一个清晰,可操作的错误来命名要安装什么——例如. "Zod适配器要求要么'zod' v4+(对于它的原生到JSONSchema),要么"zod-to-json-schema"包(Zod 3的倒置). 也没有找到——在你的项目中安装一个"
[注] TypeBox 适配器故意这样做 没有 导入 TypeBox 。 其作用纯粹是用在 schema 的平面对象外形上(通过一个 JSON 的回转),因此 TypeBox 保持了您的 schema 模块的依赖性,从来没有mjst.
使用量
每个适配器是一个函数 (source: unknown, options?: { strict?: boolean }) => Promise<JSONSchema> (TypeBox 和 Avro 适配器是同步的,但通过 getAdapter 具有相同签名的统一性 await 当你通过它. strict: true (Zod, Valibot, Avro)。 通过子路径导入它们 :
import { avroToJsonSchema } from '@amritk/adapters/avro-to-json-schema'
import { typeboxToJsonSchema } from '@amritk/adapters/typebox-to-json-schema'
import { zodToJsonSchema } from '@amritk/adapters/zod-to-json-schema'
import { valibotToJsonSchema } from '@amritk/adapters/valibot-to-json-schema'
import { effectToJsonSchema } from '@amritk/adapters/effect-to-json-schema'
或按名称解决一个问题——匹配CLI的 --input 旗帜 :
import { getAdapter } from '@amritk/adapters/get-adapter'
const adapter = getAdapter('zod') // throws for an unimplemented / unknown format
const jsonSchema = await adapter.toJSONSchema(mySchema)
适配器接收 已装入 schema值(一个导入的模块导出),而不是一个文件路径——加载模块是呼叫者的工作,它使适配器保持了纯度和微不足道的测试.
类型框
import { Type } from '@sinclair/typebox'
import { typeboxToJsonSchema } from '@amritk/adapters/typebox-to-json-schema'
const User = Type.Object({
id: Type.Integer(),
name: Type.String({ minLength: 1 }),
createdAt: Type.Date(),
})
const jsonSchema = await typeboxToJsonSchema(User)
一个TypeBox schema在运行时已经是一个JSON Schema对象——它只是携带了不可假设的符号键(Kind, (中文(简体) ), Optional,.)用于TypeBox自己的机械. JSON 将这些(和任何 undefined 值),留下了干净的平地方案. 适配器然后重写类型框 扩展类型 输入 x-mjst 暗示.
佐德语Name
[大 Zod 4更喜欢. 适配器依赖Zod的本土
toJSONSchema,在Zod 4之前不存在(见src/zod-to-json-schema.ts) (中文(简体) ). 在Zod 3上,它回到可选的zod-to-json-schema软件包 —— 与 Zod 3 一起安装 —— 并且如果没有可用的软件包, 你就会得到一个明显的错误而不是一个沉默的错失.
import { z } from 'zod'
import { zodToJsonSchema } from '@amritk/adapters/zod-to-json-schema'
const User = z.object({
id: z.number().int(),
name: z.string().min(1),
createdAt: z.date(),
})
const jsonSchema = await zodToJsonSchema(User)
z.date() 财务报告和审定财务报表 z.bigint() 没有 JSON Schema 代理, 并且会让 Zod 的出口商丢出; 适配器会用 unrepresentable: 'any' 并使用 override 钩去救他们 x-mjst 暗示。 它还沿途修理了两道Zod 4克:它恢复了固定拖带上被捆绑的长度(Zod发出一个赤裸的,没有捆绑的) prefixItems),它合并了一个对象 相交点 佐德发泄为不满足 allOf 联 合 国 additionalProperties: false 分支变成一个单独的封闭对象.
维利博特
import * as v from 'valibot'
import { valibotToJsonSchema } from '@amritk/adapters/valibot-to-json-schema'
const User = v.object({
id: v.pipe(v.number(), v.integer()),
name: v.pipe(v.string(), v.minLength(1)),
createdAt: v.date(),
})
const jsonSchema = await valibotToJsonSchema(User)
需要两者 valibot 财务报告和审定财务报表 @valibot/to-json-schema就像Zod一样, v.date() 编号 : v.bigint() 被救进 x-mjst 通过转换器提示 overrideSchema 钩. 转换器运行在 errorMode: 'ignore' (2020-12年目标草案),因此任何其他无支持的构造都降解为开放式计划;mjst收集并分批报告 [mjst] Valibot adapter: … 警告,不要扔东西.
效果
import { Schema } from 'effect'
import { effectToJsonSchema } from '@amritk/adapters/effect-to-json-schema'
const User = Schema.Struct({
id: Schema.Number,
name: Schema.String,
createdAt: Schema.DateFromSelf, // runtime Date — see the caveat below
})
const jsonSchema = await effectToJsonSchema(User)
要求数 effect@>=3A级 Schema.BigIntFromSelf 编号 : Schema.DateFromSelf 树上的任何地方 ——顶层或嵌入于结构、阵列或结合中——被救入 x-mjst 提示; 每个子树 JSONSchema.make 接受从它逐字取出. 读取 编码代表说明 在选择之间之前 Schema.Date 财务报告和审定财务报表 Schema.DateFromSelf - 它改变你是否得到一个 string 或运行时间 Date。 。 。 .
阿芙罗
import { readFile } from 'node:fs/promises'
import { avroToJsonSchema } from '@amritk/adapters/avro-to-json-schema'
const avro = JSON.parse(await readFile('user.avsc', 'utf-8'))
const jsonSchema = avroToJsonSchema(avro)
没有对等依赖性:一个Avro schema是JSON文档,因此适配器直接读取. 每个被命名的类型—— record, (中文(简体) ), enum, (中文(简体) ), fixed - 在其定义下界定一次 全名 内 $defs 参考文献 $ref 随处可见,所以递归型保持有限度 com.example.User 生成一个 ComExampleUser 类型而不是每个使用站点重复的内线形状.
选择编码其实是指
Avro 是一个二进制格式,带有 单独指定 JSON编码,而"JSON for this schema"的两种读取,则真正有分歧. 适配器让你选择:
encoding | 说明 | 工会 | bytes | 带 a 的字段 default |
|---|---|---|---|---|
'json' (默认) | 您的应用程序代码查看的对象 | 简单 anyOf· ; ["null", T] 崩溃为无效 T | 基础64 字符串 | 选项 |
'avro-json' | spec's JSON编码——什么是旅行 application/vnd.apache.avro+json | 用分支的全名标记的单键包装对象 | 密码点/ 字节字符串 | 所需经费 |
// Generating TypeScript types and parsers? You want the idiomatic shape.
avroToJsonSchema(avro)
// Validating an AsyncAPI examples.payload? You want the wire shape.
avroToJsonSchema(avro, { encoding: 'avro-json' })
这个 default 栏不是结构选择。 阿芙罗有 无可选字段 编码中包含每个已申报的字段,a default 仅在计程解析时查阅,读取对一 不同 计划 这么说吧 'avro-json' 标出每个需要的字段, 因为那是线上的东西,同时 'json' 将默认字段视为可选的,因为这就是处理的形状应用程序代码.
默认 价值 因为Avro不是用编码来写的 Avro宣称工会违约 赤线 工会价值 第一个 分支,所以在 'avro-json' 它被包裹,以匹配数据使用的分支标记(null 保持赤裸,在两个 "null" 财务报告和审定财务报表 {"type": "null"} )),拼写. 下 'json' a字节默认在错误的字母表中——Avro写作"latin-1",平面形状为"base64"——所以它被抛出而不是被误译. 这两种规则都适用于任何深度:在记录、阵列或地图默认内嵌入的结合值都以同样的方式被标记,默认内任何地方的字节值都会降低整个情况,因为半翻译的默认比无更糟糕。 这比错误的说明更重要,因为 @amritk/validation 胁迫 default。 。 。 .
故意不修炼的东西
两幅地图看起来像是遗漏,不是:
- A 类
long没有界限. 其范围为±2^63,没有JSON数字能够代表——一个已声明的maximum将转至2^63, 既错误又无法到达。 (单位:千美元) A 类long过去2^53将无法生存JSON.parse不论计划如何, 一个int实值 绑定,因为±2^31 降落在正双. - 日期和时间逻辑类型保持整数. Avro 编码
timestamp-millis作为long在它的JSON编码 和二进制一样多,所以format: 'date-time'将描述一个从未到达的字符串。 仅uuid将基础类型真正缩小到{ type: 'string', format: 'uuid' }。 。 。 .
decimal 财务报告和审定财务报表 duration 承载结构 JSON Schema 无法表达(精度/分级;以12字节表示3个未署名的32位英寸),因此它们会降解为基型并被通过通常的拓宽警告报告. 一个未识别 logicalType avro谱系要求的、悄悄地进入其基准类型——正如在它没有界定的基数上申报的基数一样(Avro谱系)decimal 编号 record因此,它被忽略了,而不是报告. aliases 和外地 order 描述如何 两个 策略在解析期间相关,在单一文档的形状上没有位置,因此被忽略. 名称 为: 校正: 正写成 $defs 键和 $ref 指着它,所以一个非法的名字会默默地产生不同的,破碎的JSON指针而不是错误.
这个 x-mjst 扩展
JSON Schema 的核心词汇没有运行时间的关键词 Date页:1 bigint或者名义上的品牌 mjst携带这些作为供应商延伸, x-mjst,使生成器读取出正确的类型脚本和运行时间检查。 同一对象带有文档设置 @amritk/generate-markdown (单位:千美元)hidden,所有东西在 markdown发电机忽略了它
x-mjst 提示 | 生成处理 |
|---|---|
{ instanceOf: 'Date' } | 类型为 Date,已选中 instanceof |
{ primitive: 'bigint' } | 类型为 bigint,已选中 typeof |
{ brand: 'UserId' } | 类型为 Base & { readonly __brand: 'UserId' }; 验证为 Base 运行时间 |
instanceOf 编号 : primitive 由适配器发射(适配器地图) 不变 提示由图书馆编写,所以 Date 生成在任何地方都相同的数据). brand 实值 手写 ——没有适配器在今天发出——而且纯粹是类型级别:运行时间值仍然验证为它的基础JSON Schema类型,但生成的TypeScript类型与一个独特的品牌相交,所以是一个 UserId 无法与平原相替换 string。见 名义品牌 见下文.
每个图书馆 Date 编号 : bigint 资料来源:
Date → instanceOf: 'Date' | bigint → primitive: 'bigint' | |
|---|---|---|
| 类型框 | Type.Date() | Type.BigInt() |
| 佐德语Name | z.date() | z.bigint() |
| 维利博特 | v.date() | v.bigint() |
| 效果 | Schema.DateFromSelf | Schema.BigIntFromSelf |
名义品牌
{ brand: 'Name' } 给一个计划 名义 (品牌)类型不作更改
它接受在运行时间。 附加到任何计划和生成的类型脚本
与唯一标记相交的碱基类型 :
{ "type": "string", "format": "uuid", "x-mjst": { "brand": "UserId" } }
// generated type: (string & { readonly __brand: 'UserId' })
// runtime check: still just "a string" (plus the uuid format check)
因为品牌只生活在类型中,两个有不同品牌的ID不是
互相分配,所以你不能通过一个 OrderId 其中a UserId 实值
- 同样的保护 Drizzle's .$type<UserId>() 现在给一列
由计划携带。 名称必须匹配 ^[\w$ -]+$ (它嵌入于一个
生成输出中的字符串字元; 一个不安全的名称被忽略 .
两种模式都以相同的方式尊重品牌:
- 页:1 代码生成器 (单位:千美元)
@amritk/validation(等等) 把它放进去.d.ts· ; - 类型级别
FromSchema读它,所以任何东西 从活的图文中打出——最出名的是@amritk/api路径params编号 :query编号 :body- 把牌子放在你的处理器里 宣布一个 路由参数{ type: 'string', 'x-mjst': { brand: 'UserId' } }制造params.id(单位:千美元)UserId,不是一个平地string,端对端(处理器) 财务报告和审定财务报表 页:1 生成的打字客户端). 写入内含图案或as const所以,字义 生存的推断.
品牌形状是
{ readonly __brand: 'UserId' }如果你想成为 不变 键入为 Drizzle-branded id, 定义该 id 到此形状( 或标记 Drizzle专栏匹配; mjst 校正一个会场, 它不重复 Drizzle 的 拥有品牌符号.
丢失构造并扩展警告
一些来源类型没有忠实的JSON Schema代表,而且 没有 被救进一个 x-mjst 指点 适配器不但没有辜负整个转换,反而拓宽了这些,以"接受任何东西"({}()——这意味着 生成类型比源计划宽 - 并发出一个 [mjst] 警告,所以扩张是可见的,绝不沉默。 每个库的行为 :
- 赵德. 这些Zod类型成为"接受任何东西":
symbol, (中文(简体) ),nan, (中文(简体) ),void, (中文(简体) ),undefined, (中文(简体) ),never, (中文(简体) ),map, (中文(简体) ),set, (中文(简体) ),promise, (中文(简体) ),function。当出现时,适配器记录,例如: "[妙. Zod适配器:功能,符号没有完整的JSON Schema代表并被拓宽. 生成的型号将比"Zod schema"更宽" - 维利博特. 转换器运行在
errorMode: 'ignore': 不支持的构造会降解为开放的 schema, 适配器会从转换器的覆盖钩中收集每一个这样的构造, 并将其报告在一个[mjst] Valibot adapter: …警告与Zod的形状相同. - 类型框. 扩展
type没有映射的字符串(见下文)没有变化,但有警告: "[mjst] TypeBox type"..."没有JSON Schema或x-mjst映射;没有更改" - 阿维罗. 只有
decimal财务报告和审定财务报表duration逻辑类型扩大,每个缩小到其基类型: "[妙. Avro适配器:十进制逻辑类型(精度/尺度)没有完整的JSON Schema代表并被拓宽. 生成的型号将比"阿夫罗"(Avro schema)更宽" Avro地图上的其他所有内容都准确无误,或者被直接拒绝——重复名称,提及未定义名称,或者不正确record编号 :enum编号 :fixed抛出而不是转换成错误的东西. - 影响. 效果没有扩大——它更严格.
JSONSchema.make扔在任何无法代表的类型, 无论它坐在哪里。 适配器在结构上捕捉并下降,重建容器(结构、阵列、结合、精炼.),并拯救每一个BigIntFromSelf编号 :DateFromSelf叶子会伸入x-mjst指点 只有一朵叶子,它不能拯救它symbol,说)是致命的,然后它扔出一个可操作的信息而不是Effect的不透明信息:将类型替换为JSON的可代表信息,或者添加一个jsonSchema该领域的说明.
如果在您的计划上有任何这些问题, 则选择一个具有代表性的替代方案( 如将一组模型作为数组) , 或者添加库本身的 JSON Schema 注释 .
洞穴
扩展类型( TypeBox)
类型框释放出非标准 type 用于运行时类的字符串(例如:. Type.Date() 生产 { type: 'Date' }) (中文(简体) ). 适配器识别出7个核心JSON Schema类型,并处理其他任何东西 type 作为类型框的槽 扩展类型。它理解的扩展类型图目前覆盖 仅限 Date 财务报告和审定财务报表 bigint (见 src/typebox-to-json-schema.ts:11-19:
扩展类型框 type | 绘制到 |
|---|---|
Date | x-mjst instanceOf: 'Date' |
bigint | x-mjst primitive: 'bigint' |
任何其他扩展类型(Uint8Array, (中文(简体) ), Symbol, (中文(简体) ), Undefined,.)与上面的正在扩大的警告没有关系;随着发电机获得更多类型的处理,通过扩展该地图而增加了支持.
效果编码 有线 任职人数
效果模型作为值 解码/编码对,以及 JSONSchema.make 描述 编码 (有线)代表——不是运行时间类型. 这是最让你惊讶的警告:
Schema.Date解码 aDate从字符串,所以它转换为string计划——不是运行时间Date。适配器通过这个没有改变,因为它准确地反映了Effect对电线的期望.- 只有
*FromSelf变式 -Schema.DateFromSelf, (中文(简体) ),Schema.BigIntFromSelf——描述运行时间值本身,这些是被救入的x-mjst运行时类型提示(参见src/effect-to-json-schema.ts:121-122) (中文(简体) ).
因此:想要生成 Date作者 Schema.DateFromSelf。想要一个字符串,将效果描述为 Date作者 Schema.Date 并期望 string 在生成的输出中。 同样的区别适用于: Schema.BigInt (-) string对 Schema.BigIntFromSelf (-) bigint) (中文(简体) ).
相关软件包
@amritk/mjst· 通过下列方式摄取这些格式的CLI:--input <format>@amritk/validation• 妇女@amritk/generate-markdown• 妇女@amritk/generate-examples这些适配器提供的发电机@amritk/helpers——定义共享x-mjst扩展