@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,290 @@
|
|
|
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
|
+
import * as restate from "../public_api";
|
|
13
|
+
import { ensureError } from "../types/errors";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A client to interact with running workflows.
|
|
17
|
+
*/
|
|
18
|
+
export interface WorkflowClient<R, U> {
|
|
19
|
+
/**
|
|
20
|
+
* Gets the ID of the workflow that this client talks to.
|
|
21
|
+
*/
|
|
22
|
+
workflowId(): string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Gets the status of the workflow, as a {@link restate.workflow.LifecycleStatus}.
|
|
26
|
+
* This will take on the values "NOT_STARTED", "RUNNING", "FINISHED", "FAILED".
|
|
27
|
+
*/
|
|
28
|
+
status(): Promise<restate.workflow.LifecycleStatus>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns a promise completed with the result. This will resolve successfully on successful
|
|
32
|
+
* termination of the workflow, and will be rejected if the workflow throws an Error.
|
|
33
|
+
*/
|
|
34
|
+
result(): Promise<R>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Gets the interface to the workflow through which all the workflow's additional methods
|
|
38
|
+
* can be called.
|
|
39
|
+
*
|
|
40
|
+
* To get the proper typed client, use the {@link WorkflowConnection.submitWorkflow} or
|
|
41
|
+
* {@link WorkflowConnection.connectToWorkflow} functions that accpet a typed ServiceApi
|
|
42
|
+
* object, as in the example below.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* In the workflow definition:
|
|
46
|
+
* ```
|
|
47
|
+
* const myWorkflow = restate.workflow.workflow("acme.myworkflow", { ... });
|
|
48
|
+
* export const myWorkflowApi = myworkflow.api;
|
|
49
|
+
* ```
|
|
50
|
+
* In the client code:
|
|
51
|
+
* ```
|
|
52
|
+
* import { myWorkflowApi } from "../server/myWorkflow"
|
|
53
|
+
* ...
|
|
54
|
+
* const restate = connectWorkflows("https://restatehost:8080");
|
|
55
|
+
* restate.submitWorkflow(myWorkflowApi, workflowId, args);
|
|
56
|
+
* restate.connectToWorkflow(myWorkflowApi, workflowId);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
workflowInterface(): restate.Client<restate.workflow.WorkflowClientApi<U>>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A connection to Restate that let's you submit workflows or connect to workflows.
|
|
64
|
+
* This is a typed client that internally makes HTTP calls to Restate to launch trigger
|
|
65
|
+
* an execution of a workflow service, or to connect to an existing execution.
|
|
66
|
+
*/
|
|
67
|
+
export interface RestateClient {
|
|
68
|
+
submitWorkflow<R, T>(
|
|
69
|
+
path: string,
|
|
70
|
+
workflowId: string,
|
|
71
|
+
params: T
|
|
72
|
+
): Promise<{
|
|
73
|
+
status: restate.workflow.WorkflowStartResult;
|
|
74
|
+
client: WorkflowClient<R, unknown>;
|
|
75
|
+
}>;
|
|
76
|
+
|
|
77
|
+
submitWorkflow<R, T, U>(
|
|
78
|
+
workflowApi: restate.ServiceApi<
|
|
79
|
+
restate.workflow.WorkflowRestateRpcApi<R, T, U>
|
|
80
|
+
>,
|
|
81
|
+
workflowId: string,
|
|
82
|
+
params: T
|
|
83
|
+
): Promise<{
|
|
84
|
+
status: restate.workflow.WorkflowStartResult;
|
|
85
|
+
client: WorkflowClient<R, U>;
|
|
86
|
+
}>;
|
|
87
|
+
|
|
88
|
+
connectToWorkflow<R = unknown>(
|
|
89
|
+
path: string,
|
|
90
|
+
workflowId: string
|
|
91
|
+
): Promise<{
|
|
92
|
+
status: restate.workflow.LifecycleStatus;
|
|
93
|
+
client: WorkflowClient<R, unknown>;
|
|
94
|
+
}>;
|
|
95
|
+
|
|
96
|
+
connectToWorkflow<R, T, U>(
|
|
97
|
+
workflowApi: restate.ServiceApi<
|
|
98
|
+
restate.workflow.WorkflowRestateRpcApi<R, T, U>
|
|
99
|
+
>,
|
|
100
|
+
workflowId: string
|
|
101
|
+
): Promise<{
|
|
102
|
+
status: restate.workflow.LifecycleStatus;
|
|
103
|
+
client: WorkflowClient<R, U>;
|
|
104
|
+
}>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a typed client to start and interact with workflow executions.
|
|
109
|
+
* The specifiec URI must point to the Restate request endpoint (ingress).
|
|
110
|
+
*
|
|
111
|
+
* This function doesn't immediately verify the connection, it will not fail
|
|
112
|
+
* if Restate is unreachable. Connection failures will only manifest when
|
|
113
|
+
* attempting to submit or connect a specific workflow.
|
|
114
|
+
*/
|
|
115
|
+
export function connect(restateUri: string): RestateClient {
|
|
116
|
+
return {
|
|
117
|
+
submitWorkflow: async <R, T, U>(
|
|
118
|
+
pathOrApi:
|
|
119
|
+
| string
|
|
120
|
+
| restate.ServiceApi<restate.workflow.WorkflowRestateRpcApi<R, T, U>>,
|
|
121
|
+
workflowId: string,
|
|
122
|
+
params: T
|
|
123
|
+
): Promise<{
|
|
124
|
+
status: restate.workflow.WorkflowStartResult;
|
|
125
|
+
client: WorkflowClient<R, U>;
|
|
126
|
+
}> => {
|
|
127
|
+
const path = typeof pathOrApi === "string" ? pathOrApi : pathOrApi.path;
|
|
128
|
+
|
|
129
|
+
let result: restate.workflow.WorkflowStartResult;
|
|
130
|
+
try {
|
|
131
|
+
result = await makeCall(restateUri, path, "submit", workflowId, params);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const error = ensureError(err);
|
|
134
|
+
throw new Error("Cannot start workflow: " + error.message, {
|
|
135
|
+
cause: error,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
status: result,
|
|
141
|
+
client: new WorkflowClientImpl(restateUri, path, workflowId),
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async connectToWorkflow<R, T, U>(
|
|
146
|
+
pathOrApi:
|
|
147
|
+
| string
|
|
148
|
+
| restate.ServiceApi<restate.workflow.WorkflowRestateRpcApi<R, T, U>>,
|
|
149
|
+
workflowId: string
|
|
150
|
+
): Promise<{
|
|
151
|
+
status: restate.workflow.LifecycleStatus;
|
|
152
|
+
client: WorkflowClient<R, U>;
|
|
153
|
+
}> {
|
|
154
|
+
const path = typeof pathOrApi === "string" ? pathOrApi : pathOrApi.path;
|
|
155
|
+
const client: WorkflowClient<R, U> = new WorkflowClientImpl(
|
|
156
|
+
restateUri,
|
|
157
|
+
path,
|
|
158
|
+
workflowId
|
|
159
|
+
);
|
|
160
|
+
const status = await client.status();
|
|
161
|
+
if (status === restate.workflow.LifecycleStatus.NOT_STARTED) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"No workflow running/finished/failed with ID " + workflowId
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
status,
|
|
168
|
+
client: new WorkflowClientImpl(restateUri, path, workflowId),
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
} satisfies RestateClient;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
class WorkflowClientImpl<R, U> implements WorkflowClient<R, U> {
|
|
175
|
+
constructor(
|
|
176
|
+
private readonly restateUri: string,
|
|
177
|
+
private readonly serviceName: string,
|
|
178
|
+
private readonly wfId: string
|
|
179
|
+
) {}
|
|
180
|
+
|
|
181
|
+
workflowId(): string {
|
|
182
|
+
return this.wfId;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
status(): Promise<restate.workflow.LifecycleStatus> {
|
|
186
|
+
return this.makeCall("status", {});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
result(): Promise<R> {
|
|
190
|
+
return this.makeCall("waitForResult", {});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
workflowInterface(): restate.Client<restate.workflow.WorkflowClientApi<U>> {
|
|
194
|
+
const clientProxy = new Proxy(
|
|
195
|
+
{},
|
|
196
|
+
{
|
|
197
|
+
get: (_target, prop) => {
|
|
198
|
+
const method = prop as string;
|
|
199
|
+
return async (args: unknown) => {
|
|
200
|
+
return this.makeCall(method, args);
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
return clientProxy as restate.Client<restate.workflow.WorkflowClientApi<U>>;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async makeCall<RR, TT>(method: string, args: TT): Promise<RR> {
|
|
210
|
+
return await makeCall(
|
|
211
|
+
this.restateUri,
|
|
212
|
+
this.serviceName,
|
|
213
|
+
method,
|
|
214
|
+
this.wfId,
|
|
215
|
+
args
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ----------------------------------------------------------------------------
|
|
221
|
+
// Utils
|
|
222
|
+
// ----------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
async function makeCall<R, T>(
|
|
225
|
+
restateUri: string,
|
|
226
|
+
serviceName: string,
|
|
227
|
+
method: string,
|
|
228
|
+
workflowId: string,
|
|
229
|
+
params: T
|
|
230
|
+
): Promise<R> {
|
|
231
|
+
if (!workflowId || typeof workflowId !== "string") {
|
|
232
|
+
throw new Error("missing workflowId");
|
|
233
|
+
}
|
|
234
|
+
if (params === undefined) {
|
|
235
|
+
params = {} as T;
|
|
236
|
+
}
|
|
237
|
+
if (typeof params !== "object") {
|
|
238
|
+
throw new Error("invalid parameters: must be an object");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const url = `${restateUri}/${serviceName}/${method}`;
|
|
242
|
+
const data = {
|
|
243
|
+
request: {
|
|
244
|
+
workflowId,
|
|
245
|
+
...params,
|
|
246
|
+
} satisfies restate.workflow.WorkflowRequest<T>,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
let body: string;
|
|
250
|
+
try {
|
|
251
|
+
body = JSON.stringify(data);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
throw new Error("Cannot encode request: " + err, { cause: err });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// eslint-disable-next-line no-console
|
|
257
|
+
console.debug(`Making call to Restate at ${url}`);
|
|
258
|
+
|
|
259
|
+
const httpResponse = await fetch(url, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers: {
|
|
262
|
+
"Content-Type": "application/json",
|
|
263
|
+
},
|
|
264
|
+
body,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const responseText = await httpResponse.text();
|
|
268
|
+
if (!httpResponse.ok) {
|
|
269
|
+
throw new Error(`Request failed: ${httpResponse.status}\n${responseText}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let response;
|
|
273
|
+
try {
|
|
274
|
+
response = JSON.parse(responseText);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
throw new Error("Cannot parse response JSON: " + err, { cause: err });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (response.error) {
|
|
280
|
+
throw new Error(response.error);
|
|
281
|
+
}
|
|
282
|
+
if (response.response) {
|
|
283
|
+
return response.response as R;
|
|
284
|
+
}
|
|
285
|
+
if (Object.keys(response).length === 0) {
|
|
286
|
+
return undefined as R;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
throw new Error("Unrecognized response object: " + responseText);
|
|
290
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { encodeMessages } from "../io/encoder";
|
|
2
|
+
import { Message } from "../types/types";
|
|
3
|
+
import { Connection } from "./connection";
|
|
4
|
+
|
|
5
|
+
export class BufferedConnection implements Connection {
|
|
6
|
+
private queue: Message[] = [];
|
|
7
|
+
private flushing: Promise<void> = Promise.resolve();
|
|
8
|
+
|
|
9
|
+
constructor(private readonly flushFn: (buffer: Buffer) => Promise<void>) {}
|
|
10
|
+
|
|
11
|
+
send(msg: Message): Promise<void> {
|
|
12
|
+
const len = this.queue.push(msg);
|
|
13
|
+
if (len === 1) {
|
|
14
|
+
// we are the first in line, therefore we schedule a flush,
|
|
15
|
+
// BUT we must wait for the previous flush to end.
|
|
16
|
+
this.flushing = this.flushing.then(() => this.scheduleFlush());
|
|
17
|
+
}
|
|
18
|
+
// we don't need to reschedule the `flush` here,
|
|
19
|
+
// because the flush happens anyway at the end of the current event loop iteration.
|
|
20
|
+
// tag along to the previously scheduled flush.
|
|
21
|
+
return this.flushing;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
end(): Promise<void> {
|
|
25
|
+
this.flushing = this.flushing.then(() => this.flush());
|
|
26
|
+
return this.flushing;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private scheduleFlush(): Promise<void> {
|
|
30
|
+
// schedule a flush at the end of the current event loop iteration.
|
|
31
|
+
return new Promise((resolve, reject) =>
|
|
32
|
+
setImmediate(() => {
|
|
33
|
+
this.flush().then(resolve).catch(reject);
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async flush(): Promise<void> {
|
|
39
|
+
if (this.queue.length === 0) {
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
const buffer = encodeMessages(this.queue) as Buffer;
|
|
43
|
+
this.queue = [];
|
|
44
|
+
|
|
45
|
+
return this.flushFn(buffer);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
import { Message } from "../types/types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A connection from the service/SDK to Restate.
|
|
16
|
+
* Accepts messages to be sent and committed to the journal.
|
|
17
|
+
*/
|
|
18
|
+
export interface Connection {
|
|
19
|
+
send(msg: Message): Promise<void>;
|
|
20
|
+
|
|
21
|
+
end(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A consumer of a message stream from Restate.
|
|
26
|
+
* Messages include journal replay messages and completion messages.
|
|
27
|
+
*/
|
|
28
|
+
export interface RestateStreamConsumer {
|
|
29
|
+
handleMessage(m: Message): boolean;
|
|
30
|
+
|
|
31
|
+
handleStreamError(e: Error): void;
|
|
32
|
+
|
|
33
|
+
handleInputClosed(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
import { RemoteContext } from "../generated/proto/services";
|
|
13
|
+
import { Message } from "../types/types";
|
|
14
|
+
import { BufferedConnection } from "./buffered_connection";
|
|
15
|
+
import { Connection } from "./connection";
|
|
16
|
+
|
|
17
|
+
export class FencedOffError extends Error {
|
|
18
|
+
constructor() {
|
|
19
|
+
super("FencedOff");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class InvocationAlreadyCompletedError extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super("Completed");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class EmbeddedConnection implements Connection {
|
|
30
|
+
private buffered: BufferedConnection;
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
private readonly operationId: string,
|
|
34
|
+
private readonly streamId: string,
|
|
35
|
+
private readonly remote: RemoteContext
|
|
36
|
+
) {
|
|
37
|
+
this.buffered = new BufferedConnection((buffer) => this.sendBuffer(buffer));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
send(msg: Message): Promise<void> {
|
|
41
|
+
return this.buffered.send(msg);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
end(): Promise<void> {
|
|
45
|
+
return this.buffered.end();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async sendBuffer(buffer: Buffer): Promise<void> {
|
|
49
|
+
const res = await this.remote.send({
|
|
50
|
+
operationId: this.operationId,
|
|
51
|
+
streamId: this.streamId,
|
|
52
|
+
messages: buffer,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (res.invalidStream !== undefined) {
|
|
56
|
+
throw new FencedOffError();
|
|
57
|
+
}
|
|
58
|
+
if (res.invocationCompleted !== undefined) {
|
|
59
|
+
throw new InvocationAlreadyCompletedError();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
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
|
+
import stream from "stream";
|
|
13
|
+
import { streamDecoder } from "../io/decoder";
|
|
14
|
+
import { Connection, RestateStreamConsumer } from "./connection";
|
|
15
|
+
import { Message } from "../types/types";
|
|
16
|
+
import { rlog } from "../logger";
|
|
17
|
+
import { finished } from "stream/promises";
|
|
18
|
+
import { BufferedConnection } from "./buffered_connection";
|
|
19
|
+
|
|
20
|
+
// utility promise, for cases where we want to save allocation of an extra promise
|
|
21
|
+
const RESOLVED: Promise<void> = Promise.resolve();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A duplex stream with Restate Messages over HTTP2.
|
|
25
|
+
*
|
|
26
|
+
* This stream handles the following concerns:
|
|
27
|
+
*
|
|
28
|
+
* (1) encoding and decoding of messages from and from raw bytes
|
|
29
|
+
*
|
|
30
|
+
* (2) buffering the outgoing messages, because the call sites that produce (potentially) large
|
|
31
|
+
* messages might not await their transfer. Aside from the fact that we achieve better pipelining
|
|
32
|
+
* that way, we also simply cannot guarantee that users of the Restate SDK actually await the
|
|
33
|
+
* relevant async API methods.
|
|
34
|
+
*
|
|
35
|
+
* This stream essentially buffers messages and, upon flush, sends them asynchronously, as the
|
|
36
|
+
* stream has availability. Flush requests queue up, if new data gets flushed while the previous
|
|
37
|
+
* data is still being sent.
|
|
38
|
+
*
|
|
39
|
+
* (3) Input messages can be pipelined to a sequence of consumers. For example, first to a journal,
|
|
40
|
+
* and afterwards to the state machine.
|
|
41
|
+
*
|
|
42
|
+
* (4) Handling the relevant stream events for errors and consolidating them to one error handler, plus
|
|
43
|
+
* notifications for cleanly closed input (to trigger suspension).
|
|
44
|
+
*/
|
|
45
|
+
export class RestateHttp2Connection implements Connection {
|
|
46
|
+
/**
|
|
47
|
+
* create a RestateDuplex stream from an http2 (duplex) stream.
|
|
48
|
+
*/
|
|
49
|
+
public static from(http2stream: stream.Duplex): RestateHttp2Connection {
|
|
50
|
+
return new RestateHttp2Connection(http2stream);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
// input as decoded messages
|
|
56
|
+
private readonly sdkInput: stream.Readable;
|
|
57
|
+
|
|
58
|
+
// consumer handling
|
|
59
|
+
private currentConsumer: RestateStreamConsumer | null = null;
|
|
60
|
+
private inputBuffer: Message[] = [];
|
|
61
|
+
private consumerError?: Error;
|
|
62
|
+
private consumerInputClosed = false;
|
|
63
|
+
|
|
64
|
+
private outputBuffer: BufferedConnection;
|
|
65
|
+
|
|
66
|
+
constructor(private readonly rawStream: stream.Duplex) {
|
|
67
|
+
this.sdkInput = rawStream.pipe(streamDecoder());
|
|
68
|
+
|
|
69
|
+
this.outputBuffer = new BufferedConnection((buffer) => {
|
|
70
|
+
const hasMoreCapacity = rawStream.write(buffer);
|
|
71
|
+
if (hasMoreCapacity) {
|
|
72
|
+
return RESOLVED;
|
|
73
|
+
} else {
|
|
74
|
+
return new Promise((resolve) => rawStream.once("drain", resolve));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// remember and forward messages
|
|
79
|
+
this.sdkInput.on("data", (m: Message) => {
|
|
80
|
+
// deliver message, if we have a consumer. otherwise buffer the message.
|
|
81
|
+
if (this.currentConsumer) {
|
|
82
|
+
if (this.currentConsumer.handleMessage(m)) {
|
|
83
|
+
this.removeCurrentConsumer();
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
this.inputBuffer.push(m);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// remember and forward close events
|
|
91
|
+
this.sdkInput.on("end", () => {
|
|
92
|
+
this.consumerInputClosed = true;
|
|
93
|
+
if (this.currentConsumer) {
|
|
94
|
+
this.currentConsumer.handleInputClosed();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// --------- error handling --------
|
|
99
|
+
// - a.k.a. node event wrangling...
|
|
100
|
+
|
|
101
|
+
// the error handler for all sorts of errors coming from streams
|
|
102
|
+
const errorHandler = (e: Error) => {
|
|
103
|
+
// make sure we don't overwrite the initial error
|
|
104
|
+
if (this.consumerError !== undefined) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.consumerError = e;
|
|
108
|
+
if (this.currentConsumer) {
|
|
109
|
+
this.currentConsumer.handleStreamError(e);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// those two event types should cover all types of connection losses
|
|
114
|
+
rawStream.on("aborted", () => {
|
|
115
|
+
rlog.error("Connection to Restate was lost");
|
|
116
|
+
errorHandler(new Error("Connection to Restate was lost"));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// this is both the raw http2 stream and the output SDK->Restate
|
|
120
|
+
rawStream.on("error", (e: Error) => {
|
|
121
|
+
rlog.error("Error in http2 stream to Restate: " + (e.stack ?? e.message));
|
|
122
|
+
errorHandler(e);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// these events notify of errors in the decoding pipeline
|
|
126
|
+
this.sdkInput.on("error", (e: Error) => {
|
|
127
|
+
rlog.error(
|
|
128
|
+
"Error in input stream (Restate to Service): " + (e.stack ?? e.message)
|
|
129
|
+
);
|
|
130
|
+
errorHandler(e);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// see if streams get torn down before they end cleanly
|
|
134
|
+
this.sdkInput.on("close", () => {
|
|
135
|
+
if (!this.consumerInputClosed) {
|
|
136
|
+
errorHandler(new Error("stream was destroyed before end"));
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --------------------------------------------------------------------------
|
|
142
|
+
// input stream handling
|
|
143
|
+
// --------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Pipes the messages from this connection to the given consumer. The consumer
|
|
147
|
+
* will also receive error and stream closing notifications.
|
|
148
|
+
*
|
|
149
|
+
* Once the 'handleMessage()' method returns 'true', the consumer is immediately removed.
|
|
150
|
+
* That way, consumers can consume a bounded amount of messages (like just the initial journal).
|
|
151
|
+
*
|
|
152
|
+
* There can only be one consumer at a time.
|
|
153
|
+
*/
|
|
154
|
+
public pipeToConsumer(consumer: RestateStreamConsumer): void {
|
|
155
|
+
if (this.currentConsumer !== null) {
|
|
156
|
+
throw new Error("Already piping to a consumer");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.currentConsumer = consumer;
|
|
160
|
+
|
|
161
|
+
// propagate pre-existing information
|
|
162
|
+
if (this.consumerError) {
|
|
163
|
+
consumer.handleStreamError(this.consumerError);
|
|
164
|
+
}
|
|
165
|
+
if (this.consumerInputClosed) {
|
|
166
|
+
consumer.handleInputClosed();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// pipe the buffered input messages, if we buffered some before the consumer was registered
|
|
170
|
+
const input = this.inputBuffer;
|
|
171
|
+
if (input.length > 0) {
|
|
172
|
+
let i = 0;
|
|
173
|
+
while (i < input.length) {
|
|
174
|
+
const done = consumer.handleMessage(input[i]);
|
|
175
|
+
i++;
|
|
176
|
+
if (done) {
|
|
177
|
+
this.removeCurrentConsumer();
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
this.inputBuffer = i === input.length ? [] : this.inputBuffer.slice(i);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Removes the current consumer, if there is one.
|
|
187
|
+
*/
|
|
188
|
+
public removeCurrentConsumer(): void {
|
|
189
|
+
this.currentConsumer = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// --------------------------------------------------------------------------
|
|
193
|
+
// output stream handling
|
|
194
|
+
// --------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Adds a message to the output stream.
|
|
198
|
+
*
|
|
199
|
+
* This always puts the message into the node stream, but will return a promise that is resolved once
|
|
200
|
+
* further messages can be written.
|
|
201
|
+
*
|
|
202
|
+
* The reasoning is that some, but not all Restate operations return promises and are typically
|
|
203
|
+
* awaited. For example, rpc, sleep, side-effect have promises and are awaited, while one-way-sends and
|
|
204
|
+
* state updates don't return promises.
|
|
205
|
+
*
|
|
206
|
+
* As a pragmatic solution, we always accept messages, but return a promise for when the output has
|
|
207
|
+
* capacity again, so that at least the operations that await results will respect backpressure.
|
|
208
|
+
*/
|
|
209
|
+
public send(msg: Message): Promise<void> {
|
|
210
|
+
return this.outputBuffer.send(msg);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Ends the stream, awaiting pending writes.
|
|
215
|
+
*/
|
|
216
|
+
public async end(): Promise<void> {
|
|
217
|
+
await this.outputBuffer.end();
|
|
218
|
+
|
|
219
|
+
this.rawStream.end();
|
|
220
|
+
|
|
221
|
+
const options = {
|
|
222
|
+
error: true,
|
|
223
|
+
cleanup: true,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
await finished(this.rawStream, options);
|
|
227
|
+
}
|
|
228
|
+
}
|