线索类型
从 Thrift IDL 文件生成类型脚本 .
安装
$ npm install --save @creditkarma/thrift-typescript
使用量
Thrift TypeScript同时提供JavaScript和命令行API.
鉴于以下文件
节俭/简单.thrift
struct MyStruct {
1: required i32 id,
2: required bool field1,
# 3: required string field,
4: required i16 field,
}
您可以通过命令行生成 TypeScript :
$ thrift-typescript --target apache --rootDir . --sourceDir thrift --outDir codegen simple.thrift
现有备选办法如下:
- --rootDir:用于解析出和源目录. 默认为当前目录 .
- --outDir: 要保存已生成文件的目录 。 如果它不存在,就会被创造出来 默认为“ codegen ” .
- --sourceDir:搜索源文件的目录. 默认为“ 撕裂 ” .
- --goal: 生成用于“ apache” 或“ thrift- server” 的核心库 。 默认为“ apache ” .
- --critical Unions:如果我们产生严格的结合(仅可用于目标='thrift-server'). 更多关于这一点,见下文)。 未定义的默认值 .
- -- fallbackNamespace: 如果不存在'j's' namespace, 则返回的名称空间 。 默认为"java". 设定为“ 无” 无法使用命名空间 .
- -- 使用Name 字段: 如果我们生成一个
__name字段。 未定义的默认值 .
所有其他字段均被假定为源文件.
如果未提供文件的明确列表, 将会使用源代码“ . thrift” 中找到的所有文件 .
您可以从一个以上 Thrift 文件 中获取 gen 代码 :
$ thrift-typescript one.thrift two.thrift three.thrift
Java 脚本 API
您也可以使用 JavaScript API 生成文件 :
import { generate } from '@creditkarma/thrift-typescript'
// Generates TypeScript and saves to given outDir
generate({
rootDir: '.',
sourceDir: 'thirft',
outDir: 'codegen',
target: 'thrift-server',
files: [
'simple.thrift'
],
fallbackNamespace: 'java',
})
向字符串倾斜
您也可以从 Thrift 的字符串中生成 TypeScript 而不保存到文件 .
说明: 这种代码生成方法并不支持包括. Thrift 生成器必须能够解析所有没有文件结构描述无法解析的标识符.
import { readFileSync } from 'fs'
import { make } from '@creditkarma/thrift-typescript'
const rawThrift: string = readFileSync('./thrift/simple.thrift', 'utf-8')
const generatedCode: string = make(rawThrift)
调色器服务器
鼠标类型中的 v2.x 等同@ creditkarma/thrift- server v. 7.0 或更高
虽然Thrift TypeScript可以用来生成编码可与 Apache 脉冲库,建议与 调色器服务器。关于Apache用法的详情如下.
Thrift Server在Express或Hapi中增加了有插件或中间软件的 Thrift支持. 与Thrift Server一起使用代码源的另一个外观是将上下文添加到服务客户端和服务处理器中. 在Thrift服务方法中,可以使用上下文来做诸如认证或追踪等事情. 背景是所有服务处理器方法和所有服务客户端方法的可选最终参数.
为您选择的服务器安装 Thrift 服务器执行 。 例如,我们将使用快递的中间软件和请求 http客户端库.
$ npm install --save @creditkarma/thrift-server-core
$ npm install --save @creditkarma/thrift-server-express
$ npm install --save @creditkarma/thrift-client
$ npm install --save express
$ npm install --save request
$ npm install --save @types/express
$ npm install --save @types/request
鉴于这项服务,让我们根据我们生成的代码建立一个客户端和服务器.
service Caluculator {
i32 add(1: i32 left, 2: i32 right)
i32 subtract(1: i32 left, 2: i32 right)
}
运行代码源为您的脉冲服务。 这个 target 选项,否则生成的代码将只与 Apache libs 工作.
$ thrift-typescript --target thrift-server --rootDir . --sourceDir thrift --outDir codegen
客户端
在这个例子中,我们正在使用请求库作为我们的基本连接实例。 请求的选项(核心选项)是我们请求的背景.
你会发现客户类是普通的 类型参数代表上下文的类型. 这通常是类型 CoreOptions 从请求库.
import {
createHttpClient,
HttpConnection,
} from '@creditkarma/thrift-client'
import * as request from 'request'
import { CoreOptions } from 'request'
import { Calculator } from './codegen/calculator'
const CONFIG = {
hostName: 'localhost',
port: 8045
}
const thriftClient: Calculator.Client<CoreOptions> = createHttpClient(Calculator.Client, CONFIG)
thriftClient.add(5, 7, { headers: { 'X-Trace-Id': 'xxxxxx' } })
.then((response: number) => {
expect(response).to.equal(12)
done()
})
服务器
在服务器上,我们可以检查 我们设置在客户端的头部.
import * as bodyParser from 'body-parser'
import * as express from 'express'
import { ThriftServerExpress } from '@creditkarma/thrift-server-express'
import {
Calculator,
} from './codegen/calculator'
// express.Request is the context for each of the service handlers
const serviceHandlers: Calculator.IHandler<express.Request> = {
add(left: number, right: number, context?: express.Request): number {
if (context && context.headers['x-trace-id']) {
// You can trace this request, perform auth, or use additional middleware to handle that.
}
return left + right
},
subtract(left: number, right: number, context?: express.Request): number {
return left - right;
},
}
const PORT = 8090
const app = express()
app.use(
'/thrift',
bodyParser.raw(),
ThriftServerExpress(Calculator.Processor, serviceHandlers),
)
app.listen(PORT, () => {
console.log(`Express server listening on port: ${PORT}`)
})
生成的数据类型
在从 Thrift 源生成 TypeScript 数据类型时 ?
简单类型
它们是:布林斯克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克 所有这些几乎都直接翻译为TypeScript.
给定方向 :
const bool FALSE_CONST = false
const i32 INT_32 = 32
const i64 INT_64 = 64
const list<string> LIST_CONST = ['hello', 'world', 'foo', 'bar']
const set<string> SET_CONST = ['hello', 'world', 'foo', 'bar']
const map<string,string> MAP_CONST = { 'hello': 'world', 'foo': 'bar' }
enum Colors {
RED,
GREEN,
BLUE,
}
typedef string name
生成类型脚本 :
export const FALSE_CONST: boolean = false;
export const INT_32: number = 32;
export const INT_64: thrift.Int64 = new thrift.Int64(64);
export const LIST_CONST: Array<string> = ["hello", "world", "foo", "bar"];
export const SET_CONST: Set<string> = new Set(["hello", "world", "foo", "bar"]);
export const MAP_CONST: Map<string, string> = new Map([["hello", "world"], ["foo", "bar"]]);
export enum Colors {
RED,
GREEN,
BLUE
}
export type name = string;
这里唯一有趣的是处理 i64。JavaScript不支持一个完整的64位整数精度,所以我们把数值用一个 Int64 对象。 你会注意到,这真的没有帮助 在你定义 常数或默认值 在你的Thrift文件中, 但是它确实允许 从JS以外的64位整数 得到正确处理。 该对象导出自 @creditkarma/thrift-server-core 扩展 节点-int64。 。 。 .
结构
结构在直观上类似于接口.
给定方向 :
struct User {
1: required string name
2: string email
3: required i32 id
}
生成类型脚本 :
export interface IUser {
name: string
email?: string
id: number
}
注意:我们采用大写"I"的前缀界面名称的常规.
只有明确要求解开的字段 ?。 。 。 .
这个 __name 外地
您可以通过一个选项, 在每个类似结构( structs, 结合, 例外) 对象上生成一个额外的属性, 也就是其字面名称 Thrift 文件 .
例如,如果我们做了 User 与结构 --withNameField 选项:
export interface IUser {
__name: "User"
name: string
email?: string
id: number
}
在使用此选项生成数据时, 您创建并传递到客户端方法中的任何数据类型都不需要 __name 字段。 然而,您从服务中获得的任何数据都会包含这个额外的属性.
工会
斯里夫特的联盟与结构非常相近. 区别在于它们只允许设置一个字段. 它们还要求设置一个领域。 隐含所有字段都是可选的,但必须设置一个字段.
因此,这转化为一个有所有可选字段的结构:
给定方向 :
union MyUnion {
1: string option1
2: i32 option2
}
生成类型脚本( 没有严格的结合) :
export interface IMyUnion = {
option1?: string
option2?: undefined
}
说明: 这里的区别是, 如果一个字段没有设置, 或者如果设置了多个字段, 则会出现运行时间错误 .
例外
例外是使用服务方法可以抛出的错误. 联合来文/技术服务更自然地造成并造成新的错误。 所以,我们确定的例外 将成为JS课程.
给定方向 :
exception MyException {
1: string message
2: i32 code
}
生成类型脚本 :
export class MyException extends thrift.StructLike implements IMyException {
public message: string
public code?: number
constructor(args?: { message?: string, code?: number }) {
// ...
}
}
然后在你的服务客户端 你可以放弃例外 因为你会任何JS错误 throw new MyException({ message: 'whoops', code: 500 });
服务
服务有点复杂。 服务有两个部分。 这是 Client 发送服务请求和 Processor 用于处理服务请求。 服务 Client 服务 Processor 每个生成的类。 它们和其他一些内部物体一起被包裹在 namespace 那有您服务的名字.
给定方向 :
service MyService {
User getUser(1: i32 id) throws (1: MyException exp);
}
生成类型脚本 :
export namespace MyService {
export class Client<Context> {
constructor(connection: thrift.IThriftConnection<Context>) {
// ...
}
getUser(id: number): Promise<User> {
// ...
}
}
export interface IHandler<Context> {
getUser(id: number, context?: Context): User | Promise<User>
}
export class Processor {
constructor(handler: IHandler<Context>) {
// ...
}
public process(input: thrift.TProtocol, output: thrift.TProtocol, context: Context): Promise<Buffer> {
// ...
}
}
}
这个 Client 相当直线前进。 你创造了一个 Client 实例,您可以调用服务方法。 内部工作 Processor 消费者不需要关心的东西。 最有趣的一点是 IHandler。这是服务小组为履行服务合同的承诺而需要实施的接口。 创建满足对象 <service-name>.IHandler 并传递给建造者 <service-name>.Processor 其他的都交给你了.
闲置类型
鉴于这两个结构:
struct User {
1: required i64 id
}
struct Profile {
1: required User user
2: binary data
3: i64 lastModified
}
在如何处理TypeScript中的事情和如何将数据传送到电线上,有些不同。 由于这个原因,当我们为这些结构生成接口时,我们为每个结构生成两个接口,一个是Thrift的精确表示,另一个是更松散的东西,为JavaScript中的数据工作提供了更大的灵活性.
主要的区别在于标记为 i64 可作为 number,作为 string 或一个 Int64 对象和 binary 可作为 string 或一个 Buffer 对象.
传统上的 JavaScript (bigint 是新且尚未完全支持)不支持64位整数。 这就是说我们需要包扎出刺刀 i64 类型 Int64 对象以保持精确度。 在您的 TypeScript 代码中, 您可能正在使用这些 number (自信的JavaScript的53位精度对于你足够好)或者作为一个 string。这些松散类型允许您这样做,生成的代码将处理转换到 Int64 给你的.
生成类型脚本 :
interface IUser {
id: thrift.Int64
}
interface IUserArgs {
id: number | string | thrift.Int64
}
interface IProfile {
user: IUser
data?: Buffer
lastModified?: thrift.Int64
}
interface IProfileArgs {
user: IUserArgs
data?: string | Buffer
lastModified?: number | string | thrift.Int64
}
松散界面的名称只是附加 Args 在界面名称的末尾。 其原因是这些接口在您的代码中最常被用作参数.
松散的接口在哪里? 松散的界面可以用于任何您,应用程序开发者向生成的代码提供数据,或者作为客户端方法的参数,或者作为服务处理器的返回值.
如果我们有这个服务:
service ProfileService {
Profile getProfileForUser(1: User user)
User getUser(1: i64 id)
}
生成类型脚本 :
namespace ProfileService {
export class Client<Context> {
constructor(connection: thrift.IThriftConnection<Context>) {
// ...
}
getProfileForUser(user: IUserArgs, context?: Context): Promise<IProfile> {
// ...
}
getUser(id: number | string | Int64): Promise<IUser> {
// ...
}
}
export interface IHandler<Context> {
getProfileForUser(user: IUser, context: Context): Promise<IProfileArgs>
getUser(id: Int64, context: Context): Promise<IUserArgs>
}
}
从这些生成类型的草图中可以看到,当数据离开应用程序代码并跨出边界进入生成的代码时,你可以传递松散的值,当数据来自生成的代码时,它总是属于严格的类型.
我们可以用一个 User 对象 id 是一个 number 或一个 string 不用包起来 Int64这些转换是为我们处理的。 A 类 number 传入的包裹 Int64 使用 Int64 构造器 : new Int64(64)A级 string 替换 Int64 使用静态转换 fromDecimalString 方法 : Int64.fromDecimalString('64')类似 string 数据可以传递到 binary 字段和转换为 Buffer 在引擎盖下处理。 这仅仅是说服接口,使TypeScript中的Thrift对象的处理更加容易. 您会注意到服务方法总是返回一个更严格的界面对象. 另外,更严格的接口总是可以通过预期松散接口的地方.
发送数据 穿过电线
当涉及到结构类数据类型(struct, 联合和例外)时,通常你不需要比什么数据类型生成更多的了解. 然而,除了生成的接口/集成/分级外,代码生成器还创建了一个伴对象,知道如何将给定的对象发送到线上.
回想起来 User 来自我们结构示例的对象,除了接口外,代码生成器还创建了这样的编码对象:
export const UserCodec: thrift.IStructCodec<IUserArgs, IUser> {
encode(obj: IUserArgs, output: thrift.TProtocol): void {
// ...
},
decode(input: thrift.TProtocol): IUser {
// ...
}
}
它仅仅是一个知道如何从 Thrift 协议读取给定对象或者将给定对象写入 Thrift 协议的对象.
解码器将永远遵循这个命名惯例,只是附加 Codec 位于您所命名的末尾.
严格结合
注:要求严格结合 thrift-server 版本 0.13.x 或更高级.
仅当生成用于 thrift-server。此选项将生成TypeScript联盟。 这在几个重要方面改变了代码源.
回到我们的工会定义:
union MyUnion {
1: string option1
2: i32 option2
}
与 --strictUnions 我们现在这样生成 TypeScript 旗:
enum MyUnionType {
MyUnionWithOption1 = "option1",
MyUnionWithOption2 = "option2"
}
type MyUnion = IMyUnionWithOption1 | IMyUnionWithOption2
interface IMyUnionWithOption1 {
__type: MyUnionType.MyUnionWithOption1
option1: string
option2?: undefined
}
interface IMyUnionWithOption2 {
__type: MyUnionType.MyUnionWithOption2
option1?: undefined
option2: number
}
type MyUnionArgs = IMyUnionWithOption1Args | IMyUnionWithOption2Args
interface IMyUnionWithOption1Args {
option1: string
option2?: undefined
}
interface IMyUnionWithOption2Args {
option1?: undefined
option2: number
}
这个 enum 表示该表的所有潜在值 __type 与我们结合的每一个变体相关的财产。 而不是产生一个 interface 带有可选属性,我们为每个需要该字段的字段生成一个接口。 我们的结果 type 然后是多个接口的结合,每个接口只有一个属性。 这提供了编译时间的保证,即我们正在为工会确定一个和只有一个领域.
松散的接口 Args 界面,其行为与结构的松散接口相类似。 他们允许你使用 number 替换 Int64 或者让你通过 string 或者说 Buffer 页:1 binary 类型。 此外,他们也放弃了 __type 属性。 在代码中,我们可以分辨出 你经过你设置的田地。 这意味着在大多数情况下,你不需要提供 __type 属性。 您可以使用松散的接口作为服务函数的返回值或作为客户端方法的参数.
这个输出比较复杂,但它允许我们做一些事情. 其中最重要的可能是它使我们能够利用我们申请守则中的歧视结合:
function processUnion(union: MyUnion) {
switch (union.__type) {
case MyUnionType.MyUnionWithOption1:
// Do something
case MyUnionType.MyUnionWithOption2:
// Do something
default:
const _exhaustiveCheck: never = union
throw new Error(`Non-exhaustive match for type: ${_exhaustiveCheck}`)
}
}
我们生成的每个接口都定义了一个需要的字段和一些 n 选项 undefined 字段,我们可以做诸如检查 union.option2 !== undefined 没有编译器错误, 但是如果您尝试使用一个在给定的结合上不应该存在的值, 我们会得到编译器错误 。 这样可以扩大工会的运作方式,使之更加普遍.
使用此表单需要您向编译器证明您工会设置了一个字段( 并且只有一个) .
除更改类型输出外 --strictUnions 旗帜更改输出 Codec 对象。 这个 Codec 对象将有一个额外的方法 create编辑 create 方法取出一个松散的接口并强迫它进入严格的接口(包括: __type (b) 财产).
举个例子 MyUnion 定义为:
const MyUnionCodec: thrift.IStructToolkit<IUserArgs, IUser> { = {
create(args: MyUnionArgs): MyUnion {
// ...
},
encode(obj: IUserArgs, output: thrift.TProtocol): void {
// ...
},
decode(input: thrift.TProtocol): IUser {
// ...
}
}
说明: 在未来的突破中 释放所有的 Codec 对象将重新命名为 Toolkit 因为它们将提供更多的公用设施,用于处理界定的闪烁物体.
阿帕奇脉冲
生成的代码也可以与 Apache 脉冲库。 。 。 .
$ npm install --save thrift
$ npm install --save @types/thrift
鉴于这项服务,让我们根据我们生成的代码建立一个客户端和服务器.
service Calculator {
i32 add(1: i32 left, 2: i32 right)
i32 subtract(1: i32 left, 2: i32 right)
}
运行代码源为您的脉冲服务。 给 --target 选项不需要,因为 apache 是默认的构建目标.
$ thrift-typescript --rootDir . --sourceDir thrift --outDir codegen
客户端
import {
createHttpConnection,
createHttpClient,
HttpConnection,
} from 'thrift'
import { Calculator } from './codegen/calculator'
// The location of the server endpoint
const CONFIG = {
hostName: 'localhost',
port: 8045
}
const options = {
transport: TBufferedTransport,
protocol: TBinaryProtocol,
https: false,
headers: {
Host: config.hostName,
}
}
const connection: HttpConnection = createHttpConnection(CONFIG.hostName, CONFIG.port, options)
const thriftClient: Calculator.Client = createHttpClient(Calculator.Client, connection)
// All client methods return a Promise of the expected result.
thriftClient.add(5, 6).then((result: number) =>{
console.log(`result: ${result}`)
})
服务器
import {
createWebServer,
TBinaryProtocol,
TBufferedTransport,
} from 'thrift'
import { Calculator } from './codegen/calculator'
// Handler: Implement the Calculator service
const myServiceHandler = {
add(left: number, right: number): number {
return left + right
},
subtract(left: number, right: number): number {
return left - right
},
}
// ServiceOptions: The I/O stack for the service
const myServiceOpts = {
handler: myServiceHandler,
processor: Calculator,
protocol: TBinaryProtocol,
transport: TBufferedTransport
}
// ServerOptions: Define server features
const serverOpt = {
services: {
'/': myServiceOpts
}
}
// Create and start the web server
const port: number = 8045;
createWebServer(serverOpt).listen(port, () => {
console.log(`Thrift server listening on port ${port}`)
})
页:1
宝石编码可以使用许多更严格的tsc编译器选项.
{
"compilerOptions": {
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true
}
}
发展
安装依赖关系
npm install
构建
npm run build
在监视模式下运行测试
npm run test:watch
捐款
欲了解更多关于贡献新功能和错误修正的信息,请参见我们 贡献准则。 。 。 . 外部捐助者必须签署捐助者许可证协定
许可证
这个项目的许可证根据 Apache 许可证版本 2.0