ts-proto
transforma sus archivos.proto
en archivos mecanografiados idiomáticos fuertemente tipo!
La liberación 2.x de TS-Proto migró el ProTobuf de bajo nivel serializando que su método encode
y decode
se usa del paquete venerable, pero envejecido y estancado, protobufjs
a @bufbuild/protobuf
.
Si solo usó los métodos encode
y decode
, este debería ser en gran medida un cambio no roto.
Sin embargo, si usó algún código que usara las antiguas clases Writer
o Reader
protobufjs
, deberá actualizar su código para usar las nuevas clases @bufbuild/protobuf
:
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
Si migrar a @bufbuild/protobuf
es un bloqueador para usted, puede fijar su versión ts-proto
a 1.x
Descargo de responsabilidad y disculpas: tenía la intención de lanzar TS-Proto 2.x como una versión alfa, pero no obtuve la configuración de liberación semántica correcta, por lo que TS-Proto 2.x se publicó como una versión importante sin un alfa adecuado /ciclo beta.
Si pudiera presentar informes (¡o mejores PR!) Para cualquier problema que se encuentre mientras el lanzamiento aún está fresco, eso sería muy apreciado.
¡Cualquier consejo o truco para otros en la migración también sería apreciado!
TS-PROTO
Tabla de contenido
Descripción general
Inicio rápido
Bufón
ESM
Objetivos
Indiferentes
Tipos de ejemplo
Reflejos
Prevención de lotes automáticos / n+1
Uso
Opciones compatibles
Soporte de Nestjs
Modo de reloj
Implementación básica de GRPC
Patrocinadores
Desarrollo
Suposiciones
Hacer
OneF Manejo
Valores predeterminados y campos no establecidos
Tipos bien conocidos
Tipos de envoltorio
Tipos de JSON (tipos de estructura)
Marca de tiempo
Tipos de números
Estado actual de valores opcionales
TS-PROTO genera tipos de mecanografiado a partir de esquemas ProtoBuf.
Es decir, dada a una person.proto
Schema Proto como:
Persona de mensaje {name de cadena = 1; }
TS-Proto generará un archivo de person.ts
como:
persona de interfaz { Nombre: String} Const Person = { encode (persona): escritor {...} decode (lector): persona {...} TOJSON (Persona): Desconocido {...} fromjson (datos): persona {...}}
También conoce los servicios y también generará tipos para ellos, es decir:
Exportar interfaz pingservice { Ping (solicitud: PingRequest): Promise <PingResponse>;}
También generará implementaciones de clientes de PingService
; Actualmente, TWIRP, GRPC-WEB, GRPC-JS y NESTJS son compatibles.
npm install ts-proto
protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=. ./simple.proto
(Tenga en cuenta que el nombre del parámetro de salida, ts_proto_out
, se llama en función del sufijo del nombre del complemento, es decir _out
el sufijo "ts_proto" en el parámetro --plugin=./node_modules/.bin/protoc-gen-ts_proto
según las convenciones de CLI de protoc
).
En Windows, use protoc --plugin=protoc-gen-ts_proto=".node_modules.binprotoc-gen-ts_proto.cmd" --ts_proto_out=. ./simple.proto
(ver #93)
Asegúrese de que esté utilizando una protoc
moderna (consulte las instrucciones de instalación para su plataforma, es decir, protoc
V 3.0.0
no admite el indicador _opt
Esto generará *.ts
archivos de origen para los tipos dados *.proto
.
Si desea empaquetar estos archivos de origen en un paquete NPM para distribuir a los clientes, simplemente ejecute tsc
en ellos como de costumbre para generar los archivos .js
/ .d.ts
e implementa la salida como un paquete NPM normal.
Si está utilizando BUF, pase strategy: all
en su archivo buf.gen.yaml
(documentos).
Versión: V1Plugins: -Nombre: TSout: ../gen/tsstrategy: Allpath: ../node_modules/ts-proto/protoc-gen-ts_proto
Para evitar que buf push
la lectura irrelevante .proto
archivos, configure buf.yaml
como así:
construir: excluye: [node_modules]
También puede usar el complemento oficial publicado en el registro BUF.
Versión: V1Plugins: -complemento: buf.build/community/stephenh-ts-protoout: ../gen/tsopt: - OutputServices = ... - UseExactTypes = ...
Si está utilizando una configuración TS moderna con esModuleInterop
o en ejecución en un entorno ESM, deberá pasar ts_proto_opt
S OF:
esModuleInterop=true
si usa esModuleInterop
en su tsconfig.json
, y
importSuffix=.js
si ejecuta el código TS-Proto generado en un entorno ESM
En términos del código que genera ts-proto
, los objetivos generales son:
Tipos de mecanografiado idiomático/ES6
ts-proto
es un descanso limpio del código de protoc
JS de Google/ protobufjs
-Java- *.js
incorporado o el enfoque de "hacer .d.ts
(Técnicamente, el paquete protobufjs/minimal
se usa para leer/escribir bytes).
TypeScript-primero salida
Interfaces sobre clases
En la medida de lo posible, los tipos son solo interfaces, por lo que puede trabajar con mensajes al igual que las estructuras de datos/datos regulares.
Solo admite Codegen *.proto
-to- *.ts
Workflow, actualmente no hay reflexión/carga de tiempo de ejecución de archivos dinámicos .proto
Tenga en cuenta que TS-Proto no es un marco RPC fuera de la caja; En su lugar, es más un cuchillo de ejército suizo (como lo presenció por sus muchas opciones de configuración), lo que le permite construir exactamente el marco RPC que desea además (es decir, que se integra mejor con el ecosistema ProtoBuf de su empresa; para bien o para bien , ProtoBuf RPC sigue siendo un ecosistema algo fragmentado).
Si desea un marco RPC listo para usar en la parte superior de TS-Proto, hay algunos ejemplos:
Bonito GRPC
starpc
(Nota para colaboradores potenciales, si desarrolla otros marcos/mini-frameworks, o incluso publicaciones/tutoriales de blog, sobre el uso de ts-proto
, estamos felices de vincularlos).
Tampoco admitimos clientes para google.api.http
basados en las API de Google Cloud, consulte #948 si desea enviar un PR.
Los tipos generados son "solo datos", es decir:
Interfaz de exportación simple { Nombre: cadena; Edad: número; creado en: fecha | indefinido; Niño: Niño | indefinido; Estado: StateNenum; nietos: niño []; Monedas: número [];}
Junto con los métodos de fábrica encode
/ decode
:
Exportar const simple = { create (baseObject?: deePARTIAL <LOYLETS>): simple {...}, encode (mensaje: simple, escritor: escritor = escritor.create ()): escritor {...}, decode (lector: lector, longitud?: número): simple {...}, fromjson (objeto: any): simple {...}, FromPartial (Object: DeEppartial <rimple>): simple {...}, tOjson (mensaje: simple): desconocido {...},};
Esto permite el uso idiomático de TS/JS como:
const bytes = simple.encode ({nombre: ..., edad: ..., ...}). Final (); const simple = simple } = simple;
Que puede aliviar drásticamente la integración al convertir a/desde otras capas sin crear una clase y llamar a los getters/setters correctos.
El intento de un hombre pobre de "devuélvanos los tipos opcionales"
Los tipos de envoltura de protobuf canonical, es decir, google.protobuf.StringValue
, se asignan como valores opcionales, es decir, string | undefined
, lo que significa que para las primitivas podemos fingir que el sistema de tipo ProtoBuf tiene tipos opcionales.
( Actualización : TS-Proto ahora también es compatible con la palabra clave optional
de Proto3).
Las marcas de tiempo se asignan como Date
(Configurable con el parámetro useDate
).
fromJSON
/ toJSON
usa el formato de codificación JSON canónico Proto3 (por ejemplo, las marcas de tiempo son cadenas ISO), a diferencia de protobufjs
.
Los objetos se pueden mapear como mongodb.ObjectId
(Configurable con el parámetro useMongoObjectId
.)
(Nota: este actualmente solo es compatible con los clientes TWIRP).
Si está utilizando a los clientes de TS-Proto para llamar a los micro-servicios de backend, de manera similar al problema N+1 en aplicaciones SQL, es fácil para los clientes de micro servicio (al cumplir una solicitud individual) desencadenan inadvertidamente múltiples llamadas RPC separadas para las llamadas de RPC separadas para "Obtenga el libro 1", "Get Book 2", "Get Book 3", que realmente debería ser un lote en un solo "Get Books [1, 2, 3]" (suponiendo que el backend admite un método RPC orientado a lotes).
TS-Proto puede ayudar con esto, y esencialmente un lío automático de sus llamadas individuales de "obtener libros" a llamadas de "obtener libros" por lotes.
Para que TS-Proto haga esto, debe implementar los métodos RPC de su servicio con la convención de lotes de:
Un nombre de método de Batch<OperationName>
El tipo de entrada Batch<OperationName>
tiene un solo campo repetido (es decir, repeated string ids = 1
)
El tipo de salida Batch<OperationName>
tiene A:
Un solo campo repetido (es decir, repeated Foo foos = 1
) donde el orden de salida es el mismo que el orden de ids
de entrada , o
Un mapa de la entrada a una salida (es decir, map<string, Entity> entities = 1;
)
Cuando TS-Proto reconoce los métodos de este patrón, creará automáticamente una versión "no por lotes" de <OperationName>
para el cliente, es decir, client.Get<OperationName>
Esto proporciona al código del cliente la ilusión de que puede hacer que las llamadas individuales Get<OperationName>
(que generalmente es preferible/más fácil al implementar la lógica comercial del cliente), pero la implementación real que proporciona TS-Proto terminará haciendo Batch<OperationName>
Llamadas al servicio de backend.
También debe habilitar el parámetro useContext=true
Build-Time, que le da a todos los métodos del cliente un parámetro ctx
de estilo GO, con un método getDataLoaders
que permite que TS-Proto caché/resuelva los dataloaders con solicitudes de solicitud, que proporcionan el bateador automental de auto-brezo fundamental Comportamiento de detección/enjuague.
Consulte el archivo batching.proto
y las pruebas relacionadas para ver ejemplos/más detalles.
Pero el efecto neto es que TS-Proto puede proporcionar prevención N+1 de estilo SQL / ORM para llamadas de clientes, lo que puede ser crítico especialmente en implementaciones de alto volumen / altamente paralelas como GraphQL front-end Gateways llamando a microervicios de backend de backend .
ts-proto
es un complemento protoc
, por lo que lo ejecuta (ya sea directamente en su proyecto, o más probablemente en su tubería de esquema Mono-Repo, es decir, me gusta Ibotta o a saber):
Agregue ts-proto
a su package.json
Ejecute npm install
para descargarlo
Invocar protoc
con un parámetro plugin
como:
Protoc --plugin = node_modules/ts-propo/protoc-gen-ts_proto ./batching.proto -i.
ts-proto
también se puede invocar con Gradle utilizando ProtoBuf-Gradle-Plugin:
ProtoBuf { Los complementos {// `TS` se pueden reemplazar por cualquier nombre de complemento no utilizado, por ejemplo,` tsproto`ts { ruta = 'ruta/a/complemento} } // Esta sección solo necesaria si proporciona opciones de complemento GenerateProTotasks { all (). Cada {tarea -> task.plugins {// debe hacer coincidir la identificación del complemento declarado Abovets { opción 'foo = bar'} } } } }
El código generado se colocará en el directorio de compilación de Gradle.
Con --ts_proto_opt=globalThisPolyfill=true
, TS-Proto incluirá un polyfill para GlobalThis.
El valor predeterminado es false
, es decir, suponemos globalThis
está disponible.
Con --ts_proto_opt=context=true
, los servicios tendrán un parámetro ctx
de estilo GO, que es útil para rastrear/registrar/etc. Si no está utilizando la API async_hooks
de Node debido a las razones de rendimiento.
Con --ts_proto_opt=forceLong=long
, todos los números de 64 bits se analizarán como instancias de Long
(usando la biblioteca larga).
Con --ts_proto_opt=forceLong=string
, todos los números de 64 bits se emitirán como cadenas.
Con --ts_proto_opt=forceLong=bigint
, todos los números de 64 bits se emitirán como BigInt
s. Esta opción todavía usa la biblioteca long
para codificar/decodificar internamente dentro de protobuf.js
, pero luego se convierte en/desde BigInt
s en el código generado por TS-Proto.
El comportamiento predeterminado es forceLong=number
, que todavía usará internamente la biblioteca long
para codificar/decodificar valores en el cable (por lo que aún verá una línea util.Long = Long
en su salida), pero convertirá los valores long
en number
automáticamente para ti. Tenga en cuenta que se lanza un error de tiempo de ejecución si, mientras realiza esta conversión, un valor de 64 bits es mayor de lo que puede almacenarse correctamente como un number
.
Con --ts_proto_opt=useJsTypeOverride
, los números de 64 bits se emitirán como FieldOption.jstype especificado en el campo. Esto tiene prioridad sobre la opción forceLong
proporcionada.
Con --ts_proto_opt=esModuleInterop=true
Cambios La salida para que sea compatible esModuleInterop
.
Específicamente, las Long
importaciones se generarán como import Long from 'long'
en lugar de import * as Long from 'long'
.
Con --ts_proto_opt=env=node
o browser
o both
, TS-Proto hará suposiciones específicas del entorno en su salida. Este valor predeterminado a both
, lo que no hace supuestos específicos del entorno.
El uso node
cambia los tipos de bytes
de Uint8Array
a Buffer
para una integración más fácil con el ecosistema de nodo que generalmente usa Buffer
.
Actualmente, browser
no tiene ningún comportamiento específico que no sea "no node
". Probablemente lo hará pronto/en algún momento.
Con --ts_proto_opt=useOptionals=messages
(para campos de mensajes) o --ts_proto_opt=useOptionals=all
(para mensajes y campos escalar), los campos se declaran como claves opcionales, eg field?: Message
en lugar del field: Message | undefined
.
TS-PROTO Predeterminado es useOptionals=none
porque:
Para la prevención de errores tipográficos, los campos opcionales facilitan que los campos adicionales se deslicen en un mensaje (hasta que obtengamos tipos exactos), es decir:
interfaz SomEMessage { primer nombre: cadena; LastName: String;} // declarado con un typoconst data = {FirstName: "A", LastTypo: "B"}; // con UseOptionals = Ninguno, esto no se compila correctamente; Si `lastName` fuera opcional, no concentraría el mensaje: SomEMessage = {... data};
Para una API consistente, si SomeMessage.lastName
es lastName?
, entonces los lectores tienen que verificar dos condiciones vacías: a) es lastName
undefined
(b/c se creó en memoria y dejó unset), o b) es la cadena vacía lastName
(b/c leemos SomeMessage
fuera del cable y, por ¿La especificación de Proto3, inicializó lastName
a la cadena vacía)?
Para garantizar la inicialización adecuada, middleInitial?
se agrega SomeMessage.middleInitial
. , es posible que tenga muchos sitios de llamadas en el código de producción que ahora deberían pasar middleInitial
para crear un SomeMessage
válido, pero no lo son.
Entonces, entre la prevención tipográfica, la inconsistencia del lector y la inicialización adecuada, TS-Proto recomienda usar useOptionals=none
como la opción "más segura".
Dicho todo esto, este enfoque requiere que los escritores/creadores establezcan cada campo (aunque fromPartial
y create
están destinados a abordar esto), por lo que si aún desea tener claves opcionales, puede establecer useOptionals=messages
o useOptionals=all
.
(Consulte este tema y este tema para las discusiones sobre useOptional
).
Evita errores tipográficos al inicializar mensajes y
Proporciona la API más consistente a los lectores
Asegura que los mensajes de producción se inicialicen correctamente con todos los campos.
Con --ts_proto_opt=exportCommonSymbols=false
, los tipos de utilidad como DeepPartial
y protobufPackage
no se export
d.
Esto debería permitir utilizar las importaciones Crear barriles de la salida generada, es decir, import * from ./foo
y import * from ./bar
.
Tenga en cuenta que si tiene el mismo nombre de mensaje utilizado en múltiples archivos *.proto
, aún obtendrá conflictos de importación.
Con --ts_proto_opt=oneof=unions
, oneof
Fields se generará como ADTS.
Consulte la sección "Oneof Manning".
Con --ts_proto_opt=unrecognizedEnumName=<NAME>
Enums contendrá una clave <NAME>
con el valor de la opción unrecognizedEnumValue
.
El valor predeterminado UNRECOGNIZED
.
Con --ts_proto_opt=unrecognizedEnumValue=<NUMBER>
Enums contendrá una clave proporcionada por la opción unrecognizedEnumName
con valor de <NUMBER>
.
El valor predeterminado a -1
.
Con --ts_proto_opt=unrecognizedEnum=false
Enums no contendrá una clave y valor de enum no reconocido según lo proporcionado por las opciones unrecognizedEnumName
y unrecognizedEnumValue
.
Con --ts_proto_opt=removeEnumPrefix=true
generados Enums tendrá el nombre de enumación eliminado de los miembros.
FooBar.FOO_BAR_BAZ = "FOO_BAR_BAZ"
generará FooBar.BAZ = "FOO_BAR_BAZ"
Con --ts_proto_opt=lowerCaseServiceMethods=true
, los nombres de métodos de los métodos de servicio se reducirán/Camel-Case, es decir, service.findFoo
en lugar de service.FindFoo
.
Con --ts_proto_opt=snakeToCamel=false
, los campos se mantendrán en el caso de serpiente tanto en las claves del mensaje como en los métodos toJSON
/ fromJSON
.
snakeToCamel
también se puede configurar como una lista de cadenas _
(la coma está reservada como la bandera delimitada), es decir, --ts_proto_opt=snakeToCamel=keys_json
, donde keys
incluidas harán que las claves de mensajes sean un caso de camello e incluido json
hará que las teclas JSON sean Caso de camello.
Cadena vacía, es decir, snakeToCamel=
, mantendrá ambas claves de mensajes y las claves JSON
como caso de serpiente (es lo mismo que snakeToCamel=false
).
Tenga en cuenta que para usar el atributo json_name
, tendrá que usar el json
.
El comportamiento predeterminado es keys_json
, es decir, ambos estarán en escala de camello, y json_name
se usará si se establece.
Con --ts_proto_opt=outputEncodeMethods=false
, el Message.encode
y Message.decode
Los métodos de Decode para trabajar con datos binarios/codificados con protobuf no se emitirán.
Esto es útil si quieres "solo tipos".
Con --ts_proto_opt=outputJsonMethods=false
, el Message.fromJSON
y Message.toJSON
Los métodos para trabajar con datos codificados por JSON no serán emitidos.
Esto también es útil si desea "solo tipos".
Con --ts_proto_opt=outputJsonMethods=to-only
y --ts_proto_opt=outputJsonMethods=from-only
podrá exportar solo uno entre los métodos Message.toJSON
y Message.fromJSON
.
Esto es útil si está utilizando TS-Proto solo para encode
o decode
y no para ambos.
Con --ts_proto_opt=outputPartialMethods=false
, el Message.fromPartial
y Message.create
los métodos para aceptar objetos/literales de objetos parcialmente formados no serán emitidos.
Con --ts_proto_opt=stringEnums=true
, los tipos de ENUM generados estarán basados en cadenas en lugar de INT basados.
Esto es útil si desea "solo tipos" y está utilizando una puerta de enlace REST GRPC configurada para serializar a las enumeras como cadenas.
(Requiere outputEncodeMethods=false
).
Con --ts_proto_opt=outputClientImpl=false
, las implementaciones del cliente, es decir, FooServiceClientImpl
, que implementan las interfaces RPC del lado del cliente (en TWIRP, consulte la siguiente opción para grpc-web
) Las interfaces RPC no se emitirán.
Con --ts_proto_opt=outputClientImpl=grpc-web
, las implementaciones del cliente, es decir, FooServiceClientImpl
, usará la biblioteca @improbable-eng/Grpc-WEB en tiempo de ejecución para enviar mensajes GRPC a un backend GRPC-WEB.
(Tenga en cuenta que esto solo usa el tiempo de ejecución GRPC-WEB, no necesita usar ninguno de su código generado, es decir, la salida TS-Proto reemplaza su salida ts-protoc-gen
).
Deberá agregar el @improbable-eng/grpc-web
y un transporte al package.json
de su proyecto; Consulte el directorio integration/grpc-web
para obtener un ejemplo de trabajo. Consulte también #504 para integrarse con GRPC-WEB-Devtools.
Con --ts_proto_opt=returnObservable=true
, el tipo de retorno de los métodos de servicio será Observable<T>
en lugar de Promise<T>
.
Con --ts_proto_opt=addGrpcMetadata=true
, el último argumento de los métodos de servicio aceptará el tipo Metadata
GRPC, que contiene información adicional con la llamada (es decir, Tokens de acceso/etc.).
(Requiere nestJs=true
).
Con --ts_proto_opt=addNestjsRestParameter=true
, el último argumento de los métodos de servicio será un parámetro REST con el tipo de Tipo. De esta manera, puede usar decoradores personalizados que normalmente puede usar en Nestjs.
(Requiere nestJs=true
).
Con --ts_proto_opt=nestJs=true
, los valores predeterminados cambiarán para generar tipos e interfaces de servicio amigables con la protoba de Nestjs que se pueden usar tanto en el lado del cliente como en el lado del cliente de las implementaciones de protoBuf Nestjs. Consulte el ReadMe Nestjs para obtener más información e ejemplos de implementación.
Específicamente, outputEncodeMethods
, outputJsonMethods
y outputClientImpl
serán falsos, lowerCaseServiceMethods
será verdadero y outputServices
serán ignorados.
Tenga en cuenta que addGrpcMetadata
, addNestjsRestParameter
y returnObservable
seguirán siendo falsos.
Con --ts_proto_opt=useDate=false
, los campos de tipo google.protobuf.Timestamp
no se asignarán para escribir Date
en los tipos generados. Vea la marca de tiempo para más detalles.
Con --ts_proto_opt=useMongoObjectId=true
, los campos de un tipo llamado ObjectId donde el mensaje se construye para tener en el campo llamado valor que es una cadena se asignará al tipo mongodb.ObjectId
en los tipos generados. Esto requerirá que su proyecto instale el paquete MongoDB NPM. Vea ObjectId para más detalles.
Con --ts_proto_opt=annotateFilesWithVersion=false
, los archivos generados no contendrán las versiones de protoc
y ts-proto
que se utilizan para generar el archivo. Esta opción se establece normalmente en true
, de modo que los archivos enumeran las versiones utilizadas.
Con --ts_proto_opt=outputSchema=true
, se generarán tipificaciones meta que luego se pueden usar en otros generadores de código.
Con --ts_proto_opt=outputSchema=no-file-descriptor
, se generarán tipings meta, pero no incluimos el descriptor de archivo en el esquema generado. Esto es útil si está tratando de minimizar el tamaño del esquema generado.
Con --ts_proto_opt=outputSchema=const
, las tipificaciones meta se generarán as const
, lo que permite el acceso a tipo seguro a todas sus propiedades. (Solo funciona con TypeScript 4.9 y arriba, porque también usa el operador satisfies
). Se puede combinar con la opción no-file-descriptor
( outputSchema=const,outputSchema=no-file-descriptor
) para no incluir el descriptor de archivo en el esquema generado.
Con --ts_proto_opt=outputTypeAnnotations=true
, cada mensaje se le dará a un campo $type
que contenga su nombre totalmente calificado. Puede usar --ts_proto_opt=outputTypeAnnotations=static-only
para omitirlo desde la declaración interface
, o --ts_proto_opt=outputTypeAnnotations=optional
para convertirlo en una propiedad opcional en la definición interface
. La última opción puede ser útil si desea usar el campo $type
para el tipo de ejecución, verificar las respuestas desde un servidor.
Con --ts_proto_opt=outputTypeRegistry=true
, se generará el registro de tipo que se puede usar para resolver los tipos de mensajes mediante un nombre totalmente calificado. Además, cada mensaje recibirá un campo $type
que contenga su nombre totalmente calificado.
Con --ts_proto_opt=outputServices=grpc-js
, TS-PROTO emitirá definiciones de servicio y stubs de servidor / cliente en formato GRPC-JS.
Con --ts_proto_opt=outputServices=generic-definitions
, TS-Proto emitirá definiciones de servicio genéricas (framework-agnostic). Estas definiciones contienen descriptores para cada método con enlaces a los tipos de solicitudes y respuesta, lo que permite generar trozos de servidor y cliente en tiempo de ejecución, y también generar tipos fuertes para ellos en el momento de la compilación. Un ejemplo de una biblioteca que utiliza este enfoque es NICE-GRPC.
Con --ts_proto_opt=outputServices=nice-grpc
, TS-PROTO emitirá stubs de servidor y cliente para NICE-GRPC. Esto debe usarse junto con definiciones genéricas, es decir, debe especificar dos opciones: outputServices=nice-grpc,outputServices=generic-definitions
.
Con --ts_proto_opt=metadataType=Foo@./some-file
, TS-Proto agregue un campo de metadatos genérico (marco-agnóstico) a la definición de servicio genérico.
Con --ts_proto_opt=outputServices=generic-definitions,outputServices=default
, TS-Proto generará definiciones e interfaces genéricas. Esto es útil si desea confiar en las interfaces, pero también tiene algunas capacidades de reflexión en tiempo de ejecución.
Con --ts_proto_opt=outputServices=false
, o =none
, TS-Proto no generará definiciones de servicio.
Con --ts_proto_opt=rpcBeforeRequest=true
, TS-Proto agregará una definición de función a la definición de la interfaz RPC con la firma: beforeRequest(service: string, message: string, request: <RequestType>)
. También se establecerá automáticamente outputServices=default
. Cada uno de los métodos del Servicio llamará beforeRequest
antes de realizar su solicitud.
Con --ts_proto_opt=rpcAfterResponse=true
, TS-Proto agregará una definición de función a la definición de la interfaz RPC con la firma: afterResponse(service: string, message: string, response: <ResponseType>)
. También se establecerá automáticamente outputServices=default
. Cada uno de los métodos del servicio llamará afterResponse
antes de devolver la respuesta.
Con --ts_proto_opt=rpcErrorHandler=true
, TS-PROTO agregará una definición de función a la definición de la interfaz RPC con la firma: handleError(service: string, message: string, error: Error)
. También se establecerá automáticamente outputServices=default
.
Con --ts_proto_opt=useAbortSignal=true
, los servicios generados aceptarán un AbortSignal
para cancelar las llamadas RPC.
Con --ts_proto_opt=useAsyncIterable=true
, los servicios generados usarán AsyncIterable
en lugar de Observable
.
Con --ts_proto_opt=emitImportedFiles=false
, ts-propo no emitirá archivos google/protobuf/*
a menos que esté explícito agregue archivos a protoc
como este protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto my_message.proto google/protobuf/duration.proto
Con --ts_proto_opt=fileSuffix=<SUFFIX>
, TS-PROTO emitirá archivos generados utilizando el sufijo especificado. Un archivo helloworld.proto
con fileSuffix=.pb
se generaría como helloworld.pb.ts
. Este es un comportamiento común en otros complementos de protocas y proporciona una forma de anotar rápidamente todos los archivos generados.
Con --ts_proto_opt=importSuffix=<SUFFIX>
, TS-Proto emitirá importaciones de archivos utilizando el sufijo especificado. Una importación de helloworld.ts
con fileSuffix=.js
generaría import "helloworld.js"
. El valor predeterminado es importar sin una extensión de archivo. Compatible con TypeScript 4.7.x y Up.
Con --ts_proto_opt=enumsAsLiterals=true
, los tipos de enum generados serán objeto enum-ish con as const
.
Con --ts_proto_opt=useExactTypes=false
, los métodos generados fromPartial
y create
no utilizarán tipos exactos.
El comportamiento predeterminado es useExactTypes=true
, que se produce fromPartial
y create
un tipo exacto de uso para que su argumento haga que TypeScript rechace las propiedades desconocidas.
Con --ts_proto_opt=unknownFields=true
, todos los campos desconocidos se analizarán y saldrán como matrices de buffers.
Con --ts_proto_opt=onlyTypes=true
, solo se emitirán tipos, y se excluirán las importaciones para long
y protobufjs/minimal
.
Esto es lo mismo que la configuración outputJsonMethods=false,outputEncodeMethods=false,outputClientImpl=false,nestJs=false
Con --ts_proto_opt=usePrototypeForDefaults=true
, el código generado envolverá nuevos objetos con Object.create
.
Esto permite que el código realice las verificaciones de Hazzer para detectar cuándo se han aplicado los valores predeterminados, que debido al comportamiento de Proto3 de no poner valores predeterminados en el cable, típicamente solo es útil para interactuar con los mensajes Proto2.
Cuando está habilitado, los valores predeterminados se heredan de un prototipo, por lo que el código puede usar Object.Keys (). Incluye ("Somefield") para detectar si Somefield estaba realmente decodificado o no.
Tenga en cuenta que, como se indica, esto significa objeto. Keeys no incluirá campos establecidos por defecto, por lo que si tiene código que itera las claves de mensajes de manera genérica, también tendrá que iterar sobre las claves heredadas del prototipo.
Con --ts_proto_opt=useJsonName=true
, json_name
definido en Protofiles se usará en lugar de nombres de campo de mensajes.
Con --ts_proto_opt=useJsonWireFormat=true
, el código generado reflejará la representación JSON de los mensajes ProtoBuf.
Requiere onlyTypes=true
. Implica useDate=string
y stringEnums=true
. Esta opción es generar tipos que se puedan usar directamente con los mensajes de ProtoBuf de mariscal/desactivación serializados como JSON. También es posible que desee establecer useOptionals=all
, ya que las puertas de enlace de GRPC no son necesarias para enviar un valor predeterminado para los valores escalares.
Con --ts_proto_opt=useNumericEnumForJson=true
, el convertidor JSON ( toJSON
) codificará los valores de enum como int, en lugar de una cadena literal.
Con --ts_proto_opt=initializeFieldsAsUndefined=false
, todos los inicializadores de campo opcionales se omitirán desde las instancias base generadas.
Con --ts_proto_opt=disableProto2Optionals=true
, todos los campos opcionales en los archivos Proto2 no se establecerán para que sea opcional. Tenga en cuenta que este indicador es principalmente para preservar el manejo heredado de los archivos Proto2 de TS-Proto, para evitar romper los cambios y, como resultado, no está destinado a usarse en el futuro.
Con --ts_proto_opt=disableProto2DefaultValues=true
, todos los campos en los archivos Proto2 que especifican un valor predeterminado no utilizarán ese valor predeterminado. Tenga en cuenta que este indicador es principalmente para preservar el manejo heredado de los archivos Proto2 de TS-Proto, para evitar romper los cambios y, como resultado, no está destinado a usarse en el futuro.
Con --ts_proto_opt=Mgoogle/protobuf/empty.proto=./google3/protobuf/empty
, ('m' significa 'importación de importación', similar a protoc-gen-go), la ruta de importación de código generado para ./google/protobuf/empty.ts
reflejará el valor anulado:
Mfoo/bar.proto=@myorg/some-lib
mapeará foo/bar.proto
Imports a import ... from '@myorg/some-lib'
.
Mfoo/bar.proto=./some/local/lib
Mape foo/bar.proto
Imports a import ... from './some/local/lib'
.
Mfoo/bar.proto=some-modules/some-lib
Maps foo/bar.proto
Imports a import ... from 'some-module/some-lib'
.
Nota : Los usos se acumulan, por lo que se esperan múltiples valores en forma de --ts_proto_opt=M... --ts_proto_opt=M...
(ONE ts_proto_opt
POR MAPPACIÓN).
Nota : No se generarán archivos Proto que coincidan con las importaciones asignadas.
Con --ts_proto_opt=useMapType=true
, el código generado para ProtoBuf map<key_type, value_type>
se convertirá en Map<key_type, value_type>
que usa el tipo de mapa JavaScript.
El comportamiento predeterminado es useMapType=false
, lo que hace que genere el código para ProTobuf map<key_type, value_type
con el par de valores clave como {[key: key_type]: value_type}
.
Con --ts_proto_opt=useReadonlyTypes=true
, los tipos generados se declararán inmutables utilizando el modificador readonly
de TypeScript.
Con --ts_proto_opt=useSnakeTypeName=false
eliminará la carcasa de serpientes de los tipos.
Ejemplo de protobuf
cuadro de mensaje {elemento de mensaje {imagen de mensaje {enum alineación {izquierda = 1; Centro = 2; Derecho = 3; } } } }
Por defecto, esto está habilitado que generaría un tipo de Box_Element_Image_Alignment
. Al deshabilitar esta opción, el tipo que se genera sería BoxElementImageAlignment
.
Con --ts_proto_opt=outputExtensions=true
, el código generado incluirá extensiones Proto2
Los métodos de codificación/decodificación de extensión cumplen con la opción outputEncodeMethods
, y si unknownFields=true
, los métodos setExtension
y getExtension
se crearán para mensajes extendibles, también cumplen con outputEncodeMethods
(setExtension = codede, getExtension = decode).
Con --ts_proto_opt=outputIndex=true
, los archivos de índice se generarán en función de los espacios de nombres de los paquetes Proto.
Esto deshabilitará exportCommonSymbols
para evitar colisiones de nombres en los símbolos comunes.
Con --ts_proto_opt=emitDefaultValues=json-methods
, el método TOJSON generado emitirá escalares como 0
y ""
como campos json.
Con --ts_proto_opt=comments=false
, los comentarios no se copiarán de los archivos Proto al código generado.
Con --ts_proto_opt=bigIntLiteral=false
, el código generado usará BigInt("0")
en lugar de 0n
para literales de BigInt. Los literales de Bigint no son compatibles con TypeScript cuando la opción del compilador "Target" se establece en algo más antiguo que "ES2020".
Con --ts_proto_opt=useNullAsOptional=true
, los valores undefined
se convertirán en null
, y si usa la etiqueta optional
en su archivo .proto
, el campo también tendrá un tipo undefined
. Por ejemplo:
Con --ts_proto_opt=typePrefix=MyPrefix
, las interfaces, enums y fábricas generadas tendrán un prefijo de MyPrefix
en sus nombres.
Con --ts_proto_opt=typeSuffix=MySuffix
, las interfaces, enums y fábricas generadas tendrán un sufijo de MySuffix
en sus nombres.
Mensaje perfilinfo {int32 id = 1; string bio = 2; string phone = 3; } Departamento de mensajes {int32 id = 1; name de cadena = 2; } Mensaje usuario {int32 id = 1; string username = 2;/* perfilInfo será opcional en typescript, el tipo será perfilinfo | NULL | Undefinado esto es necesario en los casos en que no desea proporcionar ningún valor para el perfil. */perfil de perfil opcional = 3;/* El departamento solo acepta un tipo de departamento o nulo, por lo que esto significa que debe pasarlo nulo si no hay valor disponible. */Departamento de departamento = 4; }
Las interfaces generadas serán:
Exportar interfaz de perfilinfo { ID: número; bio: cadena; teléfono: string;} Departamento de interfaz de exportación { ID: número; Nombre: String;} Interfaz de exportación Usuario { ID: número; nombre de usuario: cadena; perfil?: perfilinfo | NULL | indefinido; // Verifique este Departamento: Departamento | nulo; // Verifique este}
Con --ts_proto_opt=noDefaultsForOptionals=true
, los valores primitivos undefined
no se deforman según la especificación de ProtoBuf. Además, a diferencia del comportamiento estándar, cuando un campo se establece en su valor predeterminado estándar, se codificará permitiendo que se envíe a través del cable y se distinga de los valores indefinidos. Por ejemplo, si un mensaje no establece un valor booleano, normalmente esto se predeterminaría en false
lo cual es diferente a que no se define.
Esta opción permite que la biblioteca actúe de manera compatible con la implementación de cable mantenida y utilizada por Square/Block. Nota: Esta opción solo debe usarse en combinación con otro código de cliente/servidor generado usando Wire o TS-Proto con esta opción habilitada.
Tenemos una excelente manera de trabajar junto con Nestjs. ts-proto
genera interfaces
y decorators
para su controlador, cliente. Para obtener más información, consulte el reingreso Nestjs.
Si desea ejecutar ts-proto
en cada cambio de un archivo Proto, necesitará usar una herramienta como Chokidar-Cli y usarla como un script en package.json
:
"Proto: generar": "Protoc -TS_Proto_out =. c "npm run proto: generar" "
ts-proto
es RPC Framework Agnóstico: cómo transmite sus datos hacia y desde su fuente de datos depende de usted. Todas las implementaciones del cliente generadas esperan un parámetro rpc
, qué tipo se define así:
interfaz rpc { Solicitud (servicio: cadena, método: cadena, datos: uint8Array): promesa <uint8array>;}
Si está trabajando con GRPC, una implementación simple podría verse así:
const conn = nuevo grpc.client ( "Localhost: 8765", Grpc.credentials.createInsecure ()); type rpcImpl = (Service: String, Method: String, Data: Uint8Array) => Promise <Uint8Array>; const sendRequest: rpcImpl = (servicio, método, data) => { // convencionalmente en GRPC, la ruta de solicitud se ve como // "paquete.names.serviceName/MethodName", // Por lo tanto, construimos tal cadena const ruta = `/$ {servicio}/$ {método}`; return new Promise ((resolve, rechazar) => {// makeUnaryRequest transmite el resultado (y error) con una devolución de llamada // ¡transforma esto en una promesa! const durtcallback: unarycallback <any> = (err, res) => {if (err) {return rechazar (err);} resolve (res);}; function passthrough (argumento: any) {return argument;} // utilizando pases a través de las funciones serializadas y deserializadas. , resultado de Backback); });}; const rpc: rpc = {request: sendRequest};
Felicitaciones a nuestros patrocinadores:
Ngrok financió el soporte GRPC-WEB inicial de TS-Proto.
Si necesita personalizaciones de TS-Proto o soporte prioritario para su empresa, puede hacerme pasar por correo electrónico.
Esta sección describe cómo contribuir directamente a TS-Proto, es decir, no es necesario para ejecutar ts-proto
en protoc
o usar el TypeScript generado.
Requisitos
Estibador
yarn
npm install -g yarn
Configuración
Los comandos a continuación suponen que tiene Docker instalado. Si está utilizando OS X, instale CoreUtils , brew install coreutils
.
Consulte el repositorio del último código.
Ejecute yarn install
para instalar las dependencias.
Ejecute yarn build:test
para generar los archivos de prueba.
Esto ejecuta los siguientes comandos:
proto2ts
: ejecuta ts-proto
en los archivos integration/**/*.proto
para generar archivos .ts
.
proto2pbjs
: genera una implementación de referencia utilizando pbjs
para probar la compatibilidad.
Ejecutar yarn test
Flujo de trabajo
Agregar/actualizar una prueba de integración para su caso de uso
También puedes dejar yarn watch
en funcionamiento, y debería "hacer lo correcto"
Haga una nueva integration/your-new-test/parameters.txt
con los parámetros ts_proto_opt
necesarios
Cree una integration/your-new-test/your-new-test.proto
Schema para reproducir su caso de uso
Encuentre una prueba integration/*
existente que esté lo suficientemente cerca de su caso de uso ts_proto_opt
por ejemplo, tiene un parameters.txt
Si crea una nueva prueba de integración:
Después de cualquier cambio a your-new-test.proto
, o un archivo integration/*.proto
, ejecute yarn proto2bin
Agregue/actualice una prueba integration/your-new-test/some-test.ts
, incluso si es tan trivial como solo asegurarse de que el código generado se compila
Modificar la lógica de generación de código ts-proto
:
O yarn proto2ts your-new-test
para volver a codegen una prueba específica
Nuevamente, dejar yarn watch
debería "hacer lo correcto"
La lógica más importante se encuentra en SRC/Main.ts.
Después de cualquier cambio en los archivos src/*.ts
, ejecute yarn proto2ts
para volver a codegen todas las pruebas de integración
Ejecute yarn test
para verificar sus cambios. Pase todas las pruebas existentes
Cometer y enviar un PR
A veces, la comprobación del código generado está mal visto, pero dado el trabajo principal de TS-Proto es generar código, es útil ver las diferencias de Codegen en PRS
Ejecute yarn format
para formatear los archivos TypeScript.
Asegúrese de git add
todos los archivos *.proto
, *.bin
y *.ts
en integration/your-new-test
Pruebas en sus proyectos
Puede probar sus cambios locales de TS-Proto en sus propios proyectos ejecutando yarn add ts-proto@./path/to/ts-proto
, siempre y cuando ejecute yarn build
manualmente.
Protoca Dockerizada
El repositorio incluye una versión dockerizada de protoc
, que está configurada en Docker-Compose.yml.
Puede ser útil en caso de que desee invocar manualmente el complemento con una versión conocida de protoc
.
Uso:
# Incluya el alias de protocas en su caparazón. El directorio TS-Proto está disponible en /ts-propo.protoc --plugin =/ts-propo/protoc-gen-ts_proto -ts_proto_out =./Output -i =./Protos ./protoc/*.proto# o Use el alias TS-ProToc que especifica la ruta del complemento para usted.
Todas las rutas deben ser rutas relativas dentro del directorio de trabajo actual del host. ../
no está permitido
Dentro del contenedor Docker, la ruta absoluta a la raíz del proyecto es /ts-proto
El contenedor monta el directorio de trabajo actual en /host
, y lo establece como su directorio de trabajo.
Una vez que se obtiene aliases.sh
, puede usar el comando protoc
en cualquier carpeta.
El nombre del módulo TS/ES6 es el paquete Proto
Admite la codificación de duración basada en cadenas en fromJSON
/ toJSON
Hacer de oneof=unions-value
el comportamiento predeterminado en 2.0
Probablemente cambie el valor predeterminado forceLong
en 2.0, debería ser predeterminado a forceLong=long
Hacer que esModuleInterop=true
el valor predeterminado en 2.0
De forma predeterminada, TS-Proto modela oneof
los campos "rotundamente" en el mensaje, por ejemplo, un mensaje como:
mensaje foo {oneof o_field {string field_a = 1; cadena field_b = 2; } }
Generará un tipo Foo
con dos campos: field_a: string | undefined;
y field_b: string | undefined
.
Con esta salida, tendrá que verificar tanto if object.field_a
como if object.field_b
, y si establece uno, tendrá que recordar que desanimará al otro.
En su lugar, recomendamos usar la opción oneof=unions-value
, que cambiará la salida para que sea un tipo de datos algebraicos/ADT como:
interactúe suMessage { ¿O UNFIELD?: {$ CASE: "FIELD_A"; Valor: cadena} | {$ case: "field_b"; valor: cadena};}
Ya que esto hará cumplir automáticamente solo uno de field_a
o field_b
"Establecer" a la vez, porque los valores se almacenan en el campo eitherField
que solo pueden tener un solo valor a la vez.
(Tenga en cuenta que eitherField
es opcional b/c oneof
de ProtoBuf significa que "en la mayoría de un campo" se establece, y no significa que uno de los campos debe estar establecido).
En la liberación 2.x actualmente no sesculada de TS-Proto, oneof=unions-value
se convertirá en el comportamiento predeterminado.
También hay una opción oneof=unions
, que genera una unión donde los nombres de campo se incluyen en cada opción:
interactúe suMessage { ¿O UNFIELD?: {$ CASE: "FIELD_A"; campo_a: cadena} | {$ case: "field_b"; field_b: string};}
Esto ya no se recomienda, ya que puede ser difícil escribir código y tipos para manejar múltiples opciones:
Los siguientes tipos de ayuda pueden hacer que sea más fácil trabajar con los tipos generados a partir de oneof=unions
, aunque generalmente no son necesarios si usa oneof=unions-value
:
/** Extrae todos los nombres de casos de un campo OneOf. */type OneFcases <T> = t extiende {$ case: infer u extiende la cadena}? U: nunca;/** extrae una unión de todos los tipos de valor de un campo OneOf*/type OneOfValues <t> = t extiende {$ case: infer u extiende cadena; [clave: cadena]: desconocido}? T [u]: nunca;/** extrae el tipo específico de un caso OneOf basado en su nombre de campo*/escriba OneOfcase <t, k extiende oneOfCases <T>> = t extiende { $ Caso: K; [clave: cadena]: desconocido;} ? T : nunca;/** extrae el tipo específico de un tipo de valor de un campo OneOf*/type OneOfValue <t, k extiende OneFcases <T>> = t extiende { $ Caso: Infer U extiende K; [clave: cadena]: desconocido;} ? T [u] : nunca;
A modo de comparación, los equivalentes para oneof=unions-value
:
/** Extrae todos los nombres de casos de un campo OneOf. */type OneFcases <T> = t ['$ case'];/** extrae una unión de todos los tipos de valor de un campo OneOf*/type OneOfValues <T> = t ['valor'];/** Extrae El tipo específico de un caso OneOf basado en su nombre de campo */Escriba OneFcase <t, K extiende OneOfCases <T>> = t extiende { $ Caso: K; [clave: cadena]: desconocido;} ? T : nunca;/** extrae el tipo específico de un tipo de valor de un campo OneOf*/type OneOfValue <t, k extiende OneFcases <T>> = t extiende { $ Caso: Infer U extiende K; Valor: Desconocido;} ? T [u] : nunca;
En Core ProtoBuf (y así también ts-proto
), los valores que no sean o iguales al valor predeterminado no se envían a través del cable.
Por ejemplo, el valor predeterminado de un mensaje no está undefined
. Los tipos primitivos toman su valor predeterminado natural, por ejemplo, string
es ''
, number
es 0
, etc.
ProtoBuf eligió/aplica este comportamiento porque permite la compatibilidad directa, ya que los campos primitivos siempre tendrán un valor, incluso cuando se omiten agentes obsoletos.
Esto es bueno, pero también significa que los valores predeterminados y no se pueden distinguir en los campos ts-proto
; Es solo fundamentalmente cómo funciona ProtoBuf.
Si necesita campos primitivos donde puede detectar SET/Unset, consulte los tipos de envoltura.
Codificar / decodificar
ts-proto
sigue las reglas de ProtoBuf, y siempre devuelve los valores predeterminados para los campos de Un INSPET al decodificar, al omitirlas desde la salida cuando se está serializado en formato binario.
syntax = "proto3"; mensaje foo {string bar = 1; }
ProtoBufbytes; // Suponga que este es un objeto Foo vacío, en ProtoBuf Formatfoo.Decode (ProtoBufbytes); // => {bar: ''}
Foo.encode ({bar: ""}); // => {}, escribe un objeto foo vacío, en formato binario protobuf
Fromjson / tojson
Reading JSON también inicializará los valores predeterminados. Dado que los remitentes pueden omitir los campos Unset o establecerlos en el valor predeterminado, use fromJSON
para normalizar la entrada.
Foo.fromjson ({}); // => {bar: ''} foo.fromjson ({bar: ""}); // => {bar: ''} foo.fromjson ({bar: "baz"}); // => {bar: 'Baz'}
Al escribir JSON, ts-proto
normaliza los mensajes omitiendo campos y campos Unset establecidos en sus valores predeterminados.
Foo.tojson ({}); // => {} foo.tojson ({bar: undefined}); // => {} foo.tojson ({bar: ""}); // => {} - NOTA: omitir el valor predeterminado, como se espera. // => {bar: 'Baz'}
ProtoBuf viene con varias definiciones de mensajes predefinidos, llamadas "tipos conocidos". Su interpretación está definida por la especificación de ProtoBuf, y se espera que las bibliotecas conviertan estos mensajes en los tipos nativos correspondientes en el idioma de destino.
ts-proto
actualmente convierte automáticamente estos mensajes en sus tipos nativos correspondientes.
Google.Protobuf.Boolvalue ⇆ boolean
Google.protobuf.bytesvalue ⇆ Uint8Array
Google.protobuf.DoubleValue ⇆ number
Google.protobuf.fieldmask ⇆ string[]
Google.Protobuf.FloatValue ⇆ number
Google.Protobuf.int32Value ⇆ number
Google.protobuf.int64 Value ⇆ number
Google.protobuf.listValue ⇆ any[]
Google.protobuf.uint32Value ⇆ number
Google.protobuf.uint64 Value ⇆ number
Google.Protobuf.StringValue ⇆ string
Google.protobuf.Value ⇆ any
(es decir, es decir number | string | boolean | null | array | object
)
Google.protobuf.struct ⇆ { [key: string]: any }
Los tipos de envoltura son mensajes que contienen un solo campo primitivo y se pueden importar en archivos .proto
con import "google/protobuf/wrappers.proto"
.
Dado que estos son mensajes , su valor predeterminado está undefined
, lo que le permite distinguir primitivas no establecidas de sus valores predeterminados, cuando se usa tipos de envoltura. ts-proto
genera estos campos como <primitive> | undefined
.
Por ejemplo:
// ProtoBufSyntax = "Proto3"; import "Google/ProtoBuf/Wrappers.proto"; Mensaje EjemplMessage {google.protobuf.stringValue name = 1; }
// TypeScriptInterface EjemploPplemessage { Nombre: cadena | indefinido;}
Al codificar un mensaje, el valor primitivo se convierte en su tipo de envoltorio correspondiente:
Examplemessage.encode ({nombre: "foo"}); // => {nombre: {valor: 'foo'}}, en binario
Al llamar a Tojson, el valor no se convierte, porque los tipos de envoltura son idiomáticos en JSON.
Examplemessage.tojson ({nombre: "foo"}); // => {nombre: 'foo'}
El lenguaje y los tipos de ProtoBuf no son suficientes para representar todos los valores JSON posibles, ya que JSON puede contener valores cuyo tipo es desconocido de antemano. Por esta razón, ProtoBuf ofrece varios tipos adicionales para representar valores JSON arbitrarios.
Estos se denominan tipos de estructura y se pueden importar en archivos .proto
con import "google/protobuf/struct.proto"
.
Google.protobuf.Value ⇆ any
Este es el tipo más general, y puede representar cualquier valor JSON (es decir, number | string | boolean | null | array | object
).
Google.protobuf.listValue ⇆ any[]
Para representar una matriz JSON
Google.protobuf.struct ⇆ { [key: string]: any }
Para representar un objeto JSON
ts-proto
se convierte automáticamente entre estos tipos de estructura y sus tipos JSON correspondientes.
Ejemplo:
// ProtoBufSyntax = "Proto3"; import "Google/ProtoBuf/struct.proto"; Mensaje EjemplMessage {google.protobuf.value cualquier cosa = 1; }
// TypeScriptInterface EjemploPplemessage { Cualquier cosa: cualquiera | indefinido;}
Codificando un valor JSON integrado en un mensaje, lo convierte en un tipo de estructura:
Examplemessage.encode ({cualquier cosa: {nombre: "Hola"}});/* emite la siguiente estructura, codificada en formato binario de protobuf: {cualquier cosa: valor {structValue = struct {Fields = [MapEntry {Key = "Nombre", valor = valor {stringvalue = "hello"}]}}}}*/examplemessage.encode ({cualquier cosa: true});/* emite la siguiente estructura codificada en formato binario protobuf: {cualquier cosa: valor {boolvalue = true}} */
La representación de google.protobuf.Timestamp
es configurable por el indicador useDate
. El indicador useJsonTimestamp
controla la precisión cuando useDate
es false
.
ProtoBuf Tipo bien conocido | Predeterminado/ useDate=true | useDate=false | useDate=string | useDate=string-nano |
---|---|---|---|---|
google.protobuf.Timestamp | Date | { seconds: number, nanos: number } | string | string |
Cuando se usa useDate=false
y useJsonTimestamp=raw
TimeStamp se representa como { seconds: number, nanos: number }
, pero tiene precisión de nanosegundos.
Cuando se usa la marca de tiempo useDate=string-nano
se representa como una cadena ISO con Nanosegund Precision 1970-01-01T14:27:59.987654321Z
y se basa en la biblioteca Nano-Date para la conversión. Deberá instalarlo en su proyecto.
Se supone que los números son por defecto son number
de JavaScript sencillos.
Esto está bien para los tipos de ProtoBuf como int32
y float
, pero los tipos de 64 bits como int64
no pueden estar 100% representados por el tipo de number
de JavaScript, porque int64
puede tener valores más grandes/más pequeños que number
.
La configuración predeterminada de TS-Proto (que es forceLong=number
) aún debe usar number
para campos de 64 bits, y luego arrojar un error si un valor (en tiempo de ejecución) es mayor que Number.MAX_SAFE_INTEGER
.
Si espera usar los valores de 64 bits / super-than- MAX_SAFE_INTEGER
, puede usar la opción TS-Proto forceLong
, que utiliza el paquete NPM largo para admitir todo el rango de valores de 64 bits.
Los tipos de números de ProtoBuf maps a los tipos de JavaScript en función de la opción de configuración forceLong
:
Tipos de números de protobuf | Predeterminado/ forceLong=number | forceLong=long | forceLong=string |
---|---|---|---|
doble | número | número | número |
flotar | número | número | número |
int32 | número | número | número |
int64 | número* | Largo | cadena |
uint32 | número | número | número |
uint64 | número* | Sin firmar largo | cadena |
sint32 | número | número | número |
sint64 | número* | Largo | cadena |
fijo32 | número | número | número |
fijo64 | número* | Sin firmar largo | cadena |
sfixed32 | número | número | número |
sfixed64 | número* | Largo | cadena |
Donde (*) indica que podrían lanzar un error en tiempo de ejecución.
Primitivas requeridas: use as-is, es decir, string name = 1
.
Primitivas opcionales: use tipos de envoltorio, es decir, StringValue name = 1
.
Mensajes requeridos: no disponible
Mensajes opcionales: use AS-IS, es decir, SubMessage message = 1
.