@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,721 @@
|
|
|
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 {
|
|
13
|
+
CombineablePromise,
|
|
14
|
+
KeyedContext,
|
|
15
|
+
Rand,
|
|
16
|
+
RestateGrpcChannel,
|
|
17
|
+
ServiceApi,
|
|
18
|
+
} from "./context";
|
|
19
|
+
import { StateMachine } from "./state_machine";
|
|
20
|
+
import {
|
|
21
|
+
AwakeableEntryMessage,
|
|
22
|
+
BackgroundInvokeEntryMessage,
|
|
23
|
+
CompleteAwakeableEntryMessage,
|
|
24
|
+
DeepPartial,
|
|
25
|
+
GetStateEntryMessage,
|
|
26
|
+
GetStateKeysEntryMessage,
|
|
27
|
+
GetStateKeysEntryMessage_StateKeys,
|
|
28
|
+
InvokeEntryMessage,
|
|
29
|
+
SleepEntryMessage,
|
|
30
|
+
} from "./generated/proto/protocol";
|
|
31
|
+
import {
|
|
32
|
+
AWAKEABLE_ENTRY_MESSAGE_TYPE,
|
|
33
|
+
AWAKEABLE_IDENTIFIER_PREFIX,
|
|
34
|
+
BACKGROUND_INVOKE_ENTRY_MESSAGE_TYPE,
|
|
35
|
+
CLEAR_ALL_STATE_ENTRY_MESSAGE_TYPE,
|
|
36
|
+
CLEAR_STATE_ENTRY_MESSAGE_TYPE,
|
|
37
|
+
COMPLETE_AWAKEABLE_ENTRY_MESSAGE_TYPE,
|
|
38
|
+
GET_STATE_ENTRY_MESSAGE_TYPE,
|
|
39
|
+
GET_STATE_KEYS_ENTRY_MESSAGE_TYPE,
|
|
40
|
+
INVOKE_ENTRY_MESSAGE_TYPE,
|
|
41
|
+
SET_STATE_ENTRY_MESSAGE_TYPE,
|
|
42
|
+
SIDE_EFFECT_ENTRY_MESSAGE_TYPE,
|
|
43
|
+
SLEEP_ENTRY_MESSAGE_TYPE,
|
|
44
|
+
} from "./types/protocol";
|
|
45
|
+
import { SideEffectEntryMessage } from "./generated/proto/javascript";
|
|
46
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
47
|
+
import {
|
|
48
|
+
ErrorCodes,
|
|
49
|
+
RestateErrorCodes,
|
|
50
|
+
RestateError,
|
|
51
|
+
RetryableError,
|
|
52
|
+
TerminalError,
|
|
53
|
+
ensureError,
|
|
54
|
+
errorToFailureWithTerminal,
|
|
55
|
+
TimeoutError,
|
|
56
|
+
} from "./types/errors";
|
|
57
|
+
import { jsonSerialize, jsonDeserialize } from "./utils/utils";
|
|
58
|
+
import { Empty } from "./generated/google/protobuf/empty";
|
|
59
|
+
import {
|
|
60
|
+
DEFAULT_INFINITE_EXPONENTIAL_BACKOFF,
|
|
61
|
+
DEFAULT_INITIAL_DELAY_MS,
|
|
62
|
+
EXPONENTIAL_BACKOFF,
|
|
63
|
+
RetrySettings,
|
|
64
|
+
} from "./utils/public_utils";
|
|
65
|
+
import { Client, SendClient } from "./types/router";
|
|
66
|
+
import { RpcRequest, RpcResponse } from "./generated/proto/dynrpc";
|
|
67
|
+
import { requestFromArgs } from "./utils/assumptions";
|
|
68
|
+
import { RandImpl } from "./utils/rand";
|
|
69
|
+
import { newJournalEntryPromiseId } from "./promise_combinator_tracker";
|
|
70
|
+
import { WrappedPromise } from "./utils/promises";
|
|
71
|
+
|
|
72
|
+
export enum CallContexType {
|
|
73
|
+
None,
|
|
74
|
+
SideEffect,
|
|
75
|
+
OneWayCall,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CallContext {
|
|
79
|
+
type: CallContexType;
|
|
80
|
+
delay?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type InternalCombineablePromise<T> = CombineablePromise<T> &
|
|
84
|
+
WrappedPromise<T> & {
|
|
85
|
+
journalIndex: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export class ContextImpl implements KeyedContext, RestateGrpcChannel {
|
|
89
|
+
// here, we capture the context information for actions on the Restate context that
|
|
90
|
+
// are executed within other actions, such as
|
|
91
|
+
// ctx.oneWayCall( () => client.foo(bar) );
|
|
92
|
+
// we also use this information to ensure we check that only allowed operations are
|
|
93
|
+
// used. Within side-effects, no operations are allowed on the RestateContext.
|
|
94
|
+
// For example, this is illegal: 'ctx.sideEffect(() => {await ctx.get("my-state")})'
|
|
95
|
+
static callContext = new AsyncLocalStorage<CallContext>();
|
|
96
|
+
|
|
97
|
+
// This is used to guard users against calling ctx.sideEffect without awaiting it.
|
|
98
|
+
// See https://github.com/restatedev/sdk-typescript/issues/197 for more details.
|
|
99
|
+
private executingSideEffect = false;
|
|
100
|
+
|
|
101
|
+
constructor(
|
|
102
|
+
public readonly id: Buffer,
|
|
103
|
+
public readonly serviceName: string,
|
|
104
|
+
public readonly console: Console,
|
|
105
|
+
public readonly keyedContext: boolean,
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
private readonly stateMachine: StateMachine<any, any>,
|
|
108
|
+
public readonly rand: Rand = new RandImpl(id)
|
|
109
|
+
) {}
|
|
110
|
+
|
|
111
|
+
// DON'T make this function async!!! see sideEffect comment for details.
|
|
112
|
+
public get<T>(name: string): Promise<T | null> {
|
|
113
|
+
// Check if this is a valid action
|
|
114
|
+
this.checkState("get state");
|
|
115
|
+
this.checkStateOperation("get state");
|
|
116
|
+
|
|
117
|
+
// Create the message and let the state machine process it
|
|
118
|
+
const msg = GetStateEntryMessage.create({ key: Buffer.from(name) });
|
|
119
|
+
const completed = this.stateMachine.localStateStore.tryCompleteGet(
|
|
120
|
+
name,
|
|
121
|
+
msg
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const getState = async (): Promise<T | null> => {
|
|
125
|
+
const result = await this.stateMachine.handleUserCodeMessage(
|
|
126
|
+
GET_STATE_ENTRY_MESSAGE_TYPE,
|
|
127
|
+
msg,
|
|
128
|
+
completed
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// If the GetState message did not have a value or empty,
|
|
132
|
+
// then we went to the runtime to get the value.
|
|
133
|
+
// When we get the response, we set it in the localStateStore,
|
|
134
|
+
// to answer subsequent requests
|
|
135
|
+
if (!completed) {
|
|
136
|
+
this.stateMachine.localStateStore.add(name, result as Buffer | Empty);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!(result instanceof Buffer)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return jsonDeserialize(result.toString());
|
|
144
|
+
};
|
|
145
|
+
return getState();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// DON'T make this function async!!! see sideEffect comment for details.
|
|
149
|
+
public stateKeys(): Promise<Array<string>> {
|
|
150
|
+
// Check if this is a valid action
|
|
151
|
+
this.checkState("state keys");
|
|
152
|
+
|
|
153
|
+
// Create the message and let the state machine process it
|
|
154
|
+
const msg = GetStateKeysEntryMessage.create({});
|
|
155
|
+
const completed =
|
|
156
|
+
this.stateMachine.localStateStore.tryCompletedGetStateKeys(msg);
|
|
157
|
+
|
|
158
|
+
const getStateKeys = async (): Promise<Array<string>> => {
|
|
159
|
+
const result = await this.stateMachine.handleUserCodeMessage(
|
|
160
|
+
GET_STATE_KEYS_ENTRY_MESSAGE_TYPE,
|
|
161
|
+
msg,
|
|
162
|
+
completed
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return (result as GetStateKeysEntryMessage_StateKeys).keys.map((b) =>
|
|
166
|
+
b.toString()
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
return getStateKeys();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public set<T>(name: string, value: T): void {
|
|
173
|
+
this.checkState("set state");
|
|
174
|
+
this.checkStateOperation("set state");
|
|
175
|
+
const msg = this.stateMachine.localStateStore.set(name, value);
|
|
176
|
+
this.stateMachine.handleUserCodeMessage(SET_STATE_ENTRY_MESSAGE_TYPE, msg);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public clear(name: string): void {
|
|
180
|
+
this.checkState("clear state");
|
|
181
|
+
this.checkStateOperation("clear state");
|
|
182
|
+
|
|
183
|
+
const msg = this.stateMachine.localStateStore.clear(name);
|
|
184
|
+
this.stateMachine.handleUserCodeMessage(
|
|
185
|
+
CLEAR_STATE_ENTRY_MESSAGE_TYPE,
|
|
186
|
+
msg
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public clearAll(): void {
|
|
191
|
+
this.checkState("clear all state");
|
|
192
|
+
|
|
193
|
+
const msg = this.stateMachine.localStateStore.clearAll();
|
|
194
|
+
this.stateMachine.handleUserCodeMessage(
|
|
195
|
+
CLEAR_ALL_STATE_ENTRY_MESSAGE_TYPE,
|
|
196
|
+
msg
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// --- Calls, background calls, etc
|
|
201
|
+
|
|
202
|
+
public request(
|
|
203
|
+
service: string,
|
|
204
|
+
method: string,
|
|
205
|
+
data: Uint8Array
|
|
206
|
+
): Promise<Uint8Array> {
|
|
207
|
+
if (this.isInOneWayCall()) {
|
|
208
|
+
return this.invokeOneWay(
|
|
209
|
+
service,
|
|
210
|
+
method,
|
|
211
|
+
data,
|
|
212
|
+
this.getOneWayCallDelay()
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
return this.invoke(service, method, data);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// DON'T make this function async!!! see sideEffect comment for details.
|
|
220
|
+
private invoke(
|
|
221
|
+
service: string,
|
|
222
|
+
method: string,
|
|
223
|
+
data: Uint8Array
|
|
224
|
+
): InternalCombineablePromise<Uint8Array> {
|
|
225
|
+
this.checkState("invoke");
|
|
226
|
+
|
|
227
|
+
const msg = InvokeEntryMessage.create({
|
|
228
|
+
serviceName: service,
|
|
229
|
+
methodName: method,
|
|
230
|
+
parameter: Buffer.from(data),
|
|
231
|
+
});
|
|
232
|
+
return this.markCombineablePromise(
|
|
233
|
+
this.stateMachine
|
|
234
|
+
.handleUserCodeMessage(INVOKE_ENTRY_MESSAGE_TYPE, msg)
|
|
235
|
+
.transform((v) => v as Uint8Array)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private async invokeOneWay(
|
|
240
|
+
service: string,
|
|
241
|
+
method: string,
|
|
242
|
+
data: Uint8Array,
|
|
243
|
+
delay?: number
|
|
244
|
+
): Promise<Uint8Array> {
|
|
245
|
+
const actualDelay = delay || 0;
|
|
246
|
+
const invokeTime = actualDelay > 0 ? Date.now() + actualDelay : undefined;
|
|
247
|
+
const msg = BackgroundInvokeEntryMessage.create({
|
|
248
|
+
serviceName: service,
|
|
249
|
+
methodName: method,
|
|
250
|
+
parameter: Buffer.from(data),
|
|
251
|
+
invokeTime: invokeTime,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await this.stateMachine.handleUserCodeMessage(
|
|
255
|
+
BACKGROUND_INVOKE_ENTRY_MESSAGE_TYPE,
|
|
256
|
+
msg
|
|
257
|
+
);
|
|
258
|
+
return new Uint8Array();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// DON'T make this function async!!! see sideEffect comment for details.
|
|
262
|
+
public oneWayCall(
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
264
|
+
call: () => Promise<any>
|
|
265
|
+
): Promise<void> {
|
|
266
|
+
this.checkState("oneWayCall");
|
|
267
|
+
|
|
268
|
+
return ContextImpl.callContext.run(
|
|
269
|
+
{ type: CallContexType.OneWayCall },
|
|
270
|
+
call
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// DON'T make this function async!!! see sideEffect comment for details.
|
|
275
|
+
public delayedCall(
|
|
276
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
277
|
+
call: () => Promise<any>,
|
|
278
|
+
delayMillis?: number
|
|
279
|
+
): Promise<void> {
|
|
280
|
+
this.checkState("delayedCall");
|
|
281
|
+
|
|
282
|
+
// Delayed call is a one way call with a delay
|
|
283
|
+
return ContextImpl.callContext.run(
|
|
284
|
+
{ type: CallContexType.OneWayCall, delay: delayMillis },
|
|
285
|
+
call
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
rpc<M>({ path }: ServiceApi<M>): Client<M> {
|
|
290
|
+
const clientProxy = new Proxy(
|
|
291
|
+
{},
|
|
292
|
+
{
|
|
293
|
+
get: (_target, prop) => {
|
|
294
|
+
const route = prop as string;
|
|
295
|
+
return async (...args: unknown[]) => {
|
|
296
|
+
const request = requestFromArgs(args);
|
|
297
|
+
const requestBytes = RpcRequest.encode(request).finish();
|
|
298
|
+
return this.invoke(path, route, requestBytes).transform(
|
|
299
|
+
(responseBytes) => RpcResponse.decode(responseBytes).response
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
return clientProxy as Client<M>;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public send<M>(options: ServiceApi): SendClient<M> {
|
|
310
|
+
return this.sendDelayed(options, 0);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
public sendDelayed<M>(
|
|
314
|
+
{ path }: ServiceApi,
|
|
315
|
+
delayMillis: number
|
|
316
|
+
): SendClient<M> {
|
|
317
|
+
const clientProxy = new Proxy(
|
|
318
|
+
{},
|
|
319
|
+
{
|
|
320
|
+
get: (_target, prop) => {
|
|
321
|
+
const route = prop as string;
|
|
322
|
+
return (...args: unknown[]) => {
|
|
323
|
+
const request = requestFromArgs(args);
|
|
324
|
+
const requestBytes = RpcRequest.encode(request).finish();
|
|
325
|
+
this.invokeOneWay(path, route, requestBytes, delayMillis);
|
|
326
|
+
};
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return clientProxy as SendClient<M>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// --- Methods exposed by respective interfaces to interact with other APIs
|
|
335
|
+
|
|
336
|
+
grpcChannel(): RestateGrpcChannel {
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// DON'T make this function async!!!
|
|
341
|
+
// The reason is that we want the erros thrown by the initial checks to be propagated in the caller context,
|
|
342
|
+
// and not in the promise context. To understand the semantic difference, make this function async and run the
|
|
343
|
+
// UnawaitedSideEffectShouldFailSubsequentContextCall test.
|
|
344
|
+
public sideEffect<T>(
|
|
345
|
+
fn: () => Promise<T>,
|
|
346
|
+
retryPolicy: RetrySettings = DEFAULT_INFINITE_EXPONENTIAL_BACKOFF
|
|
347
|
+
): Promise<T> {
|
|
348
|
+
if (this.isInSideEffect()) {
|
|
349
|
+
throw new TerminalError(
|
|
350
|
+
"You cannot do sideEffect calls from within a side effect.",
|
|
351
|
+
{ errorCode: ErrorCodes.INTERNAL }
|
|
352
|
+
);
|
|
353
|
+
} else if (this.isInOneWayCall()) {
|
|
354
|
+
throw new TerminalError(
|
|
355
|
+
"Cannot do a side effect from within ctx.oneWayCall(...). " +
|
|
356
|
+
"Context method ctx.oneWayCall() can only be used to invoke other services unidirectionally. " +
|
|
357
|
+
"e.g. ctx.oneWayCall(() => client.greet(my_request))",
|
|
358
|
+
{ errorCode: ErrorCodes.INTERNAL }
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
this.checkNotExecutingSideEffect();
|
|
362
|
+
this.executingSideEffect = true;
|
|
363
|
+
|
|
364
|
+
const executeAndLogSideEffect = async () => {
|
|
365
|
+
// in replay mode, we directly return the value from the log
|
|
366
|
+
if (this.stateMachine.nextEntryWillBeReplayed()) {
|
|
367
|
+
const emptyMsg = SideEffectEntryMessage.create({});
|
|
368
|
+
return this.stateMachine.handleUserCodeMessage<T>(
|
|
369
|
+
SIDE_EFFECT_ENTRY_MESSAGE_TYPE,
|
|
370
|
+
emptyMsg
|
|
371
|
+
) as Promise<T>;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let sideEffectResult: T;
|
|
375
|
+
try {
|
|
376
|
+
sideEffectResult = await ContextImpl.callContext.run(
|
|
377
|
+
{ type: CallContexType.SideEffect },
|
|
378
|
+
fn
|
|
379
|
+
);
|
|
380
|
+
} catch (e) {
|
|
381
|
+
// we commit any error from the side effet to thr journal, and re-throw it into
|
|
382
|
+
// the function. that way, any catching by the user and reacting to it will be
|
|
383
|
+
// deterministic on replay
|
|
384
|
+
const error = ensureError(e);
|
|
385
|
+
const failure = errorToFailureWithTerminal(error);
|
|
386
|
+
const sideEffectMsg = SideEffectEntryMessage.create({
|
|
387
|
+
failure: failure,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// this may throw an error from the SDK/runtime/connection side, in case the
|
|
391
|
+
// failure message cannot be committed to the journal. That error would then
|
|
392
|
+
// be returned from this function (replace the original error)
|
|
393
|
+
// that is acceptable, because in such a situation (failure to append to journal),
|
|
394
|
+
// the state machine closes anyways and no further operations will succeed and the
|
|
395
|
+
// the execution aborts
|
|
396
|
+
await this.stateMachine.handleUserCodeMessage<T>(
|
|
397
|
+
SIDE_EFFECT_ENTRY_MESSAGE_TYPE,
|
|
398
|
+
sideEffectMsg,
|
|
399
|
+
false,
|
|
400
|
+
undefined,
|
|
401
|
+
true
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
throw e;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// we have this code outside the above try/catch block, to ensure that any error arising
|
|
408
|
+
// from here is not incorrectly attributed to the side-effect
|
|
409
|
+
const sideEffectMsg =
|
|
410
|
+
sideEffectResult !== undefined
|
|
411
|
+
? SideEffectEntryMessage.create({
|
|
412
|
+
value: Buffer.from(jsonSerialize(sideEffectResult)),
|
|
413
|
+
})
|
|
414
|
+
: SideEffectEntryMessage.create();
|
|
415
|
+
|
|
416
|
+
// if an error arises from committing the side effect result, then this error will
|
|
417
|
+
// be thrown here (reject the returned promise) and the function will see that error,
|
|
418
|
+
// even if the side-effect function completed correctly
|
|
419
|
+
// that is acceptable, because in such a situation (failure to append to journal),
|
|
420
|
+
// the state machine closes anyways and reports an execution failure, meaning no further
|
|
421
|
+
// operations will succeed and the the execution will be retried.
|
|
422
|
+
// If the side-effect result did in fact not make it to the journal, then the side-effect
|
|
423
|
+
// re-executes, and if it made it to the journal after all (error happend inly during
|
|
424
|
+
// ack-back), then retries will use the journaled result.
|
|
425
|
+
// So all good in any case, due to the beauty of "the runtime log is the ground thruth" approach.
|
|
426
|
+
await this.stateMachine.handleUserCodeMessage<T>(
|
|
427
|
+
SIDE_EFFECT_ENTRY_MESSAGE_TYPE,
|
|
428
|
+
sideEffectMsg,
|
|
429
|
+
false,
|
|
430
|
+
undefined,
|
|
431
|
+
true
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
return sideEffectResult;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const sleep = (millis: number) => this.sleepInternal(millis);
|
|
438
|
+
return executeWithRetries(
|
|
439
|
+
this.console,
|
|
440
|
+
retryPolicy,
|
|
441
|
+
executeAndLogSideEffect,
|
|
442
|
+
sleep
|
|
443
|
+
).finally(() => {
|
|
444
|
+
this.executingSideEffect = false;
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
public sleep(millis: number): CombineablePromise<void> {
|
|
449
|
+
this.checkState("sleep");
|
|
450
|
+
return this.markCombineablePromise(this.sleepInternal(millis));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private sleepInternal(millis: number): WrappedPromise<void> {
|
|
454
|
+
return this.stateMachine.handleUserCodeMessage<void>(
|
|
455
|
+
SLEEP_ENTRY_MESSAGE_TYPE,
|
|
456
|
+
SleepEntryMessage.create({ wakeUpTime: Date.now() + millis })
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// -- Awakeables
|
|
461
|
+
|
|
462
|
+
public awakeable<T>(): { id: string; promise: CombineablePromise<T> } {
|
|
463
|
+
this.checkState("awakeable");
|
|
464
|
+
|
|
465
|
+
const msg = AwakeableEntryMessage.create();
|
|
466
|
+
const promise = this.stateMachine
|
|
467
|
+
.handleUserCodeMessage<Buffer>(AWAKEABLE_ENTRY_MESSAGE_TYPE, msg)
|
|
468
|
+
.transform((result: Buffer | void) => {
|
|
469
|
+
if (!(result instanceof Buffer)) {
|
|
470
|
+
// This should either be a filled buffer or an empty buffer but never anything else.
|
|
471
|
+
throw RetryableError.internal(
|
|
472
|
+
"Awakeable was not resolved with a buffer payload"
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return JSON.parse(result.toString()) as T;
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// This needs to be done after handling the message in the state machine
|
|
480
|
+
// otherwise the index is not yet incremented.
|
|
481
|
+
const encodedEntryIndex = Buffer.alloc(4 /* Size of u32 */);
|
|
482
|
+
encodedEntryIndex.writeUInt32BE(
|
|
483
|
+
this.stateMachine.getUserCodeJournalIndex()
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
id:
|
|
488
|
+
AWAKEABLE_IDENTIFIER_PREFIX +
|
|
489
|
+
Buffer.concat([this.id, encodedEntryIndex]).toString("base64url"),
|
|
490
|
+
promise: this.markCombineablePromise(promise),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
public resolveAwakeable<T>(id: string, payload?: T): void {
|
|
495
|
+
// We coerce undefined to null as null can be stringified by JSON.stringify
|
|
496
|
+
const payloadToWrite = payload === undefined ? null : payload;
|
|
497
|
+
|
|
498
|
+
this.checkState("resolveAwakeable");
|
|
499
|
+
this.completeAwakeable(id, {
|
|
500
|
+
value: Buffer.from(JSON.stringify(payloadToWrite)),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
public rejectAwakeable(id: string, reason: string): void {
|
|
505
|
+
this.checkState("rejectAwakeable");
|
|
506
|
+
this.completeAwakeable(id, {
|
|
507
|
+
failure: { code: ErrorCodes.UNKNOWN, message: reason },
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private completeAwakeable(
|
|
512
|
+
id: string,
|
|
513
|
+
base: DeepPartial<CompleteAwakeableEntryMessage>
|
|
514
|
+
): void {
|
|
515
|
+
base.id = id;
|
|
516
|
+
this.stateMachine.handleUserCodeMessage(
|
|
517
|
+
COMPLETE_AWAKEABLE_ENTRY_MESSAGE_TYPE,
|
|
518
|
+
CompleteAwakeableEntryMessage.create(base)
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Used by static methods of CombineablePromise
|
|
523
|
+
public createCombinator<T extends readonly CombineablePromise<unknown>[]>(
|
|
524
|
+
combinatorConstructor: (
|
|
525
|
+
promises: PromiseLike<unknown>[]
|
|
526
|
+
) => Promise<unknown>,
|
|
527
|
+
promises: T
|
|
528
|
+
): WrappedPromise<unknown> {
|
|
529
|
+
const outPromises = [];
|
|
530
|
+
|
|
531
|
+
for (const promise of promises) {
|
|
532
|
+
if (promise.__restate_context !== this) {
|
|
533
|
+
throw RetryableError.internal(
|
|
534
|
+
"You're mixing up CombineablePromises from different RestateContext. This is not supported."
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
const index = (promise as InternalCombineablePromise<unknown>)
|
|
538
|
+
.journalIndex;
|
|
539
|
+
outPromises.push({
|
|
540
|
+
id: newJournalEntryPromiseId(index),
|
|
541
|
+
promise: promise,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return this.stateMachine.createCombinator(
|
|
546
|
+
combinatorConstructor,
|
|
547
|
+
outPromises
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// -- Various private methods
|
|
552
|
+
|
|
553
|
+
private isInSideEffect(): boolean {
|
|
554
|
+
const context = ContextImpl.callContext.getStore();
|
|
555
|
+
return context?.type === CallContexType.SideEffect;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private isInOneWayCall(): boolean {
|
|
559
|
+
const context = ContextImpl.callContext.getStore();
|
|
560
|
+
return context?.type === CallContexType.OneWayCall;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
private getOneWayCallDelay(): number | undefined {
|
|
564
|
+
const context = ContextImpl.callContext.getStore();
|
|
565
|
+
return context?.delay;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private checkNotExecutingSideEffect() {
|
|
569
|
+
if (this.executingSideEffect) {
|
|
570
|
+
throw new TerminalError(
|
|
571
|
+
`Invoked a RestateContext method while a side effect is still executing.
|
|
572
|
+
Make sure you await the ctx.sideEffect call before using any other RestateContext method.`,
|
|
573
|
+
{ errorCode: ErrorCodes.INTERNAL }
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private checkState(callType: string): void {
|
|
579
|
+
const context = ContextImpl.callContext.getStore();
|
|
580
|
+
if (!context) {
|
|
581
|
+
this.checkNotExecutingSideEffect();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (context.type === CallContexType.SideEffect) {
|
|
586
|
+
throw new TerminalError(
|
|
587
|
+
`You cannot do ${callType} calls from within a side effect.`,
|
|
588
|
+
{ errorCode: ErrorCodes.INTERNAL }
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (context.type === CallContexType.OneWayCall) {
|
|
593
|
+
throw new TerminalError(
|
|
594
|
+
`Cannot do a ${callType} from within ctx.oneWayCall(...).
|
|
595
|
+
Context method oneWayCall() can only be used to invoke other services in the background.
|
|
596
|
+
e.g. ctx.oneWayCall(() => client.greet(my_request))`,
|
|
597
|
+
{ errorCode: ErrorCodes.INTERNAL }
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private checkStateOperation(callType: string): void {
|
|
603
|
+
if (!this.keyedContext) {
|
|
604
|
+
throw new TerminalError(
|
|
605
|
+
`You can do ${callType} calls only from keyed services/routers.`,
|
|
606
|
+
{ errorCode: ErrorCodes.INTERNAL }
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private markCombineablePromise<T>(
|
|
612
|
+
p: WrappedPromise<T>
|
|
613
|
+
): InternalCombineablePromise<T> {
|
|
614
|
+
const journalIndex = this.stateMachine.getUserCodeJournalIndex();
|
|
615
|
+
const orTimeout = (millis: number): Promise<T> => {
|
|
616
|
+
const sleepPromise: Promise<T> = this.sleepInternal(millis).transform(
|
|
617
|
+
() => {
|
|
618
|
+
throw new TimeoutError();
|
|
619
|
+
}
|
|
620
|
+
);
|
|
621
|
+
const sleepPromiseIndex = this.stateMachine.getUserCodeJournalIndex();
|
|
622
|
+
|
|
623
|
+
return this.stateMachine.createCombinator(Promise.race.bind(Promise), [
|
|
624
|
+
{
|
|
625
|
+
id: newJournalEntryPromiseId(journalIndex),
|
|
626
|
+
promise: p,
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
id: newJournalEntryPromiseId(sleepPromiseIndex),
|
|
630
|
+
promise: sleepPromise,
|
|
631
|
+
},
|
|
632
|
+
]) as Promise<T>;
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
return Object.defineProperties(p, {
|
|
636
|
+
__restate_context: {
|
|
637
|
+
value: this,
|
|
638
|
+
},
|
|
639
|
+
journalIndex: {
|
|
640
|
+
value: journalIndex,
|
|
641
|
+
},
|
|
642
|
+
orTimeout: {
|
|
643
|
+
value: orTimeout.bind(this),
|
|
644
|
+
},
|
|
645
|
+
}) as InternalCombineablePromise<T>;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async function executeWithRetries<T>(
|
|
650
|
+
console: Console,
|
|
651
|
+
retrySettings: RetrySettings,
|
|
652
|
+
executeAndLogSideEffect: () => Promise<T>,
|
|
653
|
+
sleep: (millis: number) => Promise<void>
|
|
654
|
+
): Promise<T> {
|
|
655
|
+
const {
|
|
656
|
+
initialDelayMs = DEFAULT_INITIAL_DELAY_MS,
|
|
657
|
+
maxDelayMs = Number.MAX_SAFE_INTEGER,
|
|
658
|
+
maxRetries = Number.MAX_SAFE_INTEGER,
|
|
659
|
+
policy = EXPONENTIAL_BACKOFF,
|
|
660
|
+
name = "side-effect",
|
|
661
|
+
} = retrySettings;
|
|
662
|
+
|
|
663
|
+
let currentDelayMs = initialDelayMs;
|
|
664
|
+
let retriesLeft = maxRetries;
|
|
665
|
+
|
|
666
|
+
// eslint-disable-next-line no-constant-condition
|
|
667
|
+
while (true) {
|
|
668
|
+
try {
|
|
669
|
+
return await executeAndLogSideEffect();
|
|
670
|
+
} catch (e) {
|
|
671
|
+
if (e instanceof TerminalError) {
|
|
672
|
+
throw e;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// journal mismatch errors are special:
|
|
676
|
+
// - they are not terminal errors, because we want to allow pushing new code so
|
|
677
|
+
// that retries succeed later
|
|
678
|
+
// - they are not retried within the service, because they will never succeed within this service,
|
|
679
|
+
// but can only succeed within a new invocation going to service with fixed code
|
|
680
|
+
// we hence break the retries here similar to terminal errors
|
|
681
|
+
if (
|
|
682
|
+
e instanceof RestateError &&
|
|
683
|
+
e.code == RestateErrorCodes.JOURNAL_MISMATCH
|
|
684
|
+
) {
|
|
685
|
+
throw e;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const error = ensureError(e);
|
|
689
|
+
|
|
690
|
+
console.debug(
|
|
691
|
+
"Error while executing side effect '%s': %s - %s",
|
|
692
|
+
name,
|
|
693
|
+
error.name,
|
|
694
|
+
error.message
|
|
695
|
+
);
|
|
696
|
+
if (error.stack) {
|
|
697
|
+
console.debug(error.stack);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (retriesLeft > 0) {
|
|
701
|
+
console.debug("Retrying in %d ms", currentDelayMs);
|
|
702
|
+
} else {
|
|
703
|
+
console.debug("No retries left.");
|
|
704
|
+
throw new TerminalError(
|
|
705
|
+
`Retries exhausted for ${name}. Last error: ${error.name}: ${error.message}`,
|
|
706
|
+
{
|
|
707
|
+
errorCode: ErrorCodes.INTERNAL,
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
await sleep(currentDelayMs);
|
|
714
|
+
|
|
715
|
+
retriesLeft -= 1;
|
|
716
|
+
currentDelayMs = Math.min(
|
|
717
|
+
policy.computeNextDelay(currentDelayMs),
|
|
718
|
+
maxDelayMs
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|