@restatedev/restate-sdk 0.7.3-worker → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +29 -51
- package/dist/clients/workflow_client.d.ts +77 -0
- package/dist/clients/workflow_client.d.ts.map +1 -0
- package/dist/clients/workflow_client.js +172 -0
- package/dist/clients/workflow_client.js.map +1 -0
- package/dist/connection/buffered_connection.js +44 -0
- package/dist/connection/buffered_connection.js.map +1 -0
- package/dist/connection/connection.js +13 -0
- package/dist/connection/connection.js.map +1 -0
- package/dist/connection/embedded_connection.js +59 -0
- package/dist/connection/embedded_connection.js.map +1 -0
- package/dist/connection/http_connection.js +203 -0
- package/dist/connection/http_connection.js.map +1 -0
- package/dist/connection/lambda_connection.js +58 -0
- package/dist/connection/lambda_connection.js.map +1 -0
- package/dist/{restate_context.d.ts → context.d.ts} +239 -170
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +113 -0
- package/dist/context.js.map +1 -0
- package/dist/{restate_context_impl.d.ts → context_impl.d.ts} +26 -30
- package/dist/context_impl.d.ts.map +1 -0
- package/dist/context_impl.js +439 -0
- package/dist/context_impl.js.map +1 -0
- package/dist/embedded/api.d.ts +2 -2
- package/dist/embedded/api.d.ts.map +1 -1
- package/dist/embedded/api.js +35 -0
- package/dist/embedded/api.js.map +1 -0
- package/dist/embedded/handler.d.ts +2 -2
- package/dist/embedded/handler.d.ts.map +1 -1
- package/dist/embedded/handler.js +26 -0
- package/dist/embedded/handler.js.map +1 -0
- package/dist/embedded/http2_remote.js +91 -0
- package/dist/embedded/http2_remote.js.map +1 -0
- package/dist/embedded/invocation.d.ts.map +1 -1
- package/dist/embedded/invocation.js +94 -0
- package/dist/embedded/invocation.js.map +1 -0
- package/dist/endpoint/endpoint_impl.d.ts +35 -0
- package/dist/endpoint/endpoint_impl.d.ts.map +1 -0
- package/dist/endpoint/endpoint_impl.js +405 -0
- package/dist/endpoint/endpoint_impl.js.map +1 -0
- package/dist/endpoint/http2_handler.d.ts +11 -0
- package/dist/endpoint/http2_handler.d.ts.map +1 -0
- package/dist/endpoint/http2_handler.js +119 -0
- package/dist/endpoint/http2_handler.js.map +1 -0
- package/dist/endpoint/lambda_handler.d.ts +15 -0
- package/dist/endpoint/lambda_handler.d.ts.map +1 -0
- package/dist/endpoint/lambda_handler.js +144 -0
- package/dist/endpoint/lambda_handler.js.map +1 -0
- package/dist/endpoint.d.ts +161 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/endpoint.js +22 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/generated/dev/restate/events.js +371 -0
- package/dist/generated/dev/restate/events.js.map +1 -0
- package/dist/generated/dev/restate/ext.js +215 -0
- package/dist/generated/dev/restate/ext.js.map +1 -0
- package/dist/generated/google/protobuf/descriptor.js +6676 -0
- package/dist/generated/google/protobuf/descriptor.js.map +1 -0
- package/dist/generated/google/protobuf/empty.js +107 -0
- package/dist/generated/google/protobuf/empty.js.map +1 -0
- package/dist/generated/google/protobuf/struct.js +754 -0
- package/dist/generated/google/protobuf/struct.js.map +1 -0
- package/dist/generated/proto/discovery.js +364 -0
- package/dist/generated/proto/discovery.js.map +1 -0
- package/dist/generated/proto/dynrpc.js +668 -0
- package/dist/generated/proto/dynrpc.js.map +1 -0
- package/dist/generated/proto/javascript.d.ts +13 -0
- package/dist/generated/proto/javascript.d.ts.map +1 -1
- package/dist/generated/proto/javascript.js +416 -0
- package/dist/generated/proto/javascript.js.map +1 -0
- package/dist/generated/proto/protocol.d.ts +43 -0
- package/dist/generated/proto/protocol.d.ts.map +1 -1
- package/dist/generated/proto/protocol.js +2641 -0
- package/dist/generated/proto/protocol.js.map +1 -0
- package/dist/generated/proto/services.js +1535 -0
- package/dist/generated/proto/services.js.map +1 -0
- package/dist/generated/proto/test.js +321 -0
- package/dist/generated/proto/test.js.map +1 -0
- package/dist/invocation.d.ts +4 -1
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +157 -0
- package/dist/invocation.js.map +1 -0
- package/dist/io/decoder.d.ts +1 -0
- package/dist/io/decoder.d.ts.map +1 -1
- package/dist/io/decoder.js +140 -0
- package/dist/io/decoder.js.map +1 -0
- package/dist/io/encoder.d.ts +1 -2
- package/dist/io/encoder.d.ts.map +1 -1
- package/dist/io/encoder.js +68 -0
- package/dist/io/encoder.js.map +1 -0
- package/dist/journal.d.ts +13 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +405 -0
- package/dist/journal.js.map +1 -0
- package/dist/local_state_store.d.ts +5 -3
- package/dist/local_state_store.d.ts.map +1 -1
- package/dist/local_state_store.js +82 -0
- package/dist/local_state_store.js.map +1 -0
- package/dist/logger.d.ts +19 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +90 -0
- package/dist/logger.js.map +1 -0
- package/dist/promise_combinator_tracker.d.ts +29 -0
- package/dist/promise_combinator_tracker.d.ts.map +1 -0
- package/dist/promise_combinator_tracker.js +128 -0
- package/dist/promise_combinator_tracker.js.map +1 -0
- package/dist/public_api.d.ts +5 -5
- package/dist/public_api.d.ts.map +1 -1
- package/dist/public_api.js +60 -0
- package/dist/public_api.js.map +1 -0
- package/dist/state_machine.d.ts +19 -12
- package/dist/state_machine.d.ts.map +1 -1
- package/dist/state_machine.js +437 -0
- package/dist/state_machine.js.map +1 -0
- package/dist/types/errors.d.ts +12 -3
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +273 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/grpc.d.ts +6 -4
- package/dist/types/grpc.d.ts.map +1 -1
- package/dist/types/grpc.js +81 -0
- package/dist/types/grpc.js.map +1 -0
- package/dist/types/protocol.d.ts +9 -5
- package/dist/types/protocol.d.ts.map +1 -1
- package/dist/types/protocol.js +147 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/types/router.d.ts +8 -8
- package/dist/types/router.d.ts.map +1 -1
- package/dist/types/router.js +36 -0
- package/dist/types/router.js.map +1 -0
- package/dist/types/types.d.ts +1 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +138 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/{assumpsions.d.ts → assumptions.d.ts} +1 -1
- package/dist/utils/{assumpsions.d.ts.map → assumptions.d.ts.map} +1 -1
- package/dist/utils/assumptions.js +101 -0
- package/dist/utils/assumptions.js.map +1 -0
- package/dist/utils/message_logger.d.ts +28 -0
- package/dist/utils/message_logger.d.ts.map +1 -0
- package/dist/utils/message_logger.js +88 -0
- package/dist/utils/message_logger.js.map +1 -0
- package/dist/utils/promises.d.ts +15 -0
- package/dist/utils/promises.d.ts.map +1 -0
- package/dist/utils/promises.js +67 -0
- package/dist/utils/promises.js.map +1 -0
- package/dist/utils/public_utils.js +49 -0
- package/dist/utils/public_utils.js.map +1 -0
- package/dist/utils/rand.d.ts +1 -1
- package/dist/utils/rand.d.ts.map +1 -1
- package/dist/utils/rand.js +114 -0
- package/dist/utils/rand.js.map +1 -0
- package/dist/utils/utils.d.ts +1 -10
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +122 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/workflows/workflow.d.ts +101 -0
- package/dist/workflows/workflow.d.ts.map +1 -0
- package/dist/workflows/workflow.js +80 -0
- package/dist/workflows/workflow.js.map +1 -0
- package/dist/workflows/workflow_state_service.d.ts +35 -0
- package/dist/workflows/workflow_state_service.d.ts.map +1 -0
- package/dist/workflows/workflow_state_service.js +201 -0
- package/dist/workflows/workflow_state_service.js.map +1 -0
- package/dist/workflows/workflow_wrapper_service.d.ts +10 -0
- package/dist/workflows/workflow_wrapper_service.d.ts.map +1 -0
- package/dist/workflows/workflow_wrapper_service.js +264 -0
- package/dist/workflows/workflow_wrapper_service.js.map +1 -0
- package/package.json +38 -39
- package/src/clients/workflow_client.ts +290 -0
- package/src/connection/buffered_connection.ts +47 -0
- package/src/connection/connection.ts +34 -0
- package/src/connection/embedded_connection.ts +62 -0
- package/src/connection/http_connection.ts +228 -0
- package/src/connection/lambda_connection.ts +69 -0
- package/src/context.ts +633 -0
- package/src/context_impl.ts +721 -0
- package/src/embedded/api.ts +57 -0
- package/src/embedded/handler.ts +36 -0
- package/src/embedded/http2_remote.ts +103 -0
- package/src/embedded/invocation.ts +126 -0
- package/src/endpoint/endpoint_impl.ts +623 -0
- package/src/endpoint/http2_handler.ts +151 -0
- package/src/endpoint/lambda_handler.ts +181 -0
- package/src/endpoint.ts +187 -0
- package/src/generated/dev/restate/events.ts +430 -0
- package/src/generated/dev/restate/ext.ts +238 -0
- package/src/generated/google/protobuf/descriptor.ts +7889 -0
- package/src/generated/google/protobuf/empty.ts +150 -0
- package/src/generated/google/protobuf/struct.ts +878 -0
- package/src/generated/proto/discovery.ts +423 -0
- package/src/generated/proto/dynrpc.ts +768 -0
- package/src/generated/proto/javascript.ts +488 -0
- package/src/generated/proto/protocol.ts +3091 -0
- package/src/generated/proto/services.ts +1834 -0
- package/src/generated/proto/test.ts +387 -0
- package/src/invocation.ts +212 -0
- package/src/io/decoder.ts +171 -0
- package/src/io/encoder.ts +72 -0
- package/src/journal.ts +537 -0
- package/src/local_state_store.ts +94 -0
- package/src/logger.ts +121 -0
- package/src/promise_combinator_tracker.ts +191 -0
- package/src/public_api.ts +53 -0
- package/src/state_machine.ts +635 -0
- package/src/types/errors.ts +297 -0
- package/src/types/grpc.ts +97 -0
- package/src/types/protocol.ts +201 -0
- package/src/types/router.ts +118 -0
- package/src/types/types.ts +160 -0
- package/src/utils/assumptions.ts +131 -0
- package/src/utils/message_logger.ts +112 -0
- package/src/utils/promises.ts +118 -0
- package/src/utils/public_utils.ts +91 -0
- package/src/utils/rand.ts +142 -0
- package/src/utils/utils.ts +178 -0
- package/src/workflows/workflow.ts +178 -0
- package/src/workflows/workflow_state_service.ts +299 -0
- package/src/workflows/workflow_wrapper_service.ts +314 -0
- package/dist/cloudflare_bundle.js +0 -27387
- package/dist/restate_context.d.ts.map +0 -1
- package/dist/restate_context_impl.d.ts.map +0 -1
- package/dist/server/base_restate_server.d.ts +0 -32
- package/dist/server/base_restate_server.d.ts.map +0 -1
- package/dist/server/restate_lambda_handler.d.ts +0 -104
- package/dist/server/restate_lambda_handler.d.ts.map +0 -1
- package/dist/server/restate_server.d.ts +0 -97
- package/dist/server/restate_server.d.ts.map +0 -1
- package/dist/utils/logger.d.ts +0 -60
- package/dist/utils/logger.d.ts.map +0 -1
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH
|
|
3
|
+
*
|
|
4
|
+
* This file is part of the Restate SDK for Node.js/TypeScript,
|
|
5
|
+
* which is released under the MIT license.
|
|
6
|
+
*
|
|
7
|
+
* You can find a copy of the license in file LICENSE in the root
|
|
8
|
+
* directory of this repository or package, or at
|
|
9
|
+
* https://github.com/restatedev/sdk-typescript/blob/main/LICENSE
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
GrpcService,
|
|
16
|
+
GrpcServiceMethod,
|
|
17
|
+
HostedGrpcServiceMethod,
|
|
18
|
+
ProtoMetadata,
|
|
19
|
+
} from "../types/grpc";
|
|
20
|
+
import {
|
|
21
|
+
DeepPartial,
|
|
22
|
+
ServiceDiscoveryResponse,
|
|
23
|
+
} from "../generated/proto/discovery";
|
|
24
|
+
import { Event } from "../types/types";
|
|
25
|
+
import {
|
|
26
|
+
FileDescriptorProto,
|
|
27
|
+
UninterpretedOption,
|
|
28
|
+
} from "../generated/google/protobuf/descriptor";
|
|
29
|
+
import { Empty } from "../generated/google/protobuf/empty";
|
|
30
|
+
import {
|
|
31
|
+
FileDescriptorProto as FileDescriptorProto1,
|
|
32
|
+
ServiceDescriptorProto as ServiceDescriptorProto1,
|
|
33
|
+
MethodDescriptorProto as MethodDescriptorProto1,
|
|
34
|
+
} from "ts-proto-descriptors";
|
|
35
|
+
import {
|
|
36
|
+
fieldTypeToJSON,
|
|
37
|
+
ServiceType,
|
|
38
|
+
serviceTypeToJSON,
|
|
39
|
+
} from "../generated/dev/restate/ext";
|
|
40
|
+
import {
|
|
41
|
+
RpcRequest,
|
|
42
|
+
RpcResponse,
|
|
43
|
+
ProtoMetadata as RpcServiceProtoMetadata,
|
|
44
|
+
protoMetadata as rpcServiceProtoMetadata,
|
|
45
|
+
KeyedEvent,
|
|
46
|
+
} from "../generated/proto/dynrpc";
|
|
47
|
+
import { Context, KeyedContext, useContext, useKeyedContext } from "../context";
|
|
48
|
+
import { verifyAssumptions } from "../utils/assumptions";
|
|
49
|
+
import { RestateEndpoint, ServiceBundle, TerminalError } from "../public_api";
|
|
50
|
+
import { KeyedRouter, UnKeyedRouter, isEventHandler } from "../types/router";
|
|
51
|
+
import { jsonSafeAny } from "../utils/utils";
|
|
52
|
+
import { rlog } from "../logger";
|
|
53
|
+
import { ServiceOpts } from "../endpoint";
|
|
54
|
+
import http2, { Http2ServerRequest, Http2ServerResponse } from "http2";
|
|
55
|
+
import { Http2Handler } from "./http2_handler";
|
|
56
|
+
import { LambdaHandler } from "./lambda_handler";
|
|
57
|
+
|
|
58
|
+
export class EndpointImpl implements RestateEndpoint {
|
|
59
|
+
protected readonly methods: Record<
|
|
60
|
+
string,
|
|
61
|
+
HostedGrpcServiceMethod<unknown, unknown>
|
|
62
|
+
> = {};
|
|
63
|
+
readonly discovery: DeepPartial<ServiceDiscoveryResponse>;
|
|
64
|
+
protected readonly dynrpcDescriptor: RpcServiceProtoMetadata;
|
|
65
|
+
|
|
66
|
+
public constructor() {
|
|
67
|
+
this.discovery = {
|
|
68
|
+
files: { file: [] },
|
|
69
|
+
services: [],
|
|
70
|
+
minProtocolVersion: 0,
|
|
71
|
+
maxProtocolVersion: 0,
|
|
72
|
+
};
|
|
73
|
+
this.dynrpcDescriptor = copyProtoMetadata(rpcServiceProtoMetadata);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
bindService({ descriptor, service, instance }: ServiceOpts): RestateEndpoint {
|
|
77
|
+
const spec = parseService(descriptor, service, instance);
|
|
78
|
+
this.addDescriptor(descriptor);
|
|
79
|
+
|
|
80
|
+
const qname =
|
|
81
|
+
spec.packge === "" ? spec.name : `${spec.packge}.${spec.name}`;
|
|
82
|
+
|
|
83
|
+
this.discovery.services?.push(qname);
|
|
84
|
+
for (const method of spec.methods) {
|
|
85
|
+
const url = `/invoke/${qname}/${method.name}`;
|
|
86
|
+
this.methods[url] = new HostedGrpcServiceMethod(
|
|
87
|
+
instance,
|
|
88
|
+
spec.packge,
|
|
89
|
+
service,
|
|
90
|
+
method
|
|
91
|
+
);
|
|
92
|
+
// note that this log will not print all the keys.
|
|
93
|
+
rlog.info(`Binding: ${url} -> ${JSON.stringify(method, null, "\t")}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public bindRouter<M>(
|
|
100
|
+
path: string,
|
|
101
|
+
router: UnKeyedRouter<M>
|
|
102
|
+
): RestateEndpoint {
|
|
103
|
+
this.bindRpcService(path, router, false);
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public bindKeyedRouter<M>(
|
|
108
|
+
path: string,
|
|
109
|
+
router: KeyedRouter<M>
|
|
110
|
+
): RestateEndpoint {
|
|
111
|
+
this.bindRpcService(path, router, true);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public bind(services: ServiceBundle): RestateEndpoint {
|
|
116
|
+
services.registerServices(this);
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
http2Handler(): (
|
|
121
|
+
request: Http2ServerRequest,
|
|
122
|
+
response: Http2ServerResponse
|
|
123
|
+
) => void {
|
|
124
|
+
const handler = new Http2Handler(this);
|
|
125
|
+
return handler.acceptConnection.bind(handler);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
lambdaHandler(): (event: any, ctx: any) => Promise<any> {
|
|
130
|
+
const handler = new LambdaHandler(this);
|
|
131
|
+
return handler.handleRequest.bind(handler);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
listen(port?: number): Promise<void> {
|
|
135
|
+
const actualPort = port ?? parseInt(process.env.PORT ?? "9080");
|
|
136
|
+
rlog.info(`Listening on ${actualPort}...`);
|
|
137
|
+
|
|
138
|
+
const server = http2.createServer(this.http2Handler());
|
|
139
|
+
server.listen(actualPort);
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
141
|
+
return new Promise(() => {});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Private methods to build the endpoint
|
|
145
|
+
|
|
146
|
+
private addDescriptor(descriptor: ProtoMetadata) {
|
|
147
|
+
const desc = FileDescriptorProto.fromPartial(descriptor.fileDescriptor);
|
|
148
|
+
|
|
149
|
+
// extract out service options and put into the fileDescriptor
|
|
150
|
+
for (const name in descriptor.options?.services) {
|
|
151
|
+
if (
|
|
152
|
+
descriptor.options?.services[name]?.options?.service_type !== undefined
|
|
153
|
+
) {
|
|
154
|
+
desc.service
|
|
155
|
+
.find((desc) => desc.name === name)
|
|
156
|
+
?.options?.uninterpretedOption.push(
|
|
157
|
+
UninterpretedOption.fromPartial({
|
|
158
|
+
name: [
|
|
159
|
+
{ namePart: "dev.restate.ext.service_type", isExtension: true },
|
|
160
|
+
],
|
|
161
|
+
identifierValue: serviceTypeToJSON(
|
|
162
|
+
descriptor.options?.services[name]?.options?.service_type
|
|
163
|
+
),
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// extract out field options and put into the fileDescriptor
|
|
170
|
+
for (const messageName in descriptor.options?.messages) {
|
|
171
|
+
for (const fieldName in descriptor.options?.messages[messageName]
|
|
172
|
+
?.fields) {
|
|
173
|
+
const fields = descriptor.options?.messages[messageName]?.fields || {};
|
|
174
|
+
if (fields[fieldName]["field"] !== undefined) {
|
|
175
|
+
desc.messageType
|
|
176
|
+
.find((desc) => desc.name === messageName)
|
|
177
|
+
?.field?.find((desc) => desc.name === fieldName)
|
|
178
|
+
?.options?.uninterpretedOption.push(
|
|
179
|
+
UninterpretedOption.fromPartial({
|
|
180
|
+
name: [
|
|
181
|
+
{ namePart: "dev.restate.ext.field", isExtension: true },
|
|
182
|
+
],
|
|
183
|
+
identifierValue: fieldTypeToJSON(fields[fieldName]["field"]),
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
this.discovery.files?.file?.filter(
|
|
192
|
+
(haveDesc) => desc.name === haveDesc.name
|
|
193
|
+
).length === 0
|
|
194
|
+
) {
|
|
195
|
+
this.discovery.files?.file.push(desc);
|
|
196
|
+
}
|
|
197
|
+
descriptor.dependencies?.forEach((dep) => {
|
|
198
|
+
this.addDescriptor(dep);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private rpcHandler(
|
|
203
|
+
keyed: boolean,
|
|
204
|
+
route: string,
|
|
205
|
+
handler: Function
|
|
206
|
+
): {
|
|
207
|
+
descriptor: MethodDescriptorProto1;
|
|
208
|
+
method: GrpcServiceMethod<unknown, unknown>;
|
|
209
|
+
} {
|
|
210
|
+
const descriptor = createRpcMethodDescriptor(route);
|
|
211
|
+
|
|
212
|
+
const localMethod = (instance: unknown, input: RpcRequest) => {
|
|
213
|
+
if (keyed) {
|
|
214
|
+
return dispatchKeyedRpcHandler(
|
|
215
|
+
useKeyedContext(instance),
|
|
216
|
+
input,
|
|
217
|
+
handler
|
|
218
|
+
);
|
|
219
|
+
} else {
|
|
220
|
+
return dispatchUnkeyedRpcHandler(useContext(instance), input, handler);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const decoder = RpcRequest.decode;
|
|
225
|
+
const encoder = (message: RpcResponse) =>
|
|
226
|
+
RpcResponse.encode({
|
|
227
|
+
response: jsonSafeAny("", message.response),
|
|
228
|
+
}).finish();
|
|
229
|
+
|
|
230
|
+
const method = new GrpcServiceMethod<RpcRequest, RpcResponse>(
|
|
231
|
+
route,
|
|
232
|
+
route,
|
|
233
|
+
keyed,
|
|
234
|
+
localMethod,
|
|
235
|
+
decoder,
|
|
236
|
+
encoder
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
descriptor: descriptor,
|
|
241
|
+
method: method as GrpcServiceMethod<unknown, unknown>,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
stringKeyedEventHandler(
|
|
246
|
+
keyed: boolean,
|
|
247
|
+
route: string,
|
|
248
|
+
handler: Function
|
|
249
|
+
): {
|
|
250
|
+
descriptor: MethodDescriptorProto1;
|
|
251
|
+
method: GrpcServiceMethod<unknown, unknown>;
|
|
252
|
+
} {
|
|
253
|
+
if (!keyed) {
|
|
254
|
+
// TODO: support unkeyed rpc event handler
|
|
255
|
+
throw new TerminalError("Unkeyed Event handlers are not yet supported.");
|
|
256
|
+
}
|
|
257
|
+
const descriptor = createStringKeyedMethodDescriptor(route);
|
|
258
|
+
const localMethod = (instance: unknown, input: KeyedEvent) => {
|
|
259
|
+
return dispatchKeyedEventHandler(
|
|
260
|
+
useKeyedContext(instance),
|
|
261
|
+
input,
|
|
262
|
+
handler
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const decoder = KeyedEvent.decode;
|
|
267
|
+
const encoder = (message: Empty) => Empty.encode(message).finish();
|
|
268
|
+
|
|
269
|
+
const method = new GrpcServiceMethod<KeyedEvent, Empty>(
|
|
270
|
+
route,
|
|
271
|
+
route,
|
|
272
|
+
keyed,
|
|
273
|
+
localMethod,
|
|
274
|
+
decoder,
|
|
275
|
+
encoder
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
descriptor,
|
|
280
|
+
method: method as GrpcServiceMethod<unknown, unknown>,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private bindRpcService(name: string, router: RpcRouter, keyed: boolean) {
|
|
285
|
+
if (name === undefined || router === undefined || keyed === undefined) {
|
|
286
|
+
throw new Error("incomplete arguments: (name, router, keyed)");
|
|
287
|
+
}
|
|
288
|
+
if (!(typeof name === "string") || name.length === 0) {
|
|
289
|
+
throw new Error("service name must be a non-empty string");
|
|
290
|
+
}
|
|
291
|
+
if (name.indexOf("/") !== -1) {
|
|
292
|
+
throw new Error("service name must not contain any slash '/'");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const lastDot = name.indexOf(".");
|
|
296
|
+
const serviceName = lastDot === -1 ? name : name.substring(lastDot + 1);
|
|
297
|
+
const servicePackage = name.substring(
|
|
298
|
+
0,
|
|
299
|
+
name.length - serviceName.length - 1
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const desc = this.dynrpcDescriptor;
|
|
303
|
+
const serviceGrpcSpec = keyed
|
|
304
|
+
? pushKeyedService(desc, name)
|
|
305
|
+
: pushUnKeyedService(desc, name);
|
|
306
|
+
|
|
307
|
+
for (const [route, handler] of Object.entries(router)) {
|
|
308
|
+
let registration: {
|
|
309
|
+
descriptor: MethodDescriptorProto1;
|
|
310
|
+
method: GrpcServiceMethod<unknown, unknown>;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (isEventHandler(handler)) {
|
|
314
|
+
const theHandler = handler.handler;
|
|
315
|
+
registration = this.stringKeyedEventHandler(keyed, route, theHandler);
|
|
316
|
+
} else {
|
|
317
|
+
registration = this.rpcHandler(keyed, route, handler);
|
|
318
|
+
}
|
|
319
|
+
serviceGrpcSpec.method.push(registration.descriptor);
|
|
320
|
+
const url = `/invoke/${name}/${route}`;
|
|
321
|
+
this.methods[url] = new HostedGrpcServiceMethod(
|
|
322
|
+
{}, // we don't actually execute on any class instance
|
|
323
|
+
servicePackage,
|
|
324
|
+
serviceName,
|
|
325
|
+
registration.method
|
|
326
|
+
) as HostedGrpcServiceMethod<unknown, unknown>;
|
|
327
|
+
|
|
328
|
+
rlog.info(
|
|
329
|
+
`Binding: ${url} -> ${JSON.stringify(registration.method, null, "\t")}`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// since we modified this descriptor, we need to remove it in case it was added before,
|
|
334
|
+
// so that the modified version is processed and added again
|
|
335
|
+
const filteredFiles = this.discovery.files?.file?.filter(
|
|
336
|
+
(haveDesc) => desc.fileDescriptor.name !== haveDesc.name
|
|
337
|
+
);
|
|
338
|
+
if (this.discovery.files !== undefined && filteredFiles !== undefined) {
|
|
339
|
+
this.discovery.files.file = filteredFiles;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.addDescriptor(desc);
|
|
343
|
+
this.discovery.services?.push(name);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
methodByUrl<I, O>(
|
|
347
|
+
url: string | undefined | null
|
|
348
|
+
): HostedGrpcServiceMethod<I, O> | undefined {
|
|
349
|
+
if (url == undefined || url === null) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
return this.methods[url] as HostedGrpcServiceMethod<I, O>;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
357
|
+
function indexProperties(instance: any): Map<string, string> {
|
|
358
|
+
const names = new Map<string, string>();
|
|
359
|
+
while (
|
|
360
|
+
instance !== null &&
|
|
361
|
+
instance !== undefined &&
|
|
362
|
+
instance !== Object.prototype
|
|
363
|
+
) {
|
|
364
|
+
for (const property of Object.getOwnPropertyNames(instance)) {
|
|
365
|
+
names.set(property.toLowerCase(), property);
|
|
366
|
+
}
|
|
367
|
+
instance = Object.getPrototypeOf(instance);
|
|
368
|
+
}
|
|
369
|
+
return names;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Given:
|
|
373
|
+
// * an instance of a class that implements a gRPC TypeScript interface,
|
|
374
|
+
// as generated by our protoc plugin, this method
|
|
375
|
+
// * The ProtobufFileDescriptor as generated by the protobuf plugin
|
|
376
|
+
// * and the gRPC service name
|
|
377
|
+
//
|
|
378
|
+
// Return a GrpcService definition, as defined above.
|
|
379
|
+
//
|
|
380
|
+
// For example (see first: example.proto and example.ts):
|
|
381
|
+
//
|
|
382
|
+
// > parse(example.protoMetaData, "Greeter", new GreeterService())
|
|
383
|
+
//
|
|
384
|
+
// produces ~
|
|
385
|
+
//
|
|
386
|
+
// serviceName: 'Greeter',
|
|
387
|
+
// instance: GreeterService {},
|
|
388
|
+
// methods: {
|
|
389
|
+
// multiword: {
|
|
390
|
+
// localName: 'multiWord',
|
|
391
|
+
// fn: [Function: multiWord],
|
|
392
|
+
// inputType: [Object],
|
|
393
|
+
// outputType: [Object]
|
|
394
|
+
// },
|
|
395
|
+
// greet: {
|
|
396
|
+
// localName: 'greet',
|
|
397
|
+
// fn: [Function: greet],
|
|
398
|
+
// inputType: [Object],
|
|
399
|
+
// outputType: [Object]
|
|
400
|
+
// }
|
|
401
|
+
// }
|
|
402
|
+
//}
|
|
403
|
+
//
|
|
404
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
405
|
+
export function parseService(
|
|
406
|
+
meta: ProtoMetadata,
|
|
407
|
+
serviceName: string,
|
|
408
|
+
instance: any
|
|
409
|
+
) {
|
|
410
|
+
const svcMethods: Array<GrpcServiceMethod<unknown, unknown>> = [];
|
|
411
|
+
|
|
412
|
+
const service_type =
|
|
413
|
+
meta.options?.services?.[serviceName].options?.["service_type"];
|
|
414
|
+
const keyed = service_type !== ServiceType.UNKEYED;
|
|
415
|
+
|
|
416
|
+
// index all the existing properties that `instance` has.
|
|
417
|
+
// we index them by the lower case represention.
|
|
418
|
+
const names = indexProperties(instance);
|
|
419
|
+
for (const serviceDescriptor of meta.fileDescriptor.service) {
|
|
420
|
+
if (serviceName !== serviceDescriptor.name) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
for (const methodDescriptor of serviceDescriptor.method) {
|
|
424
|
+
const lowercaseName = methodDescriptor.name.toLowerCase();
|
|
425
|
+
const localName = names.get(lowercaseName);
|
|
426
|
+
if (localName === undefined || localName === null) {
|
|
427
|
+
throw new Error(`unimplemented method ${methodDescriptor.name}`);
|
|
428
|
+
}
|
|
429
|
+
const fn = instance[localName];
|
|
430
|
+
if (typeof fn !== "function") {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`A property ${localName} exists, which coresponds to a gRPC service named ${methodDescriptor.name}, but that property is not a function.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const localMethod = async (instance: unknown, input: unknown) => {
|
|
436
|
+
return await fn.call(instance, input);
|
|
437
|
+
};
|
|
438
|
+
let inputMessage = meta.references[methodDescriptor.inputType];
|
|
439
|
+
// If the input message type is not defined by the proto files of the service but by a dependency (e.g. BoolValue, Empty, etc)
|
|
440
|
+
// then we need to look for the encoders and decoders in the dependencies.
|
|
441
|
+
if (inputMessage === undefined) {
|
|
442
|
+
meta.dependencies?.forEach((dep) => {
|
|
443
|
+
if (dep.references[methodDescriptor.inputType] !== undefined) {
|
|
444
|
+
inputMessage = dep.references[methodDescriptor.inputType];
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
let outputMessage = meta.references[methodDescriptor.outputType];
|
|
449
|
+
// If the output message type is not defined by use but by the proto files of the service (e.g. BoolValue, Empty, etc)
|
|
450
|
+
// then we need to look for the encoders and decoders in the dependencies.
|
|
451
|
+
if (outputMessage === undefined) {
|
|
452
|
+
meta.dependencies?.forEach((dep) => {
|
|
453
|
+
if (dep.references[methodDescriptor.outputType] !== undefined) {
|
|
454
|
+
outputMessage = dep.references[methodDescriptor.outputType];
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const decoder = (buffer: Uint8Array) => inputMessage.decode(buffer);
|
|
460
|
+
const encoder = (message: unknown) =>
|
|
461
|
+
outputMessage.encode(message).finish();
|
|
462
|
+
svcMethods.push(
|
|
463
|
+
new GrpcServiceMethod<unknown, unknown>(
|
|
464
|
+
methodDescriptor.name,
|
|
465
|
+
localName,
|
|
466
|
+
keyed,
|
|
467
|
+
localMethod,
|
|
468
|
+
decoder,
|
|
469
|
+
encoder
|
|
470
|
+
)
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return new GrpcService(
|
|
475
|
+
serviceName,
|
|
476
|
+
meta.fileDescriptor.package,
|
|
477
|
+
instance,
|
|
478
|
+
svcMethods
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
throw new Error(`Unable to find a service ${serviceName}.`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export type RpcRouter = {
|
|
485
|
+
[key: string]: Function;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
async function dispatchKeyedRpcHandler(
|
|
489
|
+
ctx: KeyedContext,
|
|
490
|
+
req: RpcRequest,
|
|
491
|
+
handler: Function
|
|
492
|
+
): Promise<RpcResponse> {
|
|
493
|
+
const { key, request } = verifyAssumptions(true, req);
|
|
494
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
495
|
+
// we throw a terminal error here, because this cannot be patched by updating code:
|
|
496
|
+
// if the request is wrong (missing a key), the request can never make it
|
|
497
|
+
throw new TerminalError(
|
|
498
|
+
"Keyed handlers must recieve a non null or empty string key"
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
const jsResult = (await handler(ctx, key, request)) as any;
|
|
502
|
+
return RpcResponse.create({ response: jsResult });
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function dispatchUnkeyedRpcHandler(
|
|
506
|
+
ctx: Context,
|
|
507
|
+
req: RpcRequest,
|
|
508
|
+
handler: Function
|
|
509
|
+
): Promise<RpcResponse> {
|
|
510
|
+
const { request } = verifyAssumptions(false, req);
|
|
511
|
+
const result = await handler(ctx, request);
|
|
512
|
+
return RpcResponse.create({ response: result });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function dispatchKeyedEventHandler(
|
|
516
|
+
ctx: KeyedContext,
|
|
517
|
+
req: KeyedEvent,
|
|
518
|
+
handler: Function
|
|
519
|
+
): Promise<Empty> {
|
|
520
|
+
const key = req.key;
|
|
521
|
+
if (key === null || key === undefined || key.length === 0) {
|
|
522
|
+
// we throw a terminal error here, because this cannot be patched by updating code:
|
|
523
|
+
// if the request is wrong (missing a key), the request can never make it
|
|
524
|
+
throw new TerminalError(
|
|
525
|
+
"Keyed handlers must receive a non null or empty string key"
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
const jsEvent = new Event(key, req.payload, req.attributes);
|
|
529
|
+
await handler(ctx, jsEvent);
|
|
530
|
+
return Empty.create({});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function copyProtoMetadata(
|
|
534
|
+
original: RpcServiceProtoMetadata
|
|
535
|
+
): RpcServiceProtoMetadata {
|
|
536
|
+
// duplicate the file descriptor. shallow, because we only need to
|
|
537
|
+
// change one top-level field: service[]
|
|
538
|
+
const fileDescriptorCopy = {
|
|
539
|
+
...original.fileDescriptor,
|
|
540
|
+
} as FileDescriptorProto1;
|
|
541
|
+
fileDescriptorCopy.service = [];
|
|
542
|
+
|
|
543
|
+
let options = original.options;
|
|
544
|
+
if (options !== undefined) {
|
|
545
|
+
options = {
|
|
546
|
+
...original.options,
|
|
547
|
+
};
|
|
548
|
+
options.services = {};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
fileDescriptor: fileDescriptorCopy,
|
|
553
|
+
references: original.references,
|
|
554
|
+
dependencies: original.dependencies,
|
|
555
|
+
options: options,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function pushKeyedService(
|
|
560
|
+
desc: RpcServiceProtoMetadata,
|
|
561
|
+
newName: string
|
|
562
|
+
): ServiceDescriptorProto1 {
|
|
563
|
+
const service = {
|
|
564
|
+
...rpcServiceProtoMetadata.fileDescriptor.service[0],
|
|
565
|
+
} as ServiceDescriptorProto1;
|
|
566
|
+
service.name = newName;
|
|
567
|
+
service.method = [];
|
|
568
|
+
desc.fileDescriptor.service.push(service);
|
|
569
|
+
|
|
570
|
+
const serviceOptions =
|
|
571
|
+
rpcServiceProtoMetadata.options?.services?.["RpcEndpoint"];
|
|
572
|
+
if (serviceOptions === undefined) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
"Missing service options in original RpcEndpoint proto descriptor"
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
578
|
+
desc.options!.services![newName] = serviceOptions;
|
|
579
|
+
|
|
580
|
+
return service;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function pushUnKeyedService(
|
|
584
|
+
desc: RpcServiceProtoMetadata,
|
|
585
|
+
newName: string
|
|
586
|
+
): ServiceDescriptorProto1 {
|
|
587
|
+
const service = {
|
|
588
|
+
...rpcServiceProtoMetadata.fileDescriptor.service[1],
|
|
589
|
+
} as ServiceDescriptorProto1;
|
|
590
|
+
service.name = newName;
|
|
591
|
+
service.method = [];
|
|
592
|
+
desc.fileDescriptor.service.push(service);
|
|
593
|
+
|
|
594
|
+
const serviceOptions =
|
|
595
|
+
rpcServiceProtoMetadata.options?.services?.["UnkeyedRpcEndpoint"];
|
|
596
|
+
if (serviceOptions === undefined) {
|
|
597
|
+
throw new Error(
|
|
598
|
+
"Missing service options in original UnkeyedRpcEndpoint proto descriptor"
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
602
|
+
desc.options!.services![newName] = serviceOptions;
|
|
603
|
+
|
|
604
|
+
return service;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function createRpcMethodDescriptor(methodName: string): MethodDescriptorProto1 {
|
|
608
|
+
const desc = {
|
|
609
|
+
...rpcServiceProtoMetadata.fileDescriptor.service[0].method[0],
|
|
610
|
+
} as MethodDescriptorProto1;
|
|
611
|
+
desc.name = methodName;
|
|
612
|
+
return desc;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function createStringKeyedMethodDescriptor(
|
|
616
|
+
methodName: string
|
|
617
|
+
): MethodDescriptorProto1 {
|
|
618
|
+
const desc = {
|
|
619
|
+
...rpcServiceProtoMetadata.fileDescriptor.service[0].method[1],
|
|
620
|
+
} as MethodDescriptorProto1;
|
|
621
|
+
desc.name = methodName;
|
|
622
|
+
return desc;
|
|
623
|
+
}
|