diff --git a/README.markdown b/README.markdown index 22a1ca3f9..286c4af57 100644 --- a/README.markdown +++ b/README.markdown @@ -299,6 +299,8 @@ protoc --plugin=node_modules/ts-proto/protoc-gen-ts_proto ./batching.proto -I. - With `--ts_proto_opt=outputTypeRegistry=true`, the type registry will be generated that can be used to resolve message types by fully-qualified name. Also, each message will get extra `$type` field containing fully-qualified name. +- With `--ts_proto_opt=outputServices=grpc-js`, ts-proto will output service definitions and server / client stubs in [grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) format. + ### Only Types If you're looking for `ts-proto` to generate only types for your Protobuf types then passing all three of `outputEncodeMethods`, `outputJsonMethods`, and `outputClientImpl` as `false` is probably what you want, i.e.: diff --git a/integration/grpc-js/google/protobuf/empty.ts b/integration/grpc-js/google/protobuf/empty.ts new file mode 100644 index 000000000..042dddc18 --- /dev/null +++ b/integration/grpc-js/google/protobuf/empty.ts @@ -0,0 +1,74 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; + +export const protobufPackage = 'google.protobuf'; + +/** + * A generic empty message that you can re-use to avoid defining duplicated + * empty messages in your APIs. A typical example is to use it as the request + * or the response type of an API method. For instance: + * + * service Foo { + * rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); + * } + * + * The JSON representation for `Empty` is empty JSON object `{}`. + */ +export interface Empty {} + +const baseEmpty: object = {}; + +export const Empty = { + encode(_: Empty, writer: Writer = Writer.create()): Writer { + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Empty { + const reader = input instanceof Uint8Array ? new Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseEmpty } as Empty; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(_: any): Empty { + const message = { ...baseEmpty } as Empty; + return message; + }, + + toJSON(_: Empty): unknown { + const obj: any = {}; + return obj; + }, + + fromPartial(_: DeepPartial): Empty { + const message = { ...baseEmpty } as Empty; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | undefined; +export type DeepPartial = T extends Builtin + ? T + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} diff --git a/integration/grpc-js/google/protobuf/timestamp.ts b/integration/grpc-js/google/protobuf/timestamp.ts new file mode 100644 index 000000000..ef2662595 --- /dev/null +++ b/integration/grpc-js/google/protobuf/timestamp.ts @@ -0,0 +1,221 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; + +export const protobufPackage = 'google.protobuf'; + +/** + * A Timestamp represents a point in time independent of any time zone or local + * calendar, encoded as a count of seconds and fractions of seconds at + * nanosecond resolution. The count is relative to an epoch at UTC midnight on + * January 1, 1970, in the proleptic Gregorian calendar which extends the + * Gregorian calendar backwards to year one. + * + * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap + * second table is needed for interpretation, using a [24-hour linear + * smear](https://developers.google.com/time/smear). + * + * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By + * restricting to that range, we ensure that we can convert to and from [RFC + * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. + * + * # Examples + * + * Example 1: Compute Timestamp from POSIX `time()`. + * + * Timestamp timestamp; + * timestamp.set_seconds(time(NULL)); + * timestamp.set_nanos(0); + * + * Example 2: Compute Timestamp from POSIX `gettimeofday()`. + * + * struct timeval tv; + * gettimeofday(&tv, NULL); + * + * Timestamp timestamp; + * timestamp.set_seconds(tv.tv_sec); + * timestamp.set_nanos(tv.tv_usec * 1000); + * + * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + * + * FILETIME ft; + * GetSystemTimeAsFileTime(&ft); + * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + * + * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z + * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. + * Timestamp timestamp; + * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); + * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); + * + * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + * + * long millis = System.currentTimeMillis(); + * + * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + * .setNanos((int) ((millis % 1000) * 1000000)).build(); + * + * + * Example 5: Compute Timestamp from Java `Instant.now()`. + * + * Instant now = Instant.now(); + * + * Timestamp timestamp = + * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + * .setNanos(now.getNano()).build(); + * + * + * Example 6: Compute Timestamp from current time in Python. + * + * timestamp = Timestamp() + * timestamp.GetCurrentTime() + * + * # JSON Mapping + * + * In JSON format, the Timestamp type is encoded as a string in the + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" + * where {year} is always expressed using four digits while {month}, {day}, + * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional + * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + * is required. A proto3 JSON serializer should always use UTC (as indicated by + * "Z") when printing the Timestamp type and a proto3 JSON parser should be + * able to accept both UTC and other timezones (as indicated by an offset). + * + * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past + * 01:30 UTC on January 15, 2017. + * + * In JavaScript, one can convert a Date object to this format using the + * standard + * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) + * method. In Python, a standard `datetime.datetime` object can be converted + * to this format using + * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with + * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use + * the Joda Time's [`ISODateTimeFormat.dateTime()`]( + * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D + * ) to obtain a formatter capable of generating timestamps in this format. + */ +export interface Timestamp { + /** + * Represents seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + */ + seconds: number; + /** + * Non-negative fractions of a second at nanosecond resolution. Negative + * second values with fractions must still have non-negative nanos values + * that count forward in time. Must be from 0 to 999,999,999 + * inclusive. + */ + nanos: number; +} + +const baseTimestamp: object = { seconds: 0, nanos: 0 }; + +export const Timestamp = { + encode(message: Timestamp, writer: Writer = Writer.create()): Writer { + if (message.seconds !== 0) { + writer.uint32(8).int64(message.seconds); + } + if (message.nanos !== 0) { + writer.uint32(16).int32(message.nanos); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Timestamp { + const reader = input instanceof Uint8Array ? new Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseTimestamp } as Timestamp; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.seconds = longToNumber(reader.int64() as Long); + break; + case 2: + message.nanos = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Timestamp { + const message = { ...baseTimestamp } as Timestamp; + if (object.seconds !== undefined && object.seconds !== null) { + message.seconds = Number(object.seconds); + } else { + message.seconds = 0; + } + if (object.nanos !== undefined && object.nanos !== null) { + message.nanos = Number(object.nanos); + } else { + message.nanos = 0; + } + return message; + }, + + toJSON(message: Timestamp): unknown { + const obj: any = {}; + message.seconds !== undefined && (obj.seconds = message.seconds); + message.nanos !== undefined && (obj.nanos = message.nanos); + return obj; + }, + + fromPartial(object: DeepPartial): Timestamp { + const message = { ...baseTimestamp } as Timestamp; + if (object.seconds !== undefined && object.seconds !== null) { + message.seconds = object.seconds; + } else { + message.seconds = 0; + } + if (object.nanos !== undefined && object.nanos !== null) { + message.nanos = object.nanos; + } else { + message.nanos = 0; + } + return message; + }, +}; + +declare var self: any | undefined; +declare var window: any | undefined; +var globalThis: any = (() => { + if (typeof globalThis !== 'undefined') return globalThis; + if (typeof self !== 'undefined') return self; + if (typeof window !== 'undefined') return window; + if (typeof global !== 'undefined') return global; + throw 'Unable to locate global object'; +})(); + +type Builtin = Date | Function | Uint8Array | string | number | undefined; +export type DeepPartial = T extends Builtin + ? T + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +function longToNumber(long: Long): number { + if (long.gt(Number.MAX_SAFE_INTEGER)) { + throw new globalThis.Error('Value is larger than Number.MAX_SAFE_INTEGER'); + } + return long.toNumber(); +} + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} diff --git a/integration/grpc-js/grpc-js-test.ts b/integration/grpc-js/grpc-js-test.ts new file mode 100644 index 000000000..2641db66a --- /dev/null +++ b/integration/grpc-js/grpc-js-test.ts @@ -0,0 +1,95 @@ +/** + * @jest-environment node + */ +import { Server, ChannelCredentials, ServerCredentials } from '@grpc/grpc-js'; +import { TestClient, TestServer, TestService } from './simple'; + +describe('grpc-js-test', () => { + it('compiles', () => { + expect(TestService).not.toBeUndefined(); + }); + + it('can create a server and a client', async () => { + const server = new Server(); + + const impl: TestServer = { + unary(call, callback) { + callback(null, call.request); + }, + serverStreaming(call) { + call.write({ + timestamp: call.request.timestamp, + }); + call.end(); + }, + clientStreaming(call, callback) { + call.on('data', (request) => { + callback(null, { + timestamp: request.timestamp, + }); + }); + }, + bidiStreaming(call) { + call.on('data', (request) => { + call.write({ + timestamp: request.timestamp, + }); + }); + call.on('end', () => { + call.end(); + }); + }, + }; + + server.addService(TestService, impl); + + const port = await new Promise((resolve, reject) => { + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { + if (err) { + reject(err); + } else { + resolve(port); + } + }); + }); + server.start(); + + const client = new TestClient(`localhost:${port}`, ChannelCredentials.createInsecure()); + + expect.assertions(4); + + client.unary({}, (err, res) => { + expect(res).toEqual({}); + }); + + const timestamp = new Date(); + + const serverStreamingCall = client.serverStreaming({ timestamp }); + serverStreamingCall.on('data', (response) => { + expect(response.timestamp).toEqual(timestamp); + }); + + const clientStreamingCall = client.clientStreaming((err, res) => { + expect(res.timestamp).toEqual(timestamp); + }); + clientStreamingCall.write({ timestamp }); + clientStreamingCall.end(); + + const bidiStreamingCall = client.bidiStreaming(); + bidiStreamingCall.write({ timestamp }); + bidiStreamingCall.on('data', (response) => { + expect(response.timestamp).toEqual(timestamp); + bidiStreamingCall.end(); + }); + + await new Promise((resolve) => { + setTimeout(() => { + server.tryShutdown(() => { + client.close(); + + resolve(); + }); + }, 100); + }); + }); +}); diff --git a/integration/grpc-js/parameters.txt b/integration/grpc-js/parameters.txt new file mode 100644 index 000000000..a59ba0530 --- /dev/null +++ b/integration/grpc-js/parameters.txt @@ -0,0 +1 @@ +outputServices=grpc-js diff --git a/integration/grpc-js/simple.bin b/integration/grpc-js/simple.bin new file mode 100644 index 000000000..8bf81d02a Binary files /dev/null and b/integration/grpc-js/simple.bin differ diff --git a/integration/grpc-js/simple.proto b/integration/grpc-js/simple.proto new file mode 100644 index 000000000..dbec38e63 --- /dev/null +++ b/integration/grpc-js/simple.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +package simple; + +// Test +service Test { + option deprecated = true; + + // Unary + rpc Unary (google.protobuf.Empty) returns (google.protobuf.Empty) { + option deprecated = true; + } + // Server Streaming + rpc ServerStreaming (TestMessage) returns (stream TestMessage) {} + // Client Streaming + rpc ClientStreaming (stream TestMessage) returns (TestMessage) {} + // Bidi Streaming + rpc BidiStreaming (stream TestMessage) returns (stream TestMessage) {} +} + +message TestMessage { + google.protobuf.Timestamp timestamp = 1; +} diff --git a/integration/grpc-js/simple.ts b/integration/grpc-js/simple.ts new file mode 100644 index 000000000..d03f1d2eb --- /dev/null +++ b/integration/grpc-js/simple.ts @@ -0,0 +1,244 @@ +/* eslint-disable */ +import { + makeGenericClientConstructor, + ChannelCredentials, + ChannelOptions, + UntypedServiceImplementation, + handleUnaryCall, + handleServerStreamingCall, + handleClientStreamingCall, + handleBidiStreamingCall, + Client, + ClientUnaryCall, + Metadata, + CallOptions, + ClientReadableStream, + ClientWritableStream, + ClientDuplexStream, + ServiceError, +} from '@grpc/grpc-js'; +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; +import { Timestamp } from './google/protobuf/timestamp'; +import { Empty } from './google/protobuf/empty'; + +export const protobufPackage = 'simple'; + +export interface TestMessage { + timestamp: Date | undefined; +} + +const baseTestMessage: object = {}; + +export const TestMessage = { + encode(message: TestMessage, writer: Writer = Writer.create()): Writer { + if (message.timestamp !== undefined) { + Timestamp.encode(toTimestamp(message.timestamp), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): TestMessage { + const reader = input instanceof Uint8Array ? new Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseTestMessage } as TestMessage; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.timestamp = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): TestMessage { + const message = { ...baseTestMessage } as TestMessage; + if (object.timestamp !== undefined && object.timestamp !== null) { + message.timestamp = fromJsonTimestamp(object.timestamp); + } else { + message.timestamp = undefined; + } + return message; + }, + + toJSON(message: TestMessage): unknown { + const obj: any = {}; + message.timestamp !== undefined && + (obj.timestamp = message.timestamp !== undefined ? message.timestamp.toISOString() : null); + return obj; + }, + + fromPartial(object: DeepPartial): TestMessage { + const message = { ...baseTestMessage } as TestMessage; + if (object.timestamp !== undefined && object.timestamp !== null) { + message.timestamp = object.timestamp; + } else { + message.timestamp = undefined; + } + return message; + }, +}; + +/** + * Test + * + * @deprecated + */ +export const TestService = { + /** + * Unary + * + * @deprecated + */ + unary: { + path: '/simple.Test/Unary', + requestStream: false, + responseStream: false, + requestSerialize: (value: Empty) => Buffer.from(Empty.encode(value).finish()), + requestDeserialize: (value: Buffer) => Empty.decode(value), + responseSerialize: (value: Empty) => Buffer.from(Empty.encode(value).finish()), + responseDeserialize: (value: Buffer) => Empty.decode(value), + }, + /** Server Streaming */ + serverStreaming: { + path: '/simple.Test/ServerStreaming', + requestStream: false, + responseStream: true, + requestSerialize: (value: TestMessage) => Buffer.from(TestMessage.encode(value).finish()), + requestDeserialize: (value: Buffer) => TestMessage.decode(value), + responseSerialize: (value: TestMessage) => Buffer.from(TestMessage.encode(value).finish()), + responseDeserialize: (value: Buffer) => TestMessage.decode(value), + }, + /** Client Streaming */ + clientStreaming: { + path: '/simple.Test/ClientStreaming', + requestStream: true, + responseStream: false, + requestSerialize: (value: TestMessage) => Buffer.from(TestMessage.encode(value).finish()), + requestDeserialize: (value: Buffer) => TestMessage.decode(value), + responseSerialize: (value: TestMessage) => Buffer.from(TestMessage.encode(value).finish()), + responseDeserialize: (value: Buffer) => TestMessage.decode(value), + }, + /** Bidi Streaming */ + bidiStreaming: { + path: '/simple.Test/BidiStreaming', + requestStream: true, + responseStream: true, + requestSerialize: (value: TestMessage) => Buffer.from(TestMessage.encode(value).finish()), + requestDeserialize: (value: Buffer) => TestMessage.decode(value), + responseSerialize: (value: TestMessage) => Buffer.from(TestMessage.encode(value).finish()), + responseDeserialize: (value: Buffer) => TestMessage.decode(value), + }, +} as const; + +export interface TestServer extends UntypedServiceImplementation { + /** + * Unary + * + * @deprecated + */ + unary: handleUnaryCall; + /** Server Streaming */ + serverStreaming: handleServerStreamingCall; + /** Client Streaming */ + clientStreaming: handleClientStreamingCall; + /** Bidi Streaming */ + bidiStreaming: handleBidiStreamingCall; +} + +export interface TestClient extends Client { + /** + * Unary + * + * @deprecated + */ + unary(request: Empty, callback: (error: ServiceError | null, response: Empty) => void): ClientUnaryCall; + unary( + request: Empty, + metadata: Metadata, + callback: (error: ServiceError | null, response: Empty) => void + ): ClientUnaryCall; + unary( + request: Empty, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: Empty) => void + ): ClientUnaryCall; + /** Server Streaming */ + serverStreaming(request: TestMessage, options?: Partial): ClientReadableStream; + serverStreaming( + request: TestMessage, + metadata?: Metadata, + options?: Partial + ): ClientReadableStream; + /** Client Streaming */ + clientStreaming( + callback: (error: ServiceError | null, response: TestMessage) => void + ): ClientWritableStream; + clientStreaming( + metadata: Metadata, + callback: (error: ServiceError | null, response: TestMessage) => void + ): ClientWritableStream; + clientStreaming( + options: Partial, + callback: (error: ServiceError | null, response: TestMessage) => void + ): ClientWritableStream; + clientStreaming( + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: TestMessage) => void + ): ClientWritableStream; + /** Bidi Streaming */ + bidiStreaming(): ClientDuplexStream; + bidiStreaming(options: Partial): ClientDuplexStream; + bidiStreaming(metadata: Metadata, options?: Partial): ClientDuplexStream; +} + +export const TestClient = (makeGenericClientConstructor(TestService, 'simple.Test') as unknown) as { + new (address: string, credentials: ChannelCredentials, options?: Partial): TestClient; +}; + +type Builtin = Date | Function | Uint8Array | string | number | undefined; +export type DeepPartial = T extends Builtin + ? T + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +function toTimestamp(date: Date): Timestamp { + const seconds = date.getTime() / 1_000; + const nanos = (date.getTime() % 1_000) * 1_000_000; + return { seconds, nanos }; +} + +function fromTimestamp(t: Timestamp): Date { + let millis = t.seconds * 1_000; + millis += t.nanos / 1_000_000; + return new Date(millis); +} + +function fromJsonTimestamp(o: any): Date { + if (o instanceof Date) { + return o; + } else if (typeof o === 'string') { + return new Date(o); + } else { + return fromTimestamp(Timestamp.fromJSON(o)); + } +} + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} diff --git a/package.json b/package.json index 518d171b0..a16a7d925 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "author": "", "license": "ISC", "devDependencies": { + "@grpc/grpc-js": "^1.2.12", "@grpc/proto-loader": "^0.5.4", "@improbable-eng/grpc-web": "^0.13.0", "@improbable-eng/grpc-web-node-http-transport": "^0.13.0", diff --git a/src/generate-grpc-js.ts b/src/generate-grpc-js.ts new file mode 100644 index 000000000..5d7c0bcb3 --- /dev/null +++ b/src/generate-grpc-js.ts @@ -0,0 +1,221 @@ +import { Code, code, def, imp, joinCode } from 'ts-poet'; +import { FileDescriptorProto, ServiceDescriptorProto } from 'ts-proto-descriptors/google/protobuf/descriptor'; +import { camelCase } from './case'; +import { Context } from './context'; +import SourceInfo, { Fields } from './sourceInfo'; +import { messageToTypeName } from './types'; +import { maybeAddComment } from './utils'; + +const CallOptions = imp('CallOptions@@grpc/grpc-js'); +const ChannelCredentials = imp('ChannelCredentials@@grpc/grpc-js'); +const ChannelOptions = imp('ChannelOptions@@grpc/grpc-js'); +const Client = imp('Client@@grpc/grpc-js'); +const ClientDuplexStream = imp('ClientDuplexStream@@grpc/grpc-js'); +const ClientReadableStream = imp('ClientReadableStream@@grpc/grpc-js'); +const ClientUnaryCall = imp('ClientUnaryCall@@grpc/grpc-js'); +const ClientWritableStream = imp('ClientWritableStream@@grpc/grpc-js'); +const handleBidiStreamingCall = imp('handleBidiStreamingCall@@grpc/grpc-js'); +const handleClientStreamingCall = imp('handleClientStreamingCall@@grpc/grpc-js'); +const handleServerStreamingCall = imp('handleServerStreamingCall@@grpc/grpc-js'); +const handleUnaryCall = imp('handleUnaryCall@@grpc/grpc-js'); +const UntypedServiceImplementation = imp('UntypedServiceImplementation@@grpc/grpc-js'); +const makeGenericClientConstructor = imp('makeGenericClientConstructor@@grpc/grpc-js'); +const Metadata = imp('Metadata@@grpc/grpc-js'); +const ServiceError = imp('ServiceError@@grpc/grpc-js'); + +/** + * Generates a service definition and server / client stubs for the + * `@grpc/grpc-js` library. + */ +export function generateGrpcJsService( + ctx: Context, + fileDesc: FileDescriptorProto, + sourceInfo: SourceInfo, + serviceDesc: ServiceDescriptorProto +): Code { + const chunks: Code[] = []; + + chunks.push(generateServiceDefinition(ctx, fileDesc, sourceInfo, serviceDesc)); + chunks.push(generateServerStub(ctx, sourceInfo, serviceDesc)); + chunks.push(generateClientStub(ctx, sourceInfo, serviceDesc)); + chunks.push(generateClientConstructor(fileDesc, serviceDesc)); + + return joinCode(chunks, { on: '\n\n' }); +} + +function generateServiceDefinition( + ctx: Context, + fileDesc: FileDescriptorProto, + sourceInfo: SourceInfo, + serviceDesc: ServiceDescriptorProto +) { + const chunks: Code[] = []; + + maybeAddComment(sourceInfo, chunks, serviceDesc.options?.deprecated); + + // Service definition + chunks.push(code` + export const ${def(`${serviceDesc.name}Service`)} = { + `); + + for (const [index, methodDesc] of serviceDesc.method.entries()) { + const inputType = messageToTypeName(ctx, methodDesc.inputType); + const outputType = messageToTypeName(ctx, methodDesc.outputType); + + const info = sourceInfo.lookup(Fields.service.method, index); + maybeAddComment(info, chunks, methodDesc.options?.deprecated); + + chunks.push(code` + ${camelCase(methodDesc.name)}: { + path: '/${fileDesc.package}.${serviceDesc.name}/${methodDesc.name}', + requestStream: ${methodDesc.clientStreaming}, + responseStream: ${methodDesc.serverStreaming}, + requestSerialize: (value: ${inputType}) => + Buffer.from(${inputType}.encode(value).finish()), + requestDeserialize: (value: Buffer) => ${inputType}.decode(value), + responseSerialize: (value: ${outputType}) => + Buffer.from(${outputType}.encode(value).finish()), + responseDeserialize: (value: Buffer) => ${outputType}.decode(value), + }, + `); + } + + chunks.push(code`} as const;`); + + return joinCode(chunks, { on: '\n' }); +} + +function generateServerStub(ctx: Context, sourceInfo: SourceInfo, serviceDesc: ServiceDescriptorProto) { + const chunks: Code[] = []; + + chunks.push(code`export interface ${def(`${serviceDesc.name}Server`)} extends ${UntypedServiceImplementation} {`); + + for (const [index, methodDesc] of serviceDesc.method.entries()) { + const inputType = messageToTypeName(ctx, methodDesc.inputType); + const outputType = messageToTypeName(ctx, methodDesc.outputType); + + const info = sourceInfo.lookup(Fields.service.method, index); + maybeAddComment(info, chunks, methodDesc.options?.deprecated); + + const callType = methodDesc.clientStreaming + ? methodDesc.serverStreaming + ? handleBidiStreamingCall + : handleClientStreamingCall + : methodDesc.serverStreaming + ? handleServerStreamingCall + : handleUnaryCall; + + chunks.push(code` + ${camelCase(methodDesc.name)}: ${callType}<${inputType}, ${outputType}>; + `); + } + + chunks.push(code`}`); + + return joinCode(chunks, { on: '\n' }); +} + +function generateClientStub(ctx: Context, sourceInfo: SourceInfo, serviceDesc: ServiceDescriptorProto) { + const chunks: Code[] = []; + + chunks.push(code`export interface ${def(`${serviceDesc.name}Client`)} extends ${Client} {`); + + for (const [index, methodDesc] of serviceDesc.method.entries()) { + const inputType = messageToTypeName(ctx, methodDesc.inputType); + const outputType = messageToTypeName(ctx, methodDesc.outputType); + + const info = sourceInfo.lookup(Fields.service.method, index); + maybeAddComment(info, chunks, methodDesc.options?.deprecated); + + const responseCallback = code`(error: ${ServiceError} | null, response: ${outputType}) => void`; + + if (methodDesc.clientStreaming) { + if (methodDesc.serverStreaming) { + // bidi streaming + chunks.push(code` + ${camelCase(methodDesc.name)}(): ${ClientDuplexStream}<${inputType}, ${outputType}>; + ${camelCase(methodDesc.name)}( + options: Partial<${CallOptions}>, + ): ${ClientDuplexStream}<${inputType}, ${outputType}>; + ${camelCase(methodDesc.name)}( + metadata: ${Metadata}, + options?: Partial<${CallOptions}>, + ): ${ClientDuplexStream}<${inputType}, ${outputType}>; + `); + } else { + // client streaming + chunks.push(code` + ${camelCase(methodDesc.name)}( + callback: ${responseCallback}, + ): ${ClientWritableStream}<${inputType}>; + ${camelCase(methodDesc.name)}( + metadata: ${Metadata}, + callback: ${responseCallback}, + ): ${ClientWritableStream}<${inputType}>; + ${camelCase(methodDesc.name)}( + options: Partial<${CallOptions}>, + callback: ${responseCallback}, + ): ${ClientWritableStream}<${inputType}>; + ${camelCase(methodDesc.name)}( + metadata: ${Metadata}, + options: Partial<${CallOptions}>, + callback: ${responseCallback}, + ): ${ClientWritableStream}<${inputType}>; + `); + } + } else { + if (methodDesc.serverStreaming) { + // server streaming + chunks.push(code` + ${camelCase(methodDesc.name)}( + request: ${inputType}, + options?: Partial<${CallOptions}>, + ): ${ClientReadableStream}<${outputType}>; + ${camelCase(methodDesc.name)}( + request: ${inputType}, + metadata?: ${Metadata}, + options?: Partial<${CallOptions}>, + ): ${ClientReadableStream}<${outputType}>; + `); + } else { + // unary + chunks.push(code` + ${camelCase(methodDesc.name)}( + request: ${inputType}, + callback: ${responseCallback}, + ): ${ClientUnaryCall}; + ${camelCase(methodDesc.name)}( + request: ${inputType}, + metadata: ${Metadata}, + callback: ${responseCallback}, + ): ${ClientUnaryCall}; + ${camelCase(methodDesc.name)}( + request: ${inputType}, + metadata: ${Metadata}, + options: Partial<${CallOptions}>, + callback: ${responseCallback}, + ): ${ClientUnaryCall}; + `); + } + } + } + + chunks.push(code`}`); + + return joinCode(chunks, { on: '\n' }); +} + +function generateClientConstructor(fileDesc: FileDescriptorProto, serviceDesc: ServiceDescriptorProto) { + return code` + export const ${def(`${serviceDesc.name}Client`)} = ${makeGenericClientConstructor}( + ${serviceDesc.name}Service, + '${fileDesc.package}.${serviceDesc.name}' + ) as unknown as { + new ( + address: string, + credentials: ${ChannelCredentials}, + options?: Partial<${ChannelOptions}>, + ): ${serviceDesc.name}Client; + } + `; +} diff --git a/src/main.ts b/src/main.ts index 77eadf428..5315baebe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -59,6 +59,7 @@ import { EnvOption, LongOption, OneofOption, Options } from './options'; import { Context } from './context'; import { generateSchema } from './schema'; import { ConditionalOutput } from 'ts-poet/build/ConditionalOutput'; +import { generateGrpcJsService } from './generate-grpc-js'; export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [string, Code] { const { options, utils } = ctx; @@ -179,6 +180,8 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri } chunks.push(code`export const ${serviceConstName} = "${serviceDesc.name}";`); + } else if (options.outputServices === 'grpc-js') { + chunks.push(generateGrpcJsService(ctx, fileDesc, sInfo, serviceDesc)); } else { // This service could be Twirp or grpc-web or JSON (maybe). So far all of their // interfaces are fairly similar so we share the same service interface. @@ -199,7 +202,7 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri } }); - if (options.outputClientImpl && fileDesc.service.length > 0) { + if (!options.outputServices && options.outputClientImpl && fileDesc.service.length > 0) { if (options.outputClientImpl === true) { chunks.push(generateRpcType(ctx)); } else if (options.outputClientImpl === 'grpc-web') { diff --git a/src/options.ts b/src/options.ts index c08ca0874..a478cbb4b 100644 --- a/src/options.ts +++ b/src/options.ts @@ -30,6 +30,7 @@ export type Options = { stringEnums: boolean; constEnums: boolean; outputClientImpl: boolean | 'grpc-web'; + outputServices: false | 'grpc-js'; addGrpcMetadata: boolean; addNestjsRestParameter: boolean; returnObservable: boolean; @@ -60,6 +61,7 @@ export function defaultOptions(): Options { stringEnums: false, constEnums: false, outputClientImpl: true, + outputServices: false, returnObservable: false, addGrpcMetadata: false, addNestjsRestParameter: false, diff --git a/tests/options-test.ts b/tests/options-test.ts index 469145eac..eb1282ea8 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -21,6 +21,7 @@ describe('options', () => { "outputJsonMethods": true, "outputPartialMethods": false, "outputSchema": false, + "outputServices": false, "outputTypeRegistry": false, "returnObservable": false, "snakeToCamel": true, diff --git a/yarn.lock b/yarn.lock index fdeb39073..e14e11b6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -265,6 +265,15 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@grpc/grpc-js@^1.2.12": + version "1.2.12" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.2.12.tgz#0153f27512acf69184bb52c0a1035ca91d6c14b0" + integrity sha512-+gPCklP1eqIgrNPyzddYQdt9+GvZqPlLpIjIo+TveE+gbtp74VV1A2ju8ExeO8ma8f7MbpaGZx/KJPYVWL9eDw== + dependencies: + "@types/node" ">=12.12.47" + google-auth-library "^6.1.1" + semver "^6.2.0" + "@grpc/proto-loader@^0.5.4": version "0.5.4" resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.4.tgz#038a3820540f621eeb1b05d81fbedfb045e14de0" @@ -1037,6 +1046,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.5.tgz#3d03acd3b3414cf67faf999aed11682ed121f22b" integrity sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA== +"@types/node@>=12.12.47": + version "14.14.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" + integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== + "@types/node@^10.7.0": version "10.17.24" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.24.tgz#c57511e3a19c4b5e9692bb2995c40a3a52167944" @@ -1114,6 +1128,13 @@ abbrev@1, abbrev@~1.1.1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + acorn-globals@^4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" @@ -1333,6 +1354,11 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= +arrify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + asap@^2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -1463,6 +1489,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -1488,6 +1519,11 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.0.tgz#09c40d92e936c64777aa385c4e9b904f8147eaf0" integrity sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ== +bignumber.js@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" + integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== + bin-links@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-2.2.1.tgz#347d9dbb48f7d60e6c11fe68b77a424bee14d61b" @@ -1572,6 +1608,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -2297,6 +2338,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -2422,6 +2470,11 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + exec-sh@^0.3.2: version "0.3.4" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" @@ -2538,7 +2591,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2599,6 +2652,11 @@ fast-safe-stringify@2.0.7: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fast-text-encoding@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + fastq@^1.6.0: version "1.11.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" @@ -2760,6 +2818,25 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +gaxios@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.2.0.tgz#33bdc4fc241fc33b8915a4b8c07cfb368b932e46" + integrity sha512-Ms7fNifGv0XVU+6eIyL9LB7RVESeML9+cMvkwGS70xyD6w2Z80wl6RiqiJ9k1KFlJCUTQqFFc8tXmPQfSKUe8g== + dependencies: + abort-controller "^3.0.0" + extend "^3.0.2" + https-proxy-agent "^5.0.0" + is-stream "^2.0.0" + node-fetch "^2.3.0" + +gcp-metadata@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-4.2.1.tgz#31849fbcf9025ef34c2297c32a89a1e7e9f2cd62" + integrity sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw== + dependencies: + gaxios "^4.0.0" + json-bigint "^1.0.0" + gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" @@ -2854,6 +2931,28 @@ globby@^11.0.0, globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" +google-auth-library@^6.1.1: + version "6.1.6" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-6.1.6.tgz#deacdcdb883d9ed6bac78bb5d79a078877fdf572" + integrity sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ== + dependencies: + arrify "^2.0.0" + base64-js "^1.3.0" + ecdsa-sig-formatter "^1.0.11" + fast-text-encoding "^1.0.0" + gaxios "^4.0.0" + gcp-metadata "^4.2.0" + gtoken "^5.0.4" + jws "^4.0.0" + lru-cache "^6.0.0" + +google-p12-pem@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e" + integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA== + dependencies: + node-forge "^0.10.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.3, graceful-fs@^4.2.6: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" @@ -2881,6 +2980,15 @@ grpc@^1.24.4: node-pre-gyp "^0.16.0" protobufjs "^5.0.3" +gtoken@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.2.1.tgz#4dae1fea17270f457954b4a45234bba5fc796d16" + integrity sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw== + dependencies: + gaxios "^4.0.0" + google-p12-pem "^3.0.3" + jws "^4.0.0" + handlebars@^4.7.6: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -3909,6 +4017,13 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -3985,6 +4100,23 @@ just-diff@^3.0.1: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-3.0.2.tgz#65f4914e4d7500b364d12b7b3f03bcbacdac743b" integrity sha512-+EiNvacECnZbszZa5IMjzrJ3dy2HKMXyGaNYWBnXy+iWW+437jIvQUrWaM9M+XI/6gOH8EjqvhGUOSh7ETekyg== +jwa@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" + integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" + integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== + dependencies: + jwa "^2.0.0" + safe-buffer "^5.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -4657,6 +4789,11 @@ node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + node-gyp@^7.1.0, node-gyp@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" @@ -5877,7 +6014,7 @@ semver-regex@^3.1.2: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.x, semver@^6.0.0, semver@^6.3.0: +semver@6.x, semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==