ts-proto
将您的.proto
文件转换为强大的惯用性打字条文件!
TS-Proto的2.x释放迁移了低级原始序列化,其encode
和decode
方法从尊贵的,但老化和停滞的protobufjs
软件包使用到@bufbuild/protobuf
。
如果仅使用encode
和decode
方法,则应该在很大程度上是一个非破坏的更改。
但是,如果您使用了使用旧protobufjs
Writer
或Reader
类的任何代码,则需要更新代码以使用新的@bufbuild/protobuf
类:
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
如果迁移到@bufbuild/protobuf
是您的阻滞剂,则可以将ts-proto
版本固定在1.x
上。
免责声明和道歉:我打算将TS-Proto 2.x发布为Alpha版本,但没有使语义释放配置正确,因此TS-Proto 2.x作为主要版本发行,没有适当的Alpha /β周期。
如果您可以在发行时遇到的任何问题提交报告(或更好的PR!),这将不胜感激。
迁移中其他人的任何提示或技巧也将不胜感激!
TS-Proto
目录
概述
Quickstart
buf
ESM
目标
非目标
示例类型
亮点
自动批量 / n+1预防
用法
支持的选项
Nestjs支持
观看模式
基本的GRPC实现
赞助商
发展
假设
托多
一个处理
默认值和未设置字段
众所周知的类型
包装器类型
JSON类型(结构类型)
时间戳
数字类型
可选值的当前状态
TS-Proto从Protobuf模式生成打字稿类型。
IE给出了一个person.proto
。
消息人{字符串名称= 1; }
ts-proto将生成一个person.ts
文件,例如:
接口人{ 名称:字符串} const person = { encode(person):作者{...} 解码(读者):人{...} tojson(人):未知{...} fromjson(数据):person {...}}
它还知道服务,并将为他们生成类型,即:
导出接口Pingservice { ping(请求:pingrequest):Promise <PingResponse>;}
它还将生成PingService
的客户实现;当前支持TWIRP,GRPC-WEB,GRPC-JS和NESTJS。
npm install ts-proto
protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=. ./simple.proto
(请注意,输出参数名称ts_proto_out
是基于插件名称的后缀命名的,即--plugin=./node_modules/.bin/protoc-gen-ts_proto
参数,即_out
根据protoc
的CLI惯例。)
在Windows上,使用protoc --plugin=protoc-gen-ts_proto=".node_modules.binprotoc-gen-ts_proto.cmd" --ts_proto_out=. ./simple.proto
(请参阅#93)
确保您使用的是现代的protoc
(请参阅平台的安装说明,IE protoc
V 3.0.0
不支持_opt
标志
这将生成给定*.proto
类型的*.ts
源文件。
如果要将这些源文件包装到NPM软件包中以分配给客户端,只需像往常一样在它们上运行tsc
以生成.js
/ .d.ts
文件,然后将输出部署为常规的NPM软件包。
如果您使用的是BUF,请通过strategy: all
buf.gen.yaml
文件(文档)中的所有策略。
版本:v1plugins: - 名称:tsout:.. ../gen/tsstrategy:allpath:../ node_modules/ts-proto/protoc-gen-ts_proto
为了防止buf push
读取无关的.proto
文件,请像这样配置buf.yaml
:
构建:排除:[node_modules]
您也可以使用发布给BUF注册表的官方插件。
版本:v1plugins: - 插件:buf.build/community/stephenh-ts-protoout:../ gen/tsopt: - 输出服务= ... - useExactTypes = ...
如果您使用具有esModuleInterop
的现代TS设置或在ESM环境中运行,则需要通过以下方式传递ts_proto_opt
s:
esModuleInterop
esModuleInterop=true
tsconfig.json
importSuffix=.js
如果在ESM环境中执行生成的TS-Proto代码
就ts-proto
生成的代码而言,一般目标是:
惯用的打字稿/ES6类型
ts-proto
是内置的Google/Java式JS protoc
代码或“ Make .d.ts
文件*.js
注释” protobufjs
的方法
(从技术上讲, protobufjs/minimal
包用于实际阅读/写作字节。)
打字稿优先输出
界面上的类
尽可能多的类型只是接口,因此您可以像常规哈希/数据结构一样使用消息。
仅支持Codegen *.proto
-to- *.ts
工作流,当前没有动态.proto
文件的运行时反射/加载
请注意,TS-Proto不是开箱即用的RPC框架;取而代之的是,它更像是瑞士军刀(由其许多配置选项见证),它使您可以精确地构建自己想要的RPC框架(即最好与您公司的Protobuf生态系统融合在一起;对于更好或更糟的是,Protobuf RPC仍然是一个有点分散的生态系统)。
如果您想要在TS-Proto顶部建造的开箱即用的RPC框架,则有一些示例:
不错的GRPC
Starpc
(有关潜在贡献者的注意,如果您开发了使用ts-proto
其他框架/迷你框架,甚至是博客文章/教程,我们很乐意链接到它们。)
我们也不支持google.api.http
的Google Cloud API的客户端,如果您想提交PR,请参见#948。
生成的类型是“仅数据”,即:
导出接口简单{ 名称:字符串; 年龄:数字; 创建:日期|不明确的; 孩子:孩子|不明确的; 状态:状态; 孙子:孩子[]; 硬币:数字[];}
以及encode
/ decode
工厂方法:
导出const simple = { create(baseObject?:deeppartial <simple>):简单{...}, encode(消息:简单,作者:writer = writer.create()):writer {...}, 解码(阅读器:读者,长度?:编号):简单{...}, fromjson(对象:any):简单{...}, 源自分(object:deeppartial <simple>):simple {...}, tojson(消息:简单):未知{...},};
这允许使用惯用的TS/JS使用:
const bytes = simple.encode({name:...,age:...,...})。完成(); const simple = simple.decode(reader.create(bytes)); const {name,年龄} =简单;
在不创建类并调用正确的Getters/Setter的情况下转换/从其他层转换时,可以极大地简化集成。
一个穷人的尝试“请给我们可选类型”
规范的Protobuf包装器类型,即google.protobuf.StringValue
,映射为可选值,即string | undefined
,这意味着我们可以假装Protobuf类型系统具有可选类型。
(更新:TS-Proto现在也支持proto3 optional
关键字。)
时间戳被映射为Date
(可与useDate
参数配置。)
与protobufjs
不同, fromJSON
/ toJSON
使用Proto3典型的JSON编码格式(例如时间戳为ISO字符串)。
可以将objectids映射为mongodb.ObjectId
(可与useMongoObjectId
参数配置。)
(注意:目前仅由Twirp客户支持。)
如果您使用TS-Proto的客户端来调用后端微服务,类似于SQL应用程序中的n+1问题,那么微服务客户端很容易(在服务单个请求时)无意中触发多个单独的RPC呼叫“获取书1”,“获取书2”,“获取书3”,这确实应该分为单个“获取书籍[1、2、3]”(假设后端支持面向批处理的RPC方法)。
TS-Proto可以为此提供帮助,从本质上可以自动将您的个人“获取书籍”拨打到批处理“获取书籍”电话中。
为了使TS-Proto这样做,您需要使用以下批处理约定实现服务的RPC方法。
Batch<OperationName>
Batch<OperationName>
输入类型具有单个重复字段(即repeated string ids = 1
)
Batch<OperationName>
输出类型具有:
单个重复字段(即repeated Foo foos = 1
) ,其中输出顺序与输入ids
顺序相同或
输入的映射到输出(IE map<string, Entity> entities = 1;
)
当TS-Proto识别此模式的方法时,它将自动为客户端创建<OperationName>
的“非批量”版本,即client.Get<OperationName>
,该版本会采用单个ID并返回单个结果。
这为客户端代码提供了这样的幻觉,即它可以使个人Get<OperationName>
呼叫(在实现客户端的业务逻辑时通常更可取/更容易/更容易),但是TS-Proto提供的实际实现最终将成为Batch<OperationName>
致电后端服务。
您还需要启用useContext=true
构建时间参数,该参数为所有客户端方法提供了一个go style ctx
参数,并使用getDataLoaders
方法,该方法使ts-proto cache/resolve cache/nesove request request request scoped dataLoaders提供基本的自动批次加载检测/冲洗行为。
有关示例/更多详细信息,请参见batching.proto
文件和相关测试。
但是净效应是,TS-Proto可以为客户呼叫提供SQL- / ORM风格的n+1预防,这在大容量 /高度并行实现(如GraphQl前端网关)中尤其是至关重要的。 。
ts-proto
是一个protoc
插件,因此您可以通过它运行(直接在您的项目中,或者更有可能在单声道架构管道中,即像Ibotta一样,即):
将ts-proto
添加到您的package.json
运行npm install
以下载
用plugin
参数调用protoc
:
protoc -plugin = node_modules/ts-proto/protoc-gen-ts_proto ./batching.proto -i。
也可以使用Protobuf-Gradle-Plugin与Gradle一起调用ts-proto
:
Protobuf { 插件{//可以用任何未使用的插件名称代替,例如tsproto`ts { 路径='路径/到/插件'} } //仅当您提供插件optiongeneratePrototasks { all()。每个{task-> task.plugins {//必须匹配插件ID为abovets { 选项'foo = bar'} } } } }
生成的代码将放置在Gradle Build目录中。
使用--ts_proto_opt=globalThisPolyfill=true
,TS-Proto将包含一个全局thisthis的polyfill。
默认为false
,即我们假设globalThis
可用。
使用--ts_proto_opt=context=true
,该服务将具有GO风格的ctx
参数,该参数可用于跟踪/登录/etc。如果由于性能原因,您不使用Node的async_hooks
API。
使用--ts_proto_opt=forceLong=long
,所有64位编号将作为Long
的实例解析(使用长库)。
使用--ts_proto_opt=forceLong=string
,所有64位的数字将作为字符串输出。
使用--ts_proto_opt=forceLong=bigint
,所有64位编号将输出为BigInt
s。此选项仍然使用long
库在protobuf.js
内部内部进行编码/解码,但随后在TS-Proto生成的代码中转换为/从BigInt
S转换。
默认行为是forceLong=number
,它将在内部使用long
库来编码/解码值(因此您仍然会在输出中看到一个util.Long = Long
Line),但会将long
值转换为number
自动为您。请注意,如果执行此转换时,将抛出运行时错误,而64位值则比正确存储的number
要大。
使用--ts_proto_opt=useJsTypeOverride
,将输出64位的数字作为fieldOption.jStype在字段上指定。这比提供的forceLong
选项优先。
使用--ts_proto_opt=esModuleInterop=true
Change输出符合esModuleInterop
。
具体来说, Long
导入将作为import Long from 'long'
而不是import * as Long from 'long'
的长期导入。
使用--ts_proto_opt=env=node
或browser
或both
兼而有之,TS-Proto将在输出中做出特定于环境的假设。这两者都默认为both
,这没有特定于环境的假设。
使用node
将bytes
的类型从Uint8Array
更改为Buffer
,以便于通常使用Buffer
节点生态系统集成。
目前, browser
除了“不是node
”之外没有任何特定的行为。它可能很快/在某个时候。
带有--ts_proto_opt=useOptionals=messages
(用于消息字段)或--ts_proto_opt=useOptionals=all
(用于消息和标量字段),将字段声明为可选键,例如, field?: Message
而不是默认field: Message | undefined
。
ts-proto默认为useOptionals=none
因为它:
为了预防错字,可选字段使额外的字段轻松进入消息(直到我们获得确切类型),即:
界面somemessage { firstName:string; lastName:string;} //用typoconst data = {firstName:“ a”,lastTypo:“ b”}; // with useOptionals = none声明,这是正确的编译;如果“ lastName”是可选的,则不会发出消息:somemessage = {... data};
对于一致的API,如果SomeMessage.lastName
是可选的lastName?
,然后读者必须检查两个空的条件:a)是lastName
undefined
(b/c,它是在内存中创建的,并保持不设置),或者b)是lastName
空字符串(b/c,我们在电线上读了SomeMessage
, proto3规格,初始化的lastName
到空字符串)?
为了确保适当的初始化,如果以后的SomeMessage.middleInitial
添加,但它被标记为可选的middleInitial?
,您可能有许多在生产代码中的呼叫站点,这些呼叫网站现在应该通过middleInitial
来创建有效的SomeMessage
,但事实并非如此。
因此,在错觉,读取器的矛盾和正确初始化之间,TS-Proto建议使用useOptionals=none
作为“最安全”的选项。
综上所述,这种方法确实需要作家/创作者设置每个字段(尽管fromPartial
和create
旨在解决这个问题),因此,如果您仍然想拥有可选的键,则可以设置useOptionals=messages
= Message = useOptionals=all
。
(有关useOptional
的讨论,请参阅此问题。)
初始化消息时预防错别字,并且
为读者提供最一致的API
确保所有字段都适当初始化生产消息。
使用--ts_proto_opt=exportCommonSymbols=false
,诸如DeepPartial
和protobufPackage
之类的实用程序类型不会export
。
这应该使使用生成的输出的创建桶导入,即import * from ./foo
import * from ./bar
。
请注意,如果您在多个*.proto
文件中使用相同的消息名称,则仍然会得到导入冲突。
使用--ts_proto_opt=oneof=unions
,将生成oneof
字段作为ADT。
请参阅“单一处理”部分。
带有--ts_proto_opt=unrecognizedEnumName=<NAME>
枚举将包含一个键<NAME>
,其中包含一个unrecognizedEnumValue
选项的键。
默认值为UNRECOGNIZED
。
使用--ts_proto_opt=unrecognizedEnumValue=<NUMBER>
<数字>枚举将包含一个由unrecognizedEnumName
选项提供的键,值为<NUMBER>
。
默认为-1
。
使用--ts_proto_opt=unrecognizedEnum=false
Enums将不包含未识别的枚举键和值,而未unrecognizedEnumName
和unrecognizedEnumValue
选项。
使用--ts_proto_opt=removeEnumPrefix=true
生成的枚举将从成员中删除枚举名称。
FooBar.FOO_BAR_BAZ = "FOO_BAR_BAZ"
将生成FooBar.BAZ = "FOO_BAR_BAZ"
使用--ts_proto_opt=lowerCaseServiceMethods=true
,服务方法的方法名称将被降低/骆驼库,即service.findFoo
而不是service.FindFoo
。
使用--ts_proto_opt=snakeToCamel=false
,将在消息键和toJSON
/ fromJSON
方法中保留字段。
snakeToCamel
也可以将其设置为_
限制的字符串列表(逗号保留为标志界限),即--ts_proto_opt=snakeToCamel=keys_json
,其中包括keys
在其中,包括键在其中,将使json
keys casel casel casel be casel be casel casel casel casel casel be json keys ne json键是骆驼盒。
空字符串,即snakeToCamel=
,将两个消息键和JSON
键都作为蛇形案例(与snakeToCamel=false
相同)。
请注意,要使用json_name
属性,您必须使用json
。
默认行为是keys_json
,即两个都将是骆驼壳,如果设置,将使用json_name
。
使用--ts_proto_opt=outputEncodeMethods=false
, Message.encode
和Message.decode
方法用于使用ProtoBuf编码/二进制数据的方法将不会输出。
如果您只需要“类型”,这将很有用。
使用--ts_proto_opt=outputJsonMethods=false
, Message.fromJSON
和Message.toJSON
方法,用于使用JSON编码数据的方法将不会输出。
如果您只需“类型”,这也很有用。
使用--ts_proto_opt=outputJsonMethods=to-only
- --ts_proto_opt=outputJsonMethods=from-only
您将只能在Message.toJSON
和Message.fromJSON
方法之间导出一个。
如果您仅使用ts-proto来encode
或decode
而不是两者都不适用,这将很有用。
使用--ts_proto_opt=outputPartialMethods=false
, Message.fromPartial
Message.create
使用--ts_proto_opt=stringEnums=true
,生成的枚举类型将基于字符串而不是基于INT。
如果您只需“仅类型”,并且正在使用配置以序列化枚举为字符串的GRPC REST网关,这将很有用。
(需要outputEncodeMethods=false
。)
使用--ts_proto_opt=outputClientImpl=false
,客户端实现,即实现客户端端的FooServiceClientImpl
(在twirp中,请参见grpc-web
的下一个选项)RPC接口将不会输出。
使用--ts_proto_opt=outputClientImpl=grpc-web
,客户端实现,即FooServiceClientImpl
,将在运行时使用 @Impobable-eng/grpc-Web库将GRPC消息发送到GRPC-WEB Backend。
(请注意,这仅使用GRPC-WEB运行时,您无需使用其生成的任何代码,即TS-Proto输出替代其ts-protoc-gen
输出。)
您需要添加@improbable-eng/grpc-web
,并添加到项目package.json
的运输;有关工作示例,请参见integration/grpc-web
目录。另请参阅#504与GRPC-Web-Devtools集成。
使用--ts_proto_opt=returnObservable=true
,服务方法的返回类型将被Observable<T>
而不是Promise<T>
。
使用--ts_proto_opt=addGrpcMetadata=true
,服务方法的最后一个参数将接受GRPC Metadata
类型,其中包含带有呼叫的其他信息(即访问tokens/etc。)。
(需要nestJs=true
。)
使用--ts_proto_opt=addNestjsRestParameter=true
,服务方法的最后一个参数将是带有任何类型的REST参数。这样,您可以使用通常在Nestjs中使用的自定义装饰器。
(需要nestJs=true
。)
使用--ts_proto_opt=nestJs=true
,默认值将更改为生成Nestjs Protobuf友好类型和服务接口,这些类型和服务接口都可以在Nestjs Protobuf实现的客户端和服务器端使用。有关更多信息和实施示例,请参见Nestjs Readme。
具体而言, outputEncodeMethods
, outputJsonMethods
和outputClientImpl
都是错误的, lowerCaseServiceMethods
将是真实的,并且将忽略outputServices
。
请注意, addGrpcMetadata
, addNestjsRestParameter
和returnObservable
仍然是错误的。
使用--ts_proto_opt=useDate=false
,类型google.protobuf.Timestamp
的字段不会被映射到生成类型中的类型Date
。有关更多详细信息,请参见时间戳。
使用--ts_proto_opt=useMongoObjectId=true
,一种称为Objectid的字段的字段,其中构造消息在字段上构造为“值为值”字符串的字符串将映射到生成类型中的mongodb.ObjectId
。这将要求您的项目安装MongoDB NPM软件包。有关更多详细信息,请参见ObjectID。
使用--ts_proto_opt=annotateFilesWithVersion=false
,生成的文件将不包含用于生成文件的protoc
和ts-proto
的版本。此选项通常设置为true
,因此文件列出了使用的版本。
使用--ts_proto_opt=outputSchema=true
,将生成元键入,以后可以在其他代码生成器中使用。
使用--ts_proto_opt=outputSchema=no-file-descriptor
,将生成元键入,但我们不在生成的模式中包含文件描述符。如果您试图最大程度地减少生成的模式的大小,这将很有用。
使用--ts_proto_opt=outputSchema=const
,将生成Meta键入as const
,允许对其所有属性的类型安全访问。 (仅适用于Typescript 4.9及以上,因为它也使用satisfies
操作员)。可以将no-file-descriptor
选项( outputSchema=const,outputSchema=no-file-descriptor
)结合使用,以在生成的模式中包括文件描述符。
使用--ts_proto_opt=outputTypeAnnotations=true
,将给出每个消息,一个$type
字段,其中包含其完全合格的名称。您可以使用--ts_proto_opt=outputTypeAnnotations=static-only
省略interface
声明,或--ts_proto_opt=outputTypeAnnotations=optional
以使其成为interface
定义的可选属性。如果您要使用$type
字段进行运行时类型检查服务器的响应,则后一个选项可能会很有用。
使用--ts_proto_opt=outputTypeRegistry=true
,将生成类型注册表,可用于通过完全合格的名称来解决消息类型。另外,将为每条消息提供一个包含其完全符合条件的名称的$type
字段。
使用--ts_proto_opt=outputServices=grpc-js
,TS-Proto将以GRPC-JS格式输出服务定义和服务器 /客户端存根。
使用--ts_proto_opt=outputServices=generic-definitions
,TS-Proto将输出通用(Framework-Aggnostic)服务定义。这些定义包含每种方法的描述符,其中包含链接到请求和响应类型的链接,该链接允许在运行时生成服务器和客户端存根,并在编译时为其生成强大的类型。使用此方法的库的一个示例是不错的GRPC。
使用--ts_proto_opt=outputServices=nice-grpc
,TS-Proto将输出服务器和客户端存根,以获取NICE GRPC。这应该与通用定义一起使用,即您应该指定两个选项: outputServices=nice-grpc,outputServices=generic-definitions
。
使用--ts_proto_opt=metadataType=Foo@./some-file
使用--ts_proto_opt=outputServices=generic-definitions,outputServices=default
,ts-proto将输出通用定义和接口。如果您想依靠接口,这很有用,但在运行时也具有一些反射功能。
使用--ts_proto_opt=outputServices=false
,or =none
,ts-proto将不会输出服务定义。
使用--ts_proto_opt=rpcBeforeRequest=true
,TS-Proto将在RPC接口定义中添加功能定义,其中签名: beforeRequest(service: string, message: string, request: <RequestType>)
。它还将自动设置outputServices=default
。每种服务的方法都将在执行其请求之前调用beforeRequest
。
使用--ts_proto_opt=rpcAfterResponse=true
,TS-Proto将在RPC接口定义中添加功能定义: afterResponse(service: string, message: string, response: <ResponseType>)
。它还将自动设置outputServices=default
。每个服务的方法都会在返回响应之前调用afterResponse
。
使用--ts_proto_opt=rpcErrorHandler=true
,TS-Proto将在RPC接口定义中添加一个功能定义:thange error: handleError(service: string, message: string, error: Error)
。它还将自动设置outputServices=default
。
使用--ts_proto_opt=useAbortSignal=true
,生成的服务将接受AbortSignal
以取消RPC调用。
使用--ts_proto_opt=useAsyncIterable=true
,生成的服务将使用AsyncIterable
服务而不是Observable
。
带有--ts_proto_opt=emitImportedFiles=false
,ts-proto不会发出google/protobuf/*
文件,除非您明确将文件添加到protoc
例如protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto my_message.proto google/protobuf/duration.proto
使用--ts_proto_opt=fileSuffix=<SUFFIX>
,TS-Proto将使用指定的后缀发布生成的文件。带有fileSuffix=.pb
helloworld.proto
文件将被生成为helloworld.pb.ts
。这是其他ProtoC插件中的常见行为,并提供了一种方法来快速将所有生成的文件覆盖。
使用--ts_proto_opt=importSuffix=<SUFFIX>
,TS-Proto将使用指定的后缀发射文件导入。用fileSuffix=.js
的helloworld.ts
导入将生成import "helloworld.js"
。默认值是在没有文件扩展程序的情况下导入。由打字稿4.7.x及以上支持。
使用--ts_proto_opt=enumsAsLiterals=true
,生成的枚举类型将是枚举的对象,AS as const
。
使用--ts_proto_opt=useExactTypes=false
, fromPartial
和create
方法生成的方法将不使用精确的类型。
默认行为是useExactTypes=true
,它使得fromPartial
制成并为其参数create
精确的类型,以使打字稿拒绝任何未知属性。
使用--ts_proto_opt=unknownFields=true
,所有未知字段将被解析并输出为缓冲区数组。
使用--ts_proto_opt=onlyTypes=true
,只会发出类型,并且将排除long
的导入和protobufjs/minimal
导入。
这与设置outputJsonMethods=false,outputEncodeMethods=false,outputClientImpl=false,nestJs=false
使用--ts_proto_opt=usePrototypeForDefaults=true
,生成的代码将与Object.create
一起包装新对象。
这允许代码进行Hazzer检查以检测何时应用默认值,这是由于原始3的行为不将默认值放在电线上,通常仅对与proto2消息进行交互有用。
启用后,默认值将从原型继承,因此代码可以使用object.keys()。inclate(“ somefield”)来检测某些场地是否实际上是解码的。
请注意,如前所述,这意味着对象。键将不包括设置默认字段,因此,如果您具有以通用方式迭代的代码,则它也必须迭代从原型中继承的键进行迭代。
使用--ts_proto_opt=useJsonName=true
,将使用Protofiles中定义的json_name
代替消息字段名称。
使用--ts_proto_opt=useJsonWireFormat=true
,生成的代码将反映Protobuf消息的JSON表示形式。
需要只需onlyTypes=true
。 inmans useDate=string
and stringEnums=true
。此选项是生成可以直接用于编组/删除序列化为JSON的Protobuf消息的类型。您可能还需要设置useOptionals=all
,因为GRPC网关不需要为标量值发送默认值。
使用--ts_proto_opt=useNumericEnumForJson=true
,JSON Converter( toJSON
)将编码ENUM值为INT,而不是字符串字面。
使用--ts_proto_opt=initializeFieldsAsUndefined=false
,将从生成的基本实例中省略所有可选字段初始化器。
使用--ts_proto_opt=disableProto2Optionals=true
,proto2文件上的所有可选字段都不会设置为可选。请注意,此标志主要是为了保存TS-Proto对Proto2文件的遗产处理,以避免破坏更改,因此,它不打算向前推进。
使用--ts_proto_opt=disableProto2DefaultValues=true
,proto2文件中指定默认值的所有字段实际上不会使用该默认值。请注意,此标志主要是为了保存TS-Proto对Proto2文件的遗产处理,以避免破坏更改,因此,它不打算向前推进。
with --ts_proto_opt=Mgoogle/protobuf/empty.proto=./google3/protobuf/empty
,('m'含义'importmapping',类似于protoc-gen-go),生成的代码导入for ./google/protobuf/empty.ts
将反映覆盖价值:
Mfoo/bar.proto=@myorg/some-lib
将映射foo/bar.proto
导入到import ... from '@myorg/some-lib'
。
Mfoo/bar.proto=./some/local/lib
将映射foo/bar.proto
导入到import ... from './some/local/lib'
。
Mfoo/bar.proto=some-modules/some-lib
将映射foo/bar.proto
导入到import ... from 'some-module/some-lib'
注意:用途是累积的,因此以--ts_proto_opt=M... --ts_proto_opt=M...
ts_proto_opt
形式期望多个值。
注意:将不会生成匹配映射导入的原始文件。
使用--ts_proto_opt=useMapType=true
,Protobuf map<key_type, value_type>
的生成代码将成为Map<key_type, value_type>
使用Javascript Map类型。
默认行为是useMapType=false
,它使其生成了Protobuf映射的代码map<key_type, value_type
带有key-value对,例如{[key: key_type]: value_type}
。
使用--ts_proto_opt=useReadonlyTypes=true
,使用Typescript的readonly
修改器将生成的类型声明为不可变。
使用--ts_proto_opt=useSnakeTypeName=false
将从类型中删除蛇壳。
示例Protobuf
消息框{消息元素{消息图像{enum Arignment {left = 1; 中心= 2; 正确= 3; } } } }
默认情况下,这将启用这将生成Box_Element_Image_Alignment
的类型。通过禁用此选项,生成的类型将是BoxElementImageAlignment
。
使用--ts_proto_opt=outputExtensions=true
,生成的代码将包括proto2扩展名
扩展编码/解码方法符合outputEncodeMethods
选项,如果unknownFields=true
,则将为可扩展消息创建setExtension
和getExtension
方法,也符合outputEncodeMethods
(setExtension = encode = encode,getextension,getextension = Decode)。
使用--ts_proto_opt=outputIndex=true
,将基于原始软件包名称空间生成索引文件。
这将禁用exportCommonSymbols
以避免在公共符号上碰撞。
使用--ts_proto_opt=emitDefaultValues=json-methods
,生成的TOJSON方法将发射诸如0
和""
JSON字段之类的标量。
使用--ts_proto_opt=comments=false
,不会将注释从原始文件复制到生成的代码。
使用--ts_proto_opt=bigIntLiteral=false
,生成的代码将使用BigInt("0")
而不是0n
用于Bigint文字。当“目标”编译器选项设置为比“ ES2020”年龄的事物时,BigInt文字不受打字稿的支持。
使用--ts_proto_opt=useNullAsOptional=true
, undefined
值将转换为null
,如果您在.proto
文件中使用optional
标签,则该字段也将具有undefined
类型。例如:
使用--ts_proto_opt=typePrefix=MyPrefix
,生成的接口,枚举和工厂将在其名称中具有MyPrefix
的前缀。
使用--ts_proto_opt=typeSuffix=MySuffix
,生成的接口,枚举和工厂将以其名称中的MySuffix
后缀。
Message Profileinfo {Int32 ID = 1; String Bio = 2; String Phone = 3; }消息部{int32 ID = 1;字符串名称= 2; }消息用户{int32 ID = 1;字符串用户名= 2;/* ProfileInfo将在Typescript中是可选的,该类型将为ProfileInfo | null |在您不想为配置文件提供任何价值的情况下,这是不确定的。 */可选的profileinfo profile = 3;/*部门仅接受部门类型或null,因此这意味着如果没有可用的值,则必须将其传递为null。 */部门= 4; }
生成的接口将是:
导出接口profileinfo { id:数字; 生物:字符串; 电话:字符串;}导出接口部门{ id:数字; 名称:字符串;}导出接口用户{ id:数字; 用户名:字符串; 个人资料?:ProfileInfo | null |不明确的; //检查这个 部门:部门|无效的; //检查这个}
使用--ts_proto_opt=noDefaultsForOptionals=true
,根据Protobuf Spec不会默认undefined
原始值。另外,与标准行为不同,当将字段设置为其标准默认值时,它将被编码,允许将其通过电线发送并与未定义值区分开。例如,如果一条消息没有设置布尔值,则通常将其默认为false
,这与未定义不同。
此选项允许库与Square/Block维护和使用的导线实现以兼容的方式行动。注意:此选项仅与使用电线或TS-Proto生成的其他客户端/服务器代码结合使用,并启用了此选项。
我们有一种与Nestjs一起工作的好方法。 ts-proto
为您的控制器,客户端生成interfaces
和decorators
。有关更多信息,请参见Nestjs Readme。
如果您想在原始文件的每一个更改上运行ts-proto
,则需要使用Chokidar-Cli之类的工具,并将其用作package.json
中的脚本。
“原始:生成”:“ Protoc -ts_proto_out = ../< Proto_path>/< Proto_name> .proto -ts_proto_opt = esmoduleopt = esmoduleop = true”,“ proto:proto:watch” proto:watch“:” chokidar“ chokidar” **/*。 C“ NPM运行原始:生成”
ts-proto
是RPC Framework不可知论 - 您如何向数据源传输数据取决于您。生成的客户端实现都期望rpc
参数,哪种类型是这样定义的:
接口RPC { 请求(服务:字符串,方法:字符串,数据:uint8array):Promise <uint8array>;}
如果您正在与GRPC合作,那么一个简单的实现可能会这样:
const conn = new grpc.client( “ localhost:8765”, grpc.credentials.createinsecure()); type rpcimpl =(服务:字符串,方法:字符串,数据:uint8array)=> Promise <Uint8Array>; const sendrequest:rpcimpl =(rpcimpl =(service,service,method,data)=> {=> { //传统上,在GRPC中,请求路径看起来像 //“ package.names.names.serviceName/methodName”, //因此,我们构建了这样的字符串 const路径=`/$ {service}/$ {method}`; 返回新的承诺(((解决,拒绝)=> {// makeunaryRequest用回调传输结果(和错误)//将其转换为Promise!const resultCallback:unaryCallback <any> =(err,res,res)=> {如果(err){return reffec(err);} resolve(res);};函数passthrough(参数:any){return groment;} //使用PassIrthough作为序列化和deperialialize functionsconn.makeunaryrequest(path,passthrough,passthrough,passthrough,passthrough,passthrough,data,data ,ResultCallback); });}; const rpc:rpc = {request:sendrequest};
感谢我们的赞助商:
Ngrok资助了TS-Proto的最初GRPC-WEB支持。
如果您需要对公司的TS-Proto自定义或优先支持,则可以通过电子邮件发送我。
本节介绍了如何直接对TS-Proto做出贡献,即在protoc
中运行ts-proto
或使用生成的打字稿并不是必需的。
要求
Docker
yarn
- npm install -g yarn
设置
下面的命令假设您已安装了Docker 。如果您使用的是OS X,请安装Coreutils , brew install coreutils
。
查看存储库中的最新代码。
运行yarn install
以安装依赖项。
运行yarn build:test
以生成测试文件。
这运行以下命令:
proto2ts
- 在integration/**/*.proto
文件上运行ts-proto
以生成.ts
文件。
proto2pbjs
- 使用pbjs
生成参考实现,以测试兼容性。
运行yarn test
工作流程
为您的用例添加/更新集成测试
您也可以让yarn watch
运行,它应该“做正确的事”
与必要的ts_proto_opt
参数进行新的integration/your-new-test/parameters.txt
创建最小的integration/your-new-test/your-new-test.proto
要么找到一个现有的integration/*
测试与您的用例足够接近,例如,EG具有一个parameters.txt
,该参数匹配ts_proto_opt
参数以重现您的用例
如果创建新的集成测试:
对your-new-test.proto
或现有integration/*.proto
文件进行任何更改后,运行yarn proto2bin
添加/更新一个integration/your-new-test/some-test.ts
单元测试,即使它与仅确保生成的代码编译一样微不足道
修改ts-proto
代码生成逻辑:
或yarn proto2ts your-new-test
以重新计算特定测试
再次离开yarn watch
应“做正确的事”
最重要的逻辑是在src/main.ts中找到的。
对src/*.ts
文件进行了任何更改后,运行yarn proto2ts
重新计算所有集成测试
运行yarn test
以验证您的更改通过所有现有测试
提交并提交公关
有时会皱眉,但鉴于TS-Proto的主要工作是生成代码,看到PRS中的Codegen diffs很有帮助
运行yarn format
以格式化打字稿文件。
确保在integration/your-new-test
中git add
所有*.proto
, *.bin
和*.ts
文件
项目中的测试
只要您手动运行yarn build
,您就可以通过运行yarn add ts-proto@./path/to/ts-proto
dockerized Protoc
存储库包括一个dockerized版本的protoc
,该版本在Docker-compose.yml中配置。
如果您想用已知版本的protoc
手动调用插件,它可能会很有用。
用法:
#在您的外壳中包括原始别名。.Aliases.sh#照常运行原始的。 ts-proto目录可在/ts-proto.protoc-protoc -plugin =/ts-proto/protoc-gen-ts_proto -ts_proto_out =。/output -i = ./ protos ./ protos ./protoc/* .proto.或proto#或使用为您指定插件路径的ts-protoc别名。
所有路径必须是主机当前工作目录中的相对路径。 ../
不允许
在Docker容器中,通往项目根的绝对路径是/ts-proto
容器将当前的工作目录安装在/host
中,并将其设置为其工作目录。
一旦aliases.sh
来源,您就可以在任何文件夹中使用protoc
命令。
TS/ES6模块名称是原始软件包
支持基于fromJSON
toJSON
持续时间的编码
使oneof=unions-value
2.0中的默认行为
可能在2.0中更改forceLong
默认值,应默认为forceLong=long
使esModuleInterop=true
2.0中的默认值
默认情况下,TS-Proto在消息中“平坦”模型oneof
字段,例如:
消息foo {oneof atof awifef {字符串field_a = 1;字符串field_b = 2; } }
将生成具有两个字段的Foo
类型: field_a: string | undefined;
和field_b: string | undefined
。
使用此输出,您必须同时检查if object.field_a
和if object.field_b
,如果设置一个,则必须记住要揭开另一个。
相反,我们建议使用oneof=unions-value
选项,该选项将将输出更改为代数数据类型/ADT,例如:
接口yourmessage { 要么菲尔德?:{$ case:“ field_a”;值:字符串} | {$ case:“ field_b”;值:字符串};}
因为这将自动执行一次field_a
或field_b
“被设置”的一个,因为值存储在eitherField
字段中,该字段一次只能具有一个值。
(请注意,Protobuf中的eitherField
是可选的b/c oneof
表示“最多一个字段”,并不意味着必须设置其中一个字段。)
在TS-Proto当前未解决的2.x版本中, oneof=unions-value
将成为默认行为。
还有一个oneof=unions
选项,该选项生成一个联合,每个选项中都包含字段名称:
接口yourmessage { 要么菲尔德?:{$ case:“ field_a”; field_a:字符串} | {$ case:“ field_b”; field_b:string};}
不再建议这样做,因为很难编写代码和类型来处理多个Oneof选项:
以下助手类型可能会使使用由oneof=unions
生成的类型更容易,尽管如果您使用oneof=unions-value
,通常不需要它们:
/**从单个字段中提取所有情况名称。 */type Oneofcases <t> = t扩展{$ case:Cheble U扩展字符串}? u:从不;/**从单个字段*/type OneofValues <t> = t扩展{$ case:ceash u扩展字符串; [键:字符串]:未知}? t [u]:从不;/**根据其字段名称*/type Oneofcase <t,k扩展Oneofcases <t >> = t扩展{ $ case:k; [键:字符串]:未知;} ? t :Never;/**从OneOf字段*/type OneofValue <t,k扩展Oneofcases <t >> = t扩展{ $案例:推断u扩展k; [键:字符串]:未知;} ? t [u] : 绝不;
为了进行比较, oneof=unions-value
的等效物:
/**从单个字段中提取所有情况名称。 */type Oneofcases <t> = t ['$ case'];/**从OneOf字段中提取所有值类型的结合*/type OneOfValues <t> = t ['value'];/**基于字段名称 */type Oneofcase <t,k扩展Oneofcases <t >> = t的特定类型的特定类型 $ case:k; [键:字符串]:未知;} ? t :Never;/**从OneOf字段*/type OneofValue <t,k扩展Oneofcases <t >> = t扩展{ $案例:推断u扩展k; 价值:未知;} ? t [u] : 绝不;
在Core Protobuf(以及ts-proto
)中,未设置或等于默认值的值不会通过电线发送。
例如,消息的默认值undefined
。原始类型采用其自然默认值,例如string
为''
, number
为0
,等。
Protobuf之所以选择/执行此行为,是因为它可以使兼容性具有向前的兼容性,因为即使原始场所被过时的代理忽略,原始字段也将始终具有一个值。
这很好,但这也意味着在ts-proto
字段中无法区分默认值和未设置值。从根本上讲,这是Protobuf的工作原理。
如果您需要可以检测到设置/不设置的原始字段,请参见包装器类型。
编码 /解码
ts-proto
遵循Protobuf规则,并在解码时始终返回UNSETS字段的默认值,同时以二进制格式序列化时从输出中省略它们。
语法=“ proto3”; message foo {字符串bar = 1; }
Protobufbytes; //假设这是一个空的foo对象,protobuf binary formatfoo.decode(protobufbytes); // => {bar:''}
foo.encode({bar:“”}); // => {},以Protobuf二进制格式写一个空的foo对象
Fromjson / Tojson
读取JSON还将初始化默认值。由于发件人可以省略未设置字段,也可以将其设置为默认值,请使用fromJSON
将输入归一化。
foo.fromjson({}); // => {bar:''} foo.fromjson({bar:“”}); // => {bar:''} foo.fromjson({bar:“ baz”}); // => {bar:'baz'}
在编写JSON时, ts-proto
通过省略设置为默认值的未设置字段和字段来将消息归一化。
foo.tojson({}); // => {} foo.tojson({bar:undefined}); // => {} foo.tojson({bar:“”}); // => {} - 注意:省略默认值,如Expectfoo.tojson({bar:“ baz”}); // => {bar:'baz'}
Protobuf带有几种预定义的消息定义,称为“众所周知的类型”。它们的解释是由Protobuf规范定义的,并且库有望将这些消息转换为目标语言中相应的本机类型。
ts-proto
当前会自动将这些消息转换为相应的本机类型。
google.protobuf.boolvalue⇆ boolean
Uint8Array
Google.protobuf.doublevalue⇆ number
google.protobuf.fieldmask⇆ string[]
Google.protobuf.floatvalue⇆ number
Google.protobuf.int32Value⇆ number
Google.protobuf.int64Value⇆ number
google.protobuf.listvalue⇆ any[]
Google.protobuf.uint32Value⇆ number
Google.protobuf.uint64Value⇆ number
Google.protobuf.StringValue⇆ string
google.protobuf.value⇆ any
(即number | string | boolean | null | array | object
)
google.protobuf.struct⇆ { [key: string]: any }
包装器类型是包含单个原始字段的消息,并且可以在.proto
文件中导入import "google/protobuf/wrappers.proto"
。
由于这些是消息,因此它们的默认值undefined
,从而使您可以在使用包装器类型时将未设置的原始值与默认值区分开。 ts-proto
以<primitive> | undefined
形式生成这些字段。 <primitive> | undefined
。
例如:
// protobufsyntax =“ proto3”; import“ google/protobuf/wrappers.proto”; message exipplemessage {google.protobuf.stringvalue name = 1; }
// typeScriptInterface extplemessage { 名称:字符串|不明确的;}
编码消息时,原始值将转换回其相应的包装器类型:
explemessage.encode({name:“ foo”}); // => {name:{value:'foo'}},在二进制中
当调用Tojson时,该值不会转换,因为包装器类型在JSON中是惯用的。
explemessage.tojson({name:“ foo”}); // => {name:'foo'}
Protobuf的语言和类型不足以表示所有可能的JSON值,因为JSON可能包含预先未知类型的值。因此,Protobuf提供了多种代表任意JSON值的其他类型。
这些称为struct类型,可以在import "google/protobuf/struct.proto"
的.proto
文件中导入。
any
这是最通用的类型,可以表示任何JSON值(IE number | string | boolean | null | array | object
)。
google.protobuf.listvalue⇆ any[]
代表JSON阵列
google.protobuf.struct⇆ { [key: string]: any }
代表JSON对象
ts-proto
会自动在这些结构类型及其相应的JSON类型之间来回转换。
例子:
// protobufsyntax =“ proto3”; import“ google/protobuf/struct.proto”; message exipplemessage {google.protobuf.value notings = 1; }
// typeScriptInterface extplemessage { 任何东西:任何|不明确的;}
编码嵌入消息中的JSON值,将其转换为结构类型:
exipplemessage.encode({{nothing:{name:“ hello”}});/*输出以下结构,以Protobuf binary格式编码:{nothing:value {structValue = structvalue = struct {fields = [mapEntry = [key =“ name name,” value = value {stringValue =“ hello”}]}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} */
google.protobuf.Timestamp
的表示形式可通过useDate
标志配置。当useDate
是false
时, useJsonTimestamp
标志控制精度。
Protobuf众所周知的类型 | 默认/ useDate=true | useDate=false | useDate=string | useDate=string-nano |
---|---|---|---|---|
google.protobuf.Timestamp | Date | { seconds: number, nanos: number } | string | string |
当使用useDate=false
和useJsonTimestamp=raw
时间戳为{ seconds: number, nanos: number }
,但具有纳秒精度。
当使用useDate=string-nano
Timestamp时,将其表示为ISO字符串,具有纳秒精度为1970-01-01T14:27:59.987654321Z
,并依赖于纳米日期库进行转换。您需要将其安装在项目中。
默认情况下,数字被假定为普通的JavaScript number
s。
对于int32
和float
等Protobuf类型来说,这是可以的,但是像int64
这样的64位类型不能100%由JavaScript的number
类型表示,因为int64
可以比number
更大/更小的值。
TS-Proto的默认配置(即forceLong=number
)仍将number
用于64位字段,然后如果值(在运行时)大于Number.MAX_SAFE_INTEGER
。
如果您期望使用64位 /高于MAX_SAFE_INTEGER
值,则可以使用TS-Proto forceLong
选项,该选项使用长NPM软件包来支持整个64位值的范围。
基于forceLong
配置选项的Protobuf数字类型映射到JavaScript类型:
Protobuf数字类型 | 默认/ forceLong=number | forceLong=long | forceLong=string |
---|---|---|---|
双倍的 | 数字 | 数字 | 数字 |
漂浮 | 数字 | 数字 | 数字 |
INT32 | 数字 | 数字 | 数字 |
INT64 | 数字* | 长的 | 细绳 |
Uint32 | 数字 | 数字 | 数字 |
Uint64 | 数字* | 未签名长 | 细绳 |
sint32 | 数字 | 数字 | 数字 |
sint64 | 数字* | 长的 | 细绳 |
固定32 | 数字 | 数字 | 数字 |
固定64 | 数字* | 未签名长 | 细绳 |
Sfixed32 | 数字 | 数字 | 数字 |
Sfixed64 | 数字* | 长的 | 细绳 |
其中(*)表示他们可能在运行时丢失错误。
必需的原始内容:使用原理,即string name = 1
。
可选的原始内容:使用包装器类型,即StringValue name = 1
。
必需消息:不可用
可选消息:使用AS-IS,IE SubMessage message = 1
。