@restatedev/restate-sdk-cloudflare-workers 1.4.0 → 1.5.0

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.
Files changed (88) hide show
  1. package/dist/esm/src/common_api.d.ts +5 -5
  2. package/dist/esm/src/common_api.d.ts.map +1 -1
  3. package/dist/esm/src/common_api.js +1 -1
  4. package/dist/esm/src/common_api.js.map +1 -1
  5. package/dist/esm/src/context.d.ts +68 -10
  6. package/dist/esm/src/context.d.ts.map +1 -1
  7. package/dist/esm/src/context.js +20 -8
  8. package/dist/esm/src/context.js.map +1 -1
  9. package/dist/esm/src/context_impl.d.ts +28 -58
  10. package/dist/esm/src/context_impl.d.ts.map +1 -1
  11. package/dist/esm/src/context_impl.js +196 -425
  12. package/dist/esm/src/context_impl.js.map +1 -1
  13. package/dist/esm/src/endpoint/endpoint_builder.d.ts +4 -5
  14. package/dist/esm/src/endpoint/endpoint_builder.d.ts.map +1 -1
  15. package/dist/esm/src/endpoint/endpoint_builder.js +21 -16
  16. package/dist/esm/src/endpoint/endpoint_builder.js.map +1 -1
  17. package/dist/esm/src/endpoint/fetch_endpoint.d.ts +3 -4
  18. package/dist/esm/src/endpoint/fetch_endpoint.d.ts.map +1 -1
  19. package/dist/esm/src/endpoint/fetch_endpoint.js +0 -4
  20. package/dist/esm/src/endpoint/fetch_endpoint.js.map +1 -1
  21. package/dist/esm/src/endpoint/handlers/fetch.d.ts.map +1 -1
  22. package/dist/esm/src/endpoint/handlers/fetch.js +1 -0
  23. package/dist/esm/src/endpoint/handlers/fetch.js.map +1 -1
  24. package/dist/esm/src/endpoint/handlers/generic.d.ts +7 -13
  25. package/dist/esm/src/endpoint/handlers/generic.d.ts.map +1 -1
  26. package/dist/esm/src/endpoint/handlers/generic.js +110 -51
  27. package/dist/esm/src/endpoint/handlers/generic.js.map +1 -1
  28. package/dist/esm/src/endpoint/handlers/lambda.d.ts.map +1 -1
  29. package/dist/esm/src/endpoint/handlers/lambda.js +15 -3
  30. package/dist/esm/src/endpoint/handlers/lambda.js.map +1 -1
  31. package/dist/esm/src/endpoint/handlers/vm/sdk_shared_core_wasm_bindings.d.ts +37 -208
  32. package/dist/esm/src/endpoint/handlers/vm/sdk_shared_core_wasm_bindings_bg.js +667 -930
  33. package/dist/esm/src/endpoint/handlers/vm/sdk_shared_core_wasm_bindings_bg.wasm +0 -0
  34. package/dist/esm/src/endpoint/handlers/vm/sdk_shared_core_wasm_bindings_bg.wasm.d.ts +66 -63
  35. package/dist/esm/src/endpoint/lambda_endpoint.d.ts +3 -4
  36. package/dist/esm/src/endpoint/lambda_endpoint.d.ts.map +1 -1
  37. package/dist/esm/src/endpoint/lambda_endpoint.js +0 -4
  38. package/dist/esm/src/endpoint/lambda_endpoint.js.map +1 -1
  39. package/dist/esm/src/endpoint/node_endpoint.d.ts +3 -4
  40. package/dist/esm/src/endpoint/node_endpoint.d.ts.map +1 -1
  41. package/dist/esm/src/endpoint/node_endpoint.js +12 -4
  42. package/dist/esm/src/endpoint/node_endpoint.js.map +1 -1
  43. package/dist/esm/src/endpoint.d.ts +6 -37
  44. package/dist/esm/src/endpoint.d.ts.map +1 -1
  45. package/dist/esm/src/generated/version.d.ts +1 -1
  46. package/dist/esm/src/generated/version.js +1 -1
  47. package/dist/esm/src/io.d.ts +25 -0
  48. package/dist/esm/src/io.d.ts.map +1 -0
  49. package/dist/esm/src/io.js +73 -0
  50. package/dist/esm/src/io.js.map +1 -0
  51. package/dist/esm/src/logging/console_logger_transport.d.ts +5 -0
  52. package/dist/esm/src/logging/console_logger_transport.d.ts.map +1 -0
  53. package/dist/esm/src/{logger.js → logging/console_logger_transport.js} +40 -90
  54. package/dist/esm/src/logging/console_logger_transport.js.map +1 -0
  55. package/dist/esm/src/logging/logger.d.ts +11 -0
  56. package/dist/esm/src/logging/logger.d.ts.map +1 -0
  57. package/dist/esm/src/logging/logger.js +57 -0
  58. package/dist/esm/src/logging/logger.js.map +1 -0
  59. package/dist/esm/src/logging/logger_transport.d.ts +52 -0
  60. package/dist/esm/src/logging/logger_transport.d.ts.map +1 -0
  61. package/dist/esm/src/logging/logger_transport.js +55 -0
  62. package/dist/esm/src/logging/logger_transport.js.map +1 -0
  63. package/dist/esm/src/promises.d.ts +110 -0
  64. package/dist/esm/src/promises.d.ts.map +1 -0
  65. package/dist/esm/src/promises.js +311 -0
  66. package/dist/esm/src/promises.js.map +1 -0
  67. package/dist/esm/src/types/components.d.ts +11 -3
  68. package/dist/esm/src/types/components.d.ts.map +1 -1
  69. package/dist/esm/src/types/components.js +36 -3
  70. package/dist/esm/src/types/components.js.map +1 -1
  71. package/dist/esm/src/types/discovery.d.ts +4 -0
  72. package/dist/esm/src/types/discovery.d.ts.map +1 -1
  73. package/dist/esm/src/types/errors.d.ts +8 -0
  74. package/dist/esm/src/types/errors.d.ts.map +1 -1
  75. package/dist/esm/src/types/errors.js +10 -0
  76. package/dist/esm/src/types/errors.js.map +1 -1
  77. package/dist/esm/src/types/rpc.d.ts +66 -41
  78. package/dist/esm/src/types/rpc.d.ts.map +1 -1
  79. package/dist/esm/src/types/rpc.js +25 -76
  80. package/dist/esm/src/types/rpc.js.map +1 -1
  81. package/dist/esm/src/user_agent.d.ts +1 -1
  82. package/dist/esm/src/utils/buffer.d.ts +1 -0
  83. package/dist/esm/src/utils/buffer.d.ts.map +1 -1
  84. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  85. package/package.json +2 -2
  86. package/dist/esm/src/logger.d.ts +0 -35
  87. package/dist/esm/src/logger.d.ts.map +0 -1
  88. package/dist/esm/src/logger.js.map +0 -1
@@ -8,19 +8,22 @@
8
8
  * directory of this repository or package, or at
9
9
  * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE
10
10
  */
11
- import { ensureError, INTERNAL_ERROR_CODE, RestateError, SUSPENDED_ERROR_CODE, TerminalError, TimeoutError, UNKNOWN_ERROR_CODE, } from "./types/errors.js";
11
+ import { WasmHeader } from "./endpoint/handlers/vm/sdk_shared_core_wasm_bindings.js";
12
+ import { ensureError, INTERNAL_ERROR_CODE, RestateError, SUSPENDED_ERROR_CODE, TerminalError, UNKNOWN_ERROR_CODE, } from "./types/errors.js";
12
13
  import { defaultSerde, HandlerKind, makeRpcCallProxy, makeRpcSendProxy, } from "./types/rpc.js";
13
14
  import { serde } from "@restatedev/restate-sdk-core";
14
15
  import { RandImpl } from "./utils/rand.js";
16
+ import { CompletablePromise } from "./utils/completable_promise.js";
17
+ import { extractContext, pendingPromise, PromisesExecutor, RestateInvocationPromise, RestateCombinatorPromise, RestatePendingPromise, InvocationPendingPromise, RestateSinglePromise, } from "./promises.js";
18
+ import { InputPump, OutputPump } from "./io.js";
15
19
  export class ContextImpl {
16
20
  coreVm;
17
21
  input;
18
22
  console;
19
23
  handlerKind;
20
- invocationEndPromise;
21
- inputReader;
22
- outputWriter;
24
+ vmLogger;
23
25
  invocationRequest;
26
+ invocationEndPromise;
24
27
  rand;
25
28
  date = {
26
29
  now: () => {
@@ -30,35 +33,34 @@ export class ContextImpl {
30
33
  return this.run(() => new Date().toJSON());
31
34
  },
32
35
  };
33
- currentRead;
34
- constructor(coreVm, input, console, handlerKind, attemptHeaders, extraArgs, invocationEndPromise, inputReader, outputWriter) {
36
+ outputPump;
37
+ runClosuresTracker;
38
+ promisesExecutor;
39
+ constructor(coreVm, input, console, handlerKind, vmLogger, invocationRequest, invocationEndPromise, inputReader, outputWriter) {
35
40
  this.coreVm = coreVm;
36
41
  this.input = input;
37
42
  this.console = console;
38
43
  this.handlerKind = handlerKind;
44
+ this.vmLogger = vmLogger;
45
+ this.invocationRequest = invocationRequest;
39
46
  this.invocationEndPromise = invocationEndPromise;
40
- this.inputReader = inputReader;
41
- this.outputWriter = outputWriter;
42
- this.invocationRequest = {
43
- id: input.invocation_id,
44
- headers: input.headers.reduce((headers, { key, value }) => {
45
- headers.set(key, value);
46
- return headers;
47
- }, new Map()),
48
- attemptHeaders: Object.entries(attemptHeaders).reduce((headers, [key, value]) => {
49
- if (value !== undefined) {
50
- headers.set(key, value instanceof Array ? value[0] : value);
51
- }
52
- return headers;
53
- }, new Map()),
54
- body: input.input,
55
- extraArgs,
56
- };
57
47
  this.rand = new RandImpl(input.invocation_id, () => {
58
- if (coreVm.is_inside_run()) {
59
- throw new Error("Cannot generate random numbers within a run closure. Use the random object outside the run closure.");
60
- }
48
+ // TODO reimplement this check with async context
49
+ // if (coreVm.is_inside_run()) {
50
+ // throw new Error(
51
+ // "Cannot generate random numbers within a run closure. Use the random object outside the run closure."
52
+ // );
53
+ // }
61
54
  });
55
+ this.outputPump = new OutputPump(coreVm, outputWriter);
56
+ this.runClosuresTracker = new RunClosuresTracker();
57
+ this.promisesExecutor = new PromisesExecutor(coreVm, new InputPump(coreVm, inputReader, this.handleInvocationEndError.bind(this)), this.outputPump, this.runClosuresTracker, this.handleInvocationEndError.bind(this));
58
+ }
59
+ cancel(invocationId) {
60
+ this.processNonCompletableEntry((vm) => vm.sys_cancel_invocation(invocationId));
61
+ }
62
+ attach(invocationId, serde) {
63
+ return this.processCompletableEntry((vm) => vm.sys_attach_invocation(invocationId), completeUsing(SuccessWithSerde(serde ?? defaultSerde()), Failure));
62
64
  }
63
65
  get key() {
64
66
  switch (this.handlerKind) {
@@ -75,36 +77,10 @@ export class ContextImpl {
75
77
  return this.invocationRequest;
76
78
  }
77
79
  get(name, serde) {
78
- return this.processCompletableEntry((vm) => vm.sys_get_state(name), (asyncResultValue) => {
79
- if (asyncResultValue === "Empty") {
80
- // Empty
81
- return null;
82
- }
83
- else if ("Success" in asyncResultValue) {
84
- return (serde ?? defaultSerde()).deserialize(asyncResultValue.Success);
85
- }
86
- else if ("Failure" in asyncResultValue) {
87
- throw new TerminalError(asyncResultValue.Failure.message, {
88
- errorCode: asyncResultValue.Failure.code,
89
- });
90
- }
91
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
92
- });
80
+ return this.processCompletableEntry((vm) => vm.sys_get_state(name), completeUsing(VoidAsNull, SuccessWithSerde(serde ?? defaultSerde())));
93
81
  }
94
82
  stateKeys() {
95
- return this.processCompletableEntry((vm) => vm.sys_get_state_keys(), (asyncResultValue) => {
96
- if (typeof asyncResultValue === "object" &&
97
- "StateKeys" in asyncResultValue) {
98
- return asyncResultValue.StateKeys;
99
- }
100
- else if (typeof asyncResultValue === "object" &&
101
- "Failure" in asyncResultValue) {
102
- throw new TerminalError(asyncResultValue.Failure.message, {
103
- errorCode: asyncResultValue.Failure.code,
104
- });
105
- }
106
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
107
- });
83
+ return this.processCompletableEntry((vm) => vm.sys_get_state_keys(), completeUsing(StateKeys));
108
84
  }
109
85
  set(name, value, serde) {
110
86
  this.processNonCompletableEntry((vm) => vm.sys_set_state(name, (serde ?? defaultSerde()).serialize(value)));
@@ -120,33 +96,50 @@ export class ContextImpl {
120
96
  genericCall(call) {
121
97
  const requestSerde = call.inputSerde ?? serde.binary;
122
98
  const responseSerde = call.outputSerde ?? serde.binary;
123
- return this.processCompletableEntry((vm) => {
99
+ try {
100
+ const vm = this.coreVm;
124
101
  const parameter = requestSerde.serialize(call.parameter);
125
- return vm.sys_call(call.service, call.method, parameter, call.key);
126
- }, (asyncResultValue) => {
127
- if (typeof asyncResultValue === "object" &&
128
- "Success" in asyncResultValue) {
129
- return responseSerde.deserialize(asyncResultValue.Success);
130
- }
131
- else if (typeof asyncResultValue === "object" &&
132
- "Failure" in asyncResultValue) {
133
- throw new TerminalError(asyncResultValue.Failure.message, {
134
- errorCode: asyncResultValue.Failure.code,
135
- });
136
- }
137
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
138
- });
102
+ const call_handles = vm.sys_call(call.service, call.method, parameter, call.key, call.headers
103
+ ? Object.entries(call.headers).map(([key, value]) => new WasmHeader(key, value))
104
+ : [], call.idempotencyKey);
105
+ const invocationIdHandle = call_handles.invocation_id_completion_id;
106
+ const invocationIdPromise = this.createInvocationIdPromise(invocationIdHandle);
107
+ const callHandle = call_handles.call_completion_id;
108
+ return new RestateInvocationPromise(this, callHandle, completeUsing(SuccessWithSerde(responseSerde), Failure), invocationIdPromise);
109
+ }
110
+ catch (e) {
111
+ this.handleInvocationEndError(e);
112
+ // We return a pending promise to avoid the caller to see the error.
113
+ return new InvocationPendingPromise(this);
114
+ }
139
115
  }
140
116
  genericSend(send) {
141
- this.processNonCompletableEntry((vm) => {
117
+ try {
118
+ const vm = this.coreVm;
142
119
  const requestSerde = send.inputSerde ?? serde.binary;
143
120
  const parameter = requestSerde.serialize(send.parameter);
144
121
  let delay;
145
122
  if (send.delay !== undefined) {
146
123
  delay = BigInt(send.delay);
147
124
  }
148
- vm.sys_send(send.service, send.method, parameter, send.key, delay);
149
- });
125
+ const handles = vm.sys_send(send.service, send.method, parameter, send.key, send.headers
126
+ ? Object.entries(send.headers).map(([key, value]) => new WasmHeader(key, value))
127
+ : [], delay, send.idempotencyKey);
128
+ const handle = handles.invocation_id_completion_id;
129
+ const invocationId = this.createInvocationIdPromise(handle);
130
+ return {
131
+ invocationId,
132
+ };
133
+ }
134
+ catch (e) {
135
+ this.handleInvocationEndError(e);
136
+ return {
137
+ invocationId: pendingPromise(),
138
+ };
139
+ }
140
+ }
141
+ createInvocationIdPromise(handle) {
142
+ return new RestateSinglePromise(this, handle, completeUsing(InvocationIdCompleter));
150
143
  }
151
144
  serviceClient({ name }) {
152
145
  return makeRpcCallProxy((call) => this.genericCall(call), name);
@@ -171,28 +164,20 @@ export class ContextImpl {
171
164
  // and not in the promise context. To understand the semantic difference, make this function async and run the
172
165
  // UnawaitedSideEffectShouldFailSubsequentContextCall test.
173
166
  run(nameOrAction, actionSecondParameter, options) {
174
- const { name, action } = unpack(nameOrAction, actionSecondParameter);
167
+ const { name, action } = unpackRunParameters(nameOrAction, actionSecondParameter);
175
168
  const serde = options?.serde ?? defaultSerde();
169
+ // Prepare the handle
170
+ let handle;
176
171
  try {
177
- const runEnterResult = this.coreVm.sys_run_enter(name || "");
178
- // Check if the run was already executed
179
- if (typeof runEnterResult === "object" &&
180
- "ExecutedWithSuccess" in runEnterResult) {
181
- return Promise.resolve(serde.deserialize(runEnterResult.ExecutedWithSuccess));
182
- }
183
- else if (typeof runEnterResult === "object" &&
184
- "ExecutedWithFailure" in runEnterResult) {
185
- return Promise.reject(new TerminalError(runEnterResult.ExecutedWithFailure.message, {
186
- errorCode: runEnterResult.ExecutedWithFailure.code,
187
- }));
188
- }
172
+ handle = this.coreVm.sys_run(name || "");
189
173
  }
190
174
  catch (e) {
191
175
  this.handleInvocationEndError(e);
192
- return pendingPromise();
176
+ return new RestatePendingPromise(this);
193
177
  }
194
- // We wrap the rest of the execution in this closure to create a future
178
+ // Now prepare the run task
195
179
  const doRun = async () => {
180
+ // Execute the user code
196
181
  const startTime = Date.now();
197
182
  let res;
198
183
  let err;
@@ -203,28 +188,30 @@ export class ContextImpl {
203
188
  err = ensureError(e);
204
189
  }
205
190
  const attemptDuration = Date.now() - startTime;
206
- // Record the result/failure, get back the handle for the ack.
207
- let handle;
191
+ // Propose the completion to the VM
208
192
  try {
209
193
  if (err !== undefined) {
210
194
  if (err instanceof TerminalError) {
211
195
  // Record failure, go ahead
212
- handle = this.coreVm.sys_run_exit_failure({
196
+ this.coreVm.propose_run_completion_failure(handle, {
213
197
  code: err.code,
214
198
  message: err.message,
215
199
  });
216
200
  }
217
201
  else {
202
+ this.vmLogger.warn(`Error when processing ctx.run '${name}'.\n`, err);
218
203
  if (options?.retryIntervalFactor === undefined &&
219
204
  options?.initialRetryIntervalMillis === undefined &&
220
205
  options?.maxRetryAttempts === undefined &&
221
206
  options?.maxRetryDurationMillis === undefined &&
222
207
  options?.maxRetryIntervalMillis === undefined) {
223
- // If no retry option was set, simply throw the error.
224
- // This will lead to the invoker applying its retry, without the SDK overriding it.
225
- throw err;
208
+ // If no retry option was set, simply notify the error.
209
+ this.coreVm.notify_error(err.message, err.stack);
210
+ // From now on, no progress will be made.
211
+ this.invocationEndPromise.resolve();
212
+ return pendingPromise();
226
213
  }
227
- handle = this.coreVm.sys_run_exit_failure_transient(err.message, err.cause?.toString(), BigInt(attemptDuration), {
214
+ this.coreVm.propose_run_completion_failure_transient(handle, err.message, err.cause?.toString(), BigInt(attemptDuration), {
228
215
  factor: options?.retryIntervalFactor || 2.0,
229
216
  initial_interval: options?.initialRetryIntervalMillis || 50,
230
217
  max_attempts: options?.maxRetryAttempts,
@@ -234,45 +221,25 @@ export class ContextImpl {
234
221
  }
235
222
  }
236
223
  else {
224
+ this.coreVm.propose_run_completion_success(handle,
237
225
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
238
226
  // @ts-expect-error
239
- handle = this.coreVm.sys_run_exit_success(serde.serialize(res));
227
+ serde.serialize(res));
240
228
  }
241
229
  }
242
230
  catch (e) {
243
231
  this.handleInvocationEndError(e);
244
232
  return pendingPromise();
245
233
  }
246
- // Got the handle, wait for the result now (which we get once we get the ack)
247
- return await this.pollAsyncResult(handle, (asyncResultValue) => {
248
- if (typeof asyncResultValue === "object" &&
249
- "Success" in asyncResultValue) {
250
- return serde.deserialize(asyncResultValue.Success);
251
- }
252
- else if (typeof asyncResultValue === "object" &&
253
- "Failure" in asyncResultValue) {
254
- throw new TerminalError(asyncResultValue.Failure.message, {
255
- errorCode: asyncResultValue.Failure.code,
256
- });
257
- }
258
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
259
- });
234
+ await this.outputPump.awaitNextProgress();
260
235
  };
261
- return doRun();
236
+ // Register the run to execute
237
+ this.runClosuresTracker.registerRunClosure(handle, doRun);
238
+ // Return the promise
239
+ return new RestateSinglePromise(this, handle, completeUsing(SuccessWithSerde(serde), Failure));
262
240
  }
263
241
  sleep(millis) {
264
- return this.processCompletableEntry((vm) => vm.sys_sleep(BigInt(millis)), (asyncResultValue) => {
265
- if (asyncResultValue === "Empty") {
266
- // Empty
267
- return undefined;
268
- }
269
- else if ("Failure" in asyncResultValue) {
270
- throw new TerminalError(asyncResultValue.Failure.message, {
271
- errorCode: asyncResultValue.Failure.code,
272
- });
273
- }
274
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
275
- });
242
+ return this.processCompletableEntry((vm) => vm.sys_sleep(BigInt(millis)), completeUsing(VoidAsUndefined));
276
243
  }
277
244
  // -- Awakeables
278
245
  awakeable(serde) {
@@ -284,30 +251,12 @@ export class ContextImpl {
284
251
  this.handleInvocationEndError(e);
285
252
  return {
286
253
  id: "invalid",
287
- promise: new LazyContextPromise(0, this, () => pendingPromise()),
254
+ promise: new RestatePendingPromise(this),
288
255
  };
289
256
  }
290
257
  return {
291
258
  id: awakeable.id,
292
- promise: new LazyContextPromise(awakeable.handle, this, () => this.pollAsyncResult(awakeable.handle, (asyncResultValue) => {
293
- if (typeof asyncResultValue === "object" &&
294
- "Success" in asyncResultValue) {
295
- if (!serde) {
296
- return defaultSerde().deserialize(asyncResultValue.Success);
297
- }
298
- if (asyncResultValue.Success.length === 0) {
299
- return undefined;
300
- }
301
- return serde.deserialize(asyncResultValue.Success);
302
- }
303
- else if (typeof asyncResultValue === "object" &&
304
- "Failure" in asyncResultValue) {
305
- throw new TerminalError(asyncResultValue.Failure.message, {
306
- errorCode: asyncResultValue.Failure.code,
307
- });
308
- }
309
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
310
- })),
259
+ promise: new RestateSinglePromise(this, awakeable.handle, completeUsing(VoidAsUndefined, SuccessWithSerde(serde), Failure)),
311
260
  };
312
261
  }
313
262
  resolveAwakeable(id, payload, serde) {
@@ -339,7 +288,7 @@ export class ContextImpl {
339
288
  return new DurablePromiseImpl(this, name, serde);
340
289
  }
341
290
  // Used by static methods of CombineablePromise
342
- static createCombinator(combinatorType, promises) {
291
+ static createCombinator(combinatorConstructor, promises) {
343
292
  // Extract context from first promise
344
293
  const self = extractContext(promises[0]);
345
294
  if (!self) {
@@ -350,133 +299,11 @@ export class ContextImpl {
350
299
  for (const promise of promises) {
351
300
  if (extractContext(promise) !== self) {
352
301
  self.handleInvocationEndError(new Error("You're mixing up CombineablePromises from different RestateContext. This is not supported."));
353
- return pendingPromise();
302
+ return new RestatePendingPromise(self);
354
303
  }
355
304
  castedPromises.push(promise);
356
305
  }
357
- const handles = new Uint32Array(castedPromises.map((p) => p.asyncResultHandle));
358
- // From now on, lazily executes on await
359
- return new LazyPromise(async () => {
360
- let combinatorResultHandle;
361
- try {
362
- // Take output
363
- const nextOutput1 = self.coreVm.take_output();
364
- if (nextOutput1 instanceof Uint8Array) {
365
- await self.outputWriter.write(nextOutput1);
366
- }
367
- for (;;) {
368
- switch (combinatorType) {
369
- case "All":
370
- combinatorResultHandle =
371
- self.coreVm.sys_try_complete_all_combinator(handles);
372
- break;
373
- case "Any":
374
- combinatorResultHandle =
375
- self.coreVm.sys_try_complete_any_combinator(handles);
376
- break;
377
- case "AllSettled":
378
- combinatorResultHandle =
379
- self.coreVm.sys_try_complete_all_settled_combinator(handles);
380
- break;
381
- case "Race":
382
- case "OrTimeout":
383
- combinatorResultHandle =
384
- self.coreVm.sys_try_complete_race_combinator(handles);
385
- break;
386
- }
387
- // We got a result, we're done in this loop
388
- if (combinatorResultHandle !== undefined) {
389
- break;
390
- }
391
- // No result yet, await the next read
392
- await self.awaitNextRead();
393
- }
394
- // We got a result, we need to take_output to write the combinator entry, then we need to poll the result
395
- const nextOutput = self.coreVm.take_output();
396
- if (nextOutput instanceof Uint8Array) {
397
- await self.outputWriter.write(nextOutput);
398
- }
399
- }
400
- catch (e) {
401
- if (e instanceof TerminalError) {
402
- // All good, this is a recorded failure
403
- throw e;
404
- }
405
- // Not good, this is a retryable error.
406
- self.handleInvocationEndError(e);
407
- return await pendingPromise();
408
- }
409
- const handlesResult = await self.pollAsyncResult(combinatorResultHandle, (asyncResultValue) => {
410
- if (typeof asyncResultValue === "object" &&
411
- "CombinatorResult" in asyncResultValue) {
412
- return asyncResultValue.CombinatorResult;
413
- }
414
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
415
- });
416
- const promisesMap = new Map(castedPromises.map((p) => [p.asyncResultHandle, p]));
417
- // Now all we need to do is to construct the final output based on the handles,
418
- // this depends on combinators themselves.
419
- switch (combinatorType) {
420
- case "All":
421
- return this.extractAllCombinatorResult(handlesResult, promisesMap);
422
- case "Any":
423
- return this.extractAnyCombinatorResult(handlesResult, promisesMap);
424
- case "AllSettled":
425
- return this.extractAllSettledCombinatorResult(handlesResult, promisesMap);
426
- case "Race":
427
- // Just one promise succeeded
428
- return promisesMap.get(handlesResult[0]);
429
- case "OrTimeout":
430
- // The sleep promise is always the second one in the list.
431
- if (handlesResult[0] === castedPromises[1].asyncResultHandle) {
432
- return Promise.reject(new TimeoutError());
433
- }
434
- else {
435
- return promisesMap.get(handlesResult[0]);
436
- }
437
- }
438
- });
439
- }
440
- static async extractAllCombinatorResult(handlesResult, promisesMap) {
441
- // The result can either all values, or one error
442
- const resultValues = [];
443
- for (const handle of handlesResult) {
444
- try {
445
- resultValues.push(await promisesMap.get(handle));
446
- }
447
- catch (e) {
448
- return Promise.reject(e);
449
- }
450
- }
451
- return Promise.resolve(resultValues);
452
- }
453
- static async extractAnyCombinatorResult(handlesResult, promisesMap) {
454
- // The result can either be one value, or a list of errors
455
- const resultFailures = [];
456
- for (const handle of handlesResult) {
457
- try {
458
- return Promise.resolve(await promisesMap.get(handle));
459
- }
460
- catch (e) {
461
- resultFailures.push(e);
462
- }
463
- }
464
- // Giving back the cause here is completely fine, because all these errors in Aggregate error are Terminal errors!
465
- return Promise.reject(new TerminalError("All input promises failed", {
466
- cause: new AggregateError(resultFailures),
467
- }));
468
- }
469
- static async extractAllSettledCombinatorResult(handlesResult, promisesMap) {
470
- const resultValues = [];
471
- for (const handle of handlesResult) {
472
- try {
473
- resultValues.push(await promisesMap.get(handle));
474
- }
475
- catch (e) {
476
- resultValues.push(e);
477
- }
478
- }
479
- return Promise.resolve(resultValues);
306
+ return new RestateCombinatorPromise(self, combinatorConstructor, castedPromises);
480
307
  }
481
308
  // -- Various private methods
482
309
  processNonCompletableEntry(vmCall) {
@@ -487,88 +314,29 @@ export class ContextImpl {
487
314
  this.handleInvocationEndError(e);
488
315
  }
489
316
  }
490
- processCompletableEntry(vmCall, transformer) {
317
+ processCompletableEntry(vmCall, completer) {
491
318
  let handle;
492
319
  try {
493
320
  handle = vmCall(this.coreVm);
494
321
  }
495
322
  catch (e) {
496
323
  this.handleInvocationEndError(e);
497
- return new LazyContextPromise(0, this, () => pendingPromise());
498
- }
499
- return new LazyContextPromise(handle, this, () => this.pollAsyncResult(handle, transformer));
500
- }
501
- async pollAsyncResult(handle, transformer) {
502
- try {
503
- // Take output
504
- const nextOutput = this.coreVm.take_output();
505
- if (nextOutput instanceof Uint8Array) {
506
- await this.outputWriter.write(nextOutput);
507
- }
508
- // Now loop waiting for the async result
509
- let asyncResult = this.coreVm.take_async_result(handle);
510
- while (asyncResult === "NotReady") {
511
- await this.awaitNextRead();
512
- // Using notify_await_point immediately before take_async_result
513
- // makes sure the state machine will try to suspend only now,
514
- // in case there aren't other concurrent tasks trying to poll this async result.
515
- this.coreVm.notify_await_point(handle);
516
- asyncResult = this.coreVm.take_async_result(handle);
517
- }
518
- return transformer(asyncResult);
519
- }
520
- catch (e) {
521
- if (e instanceof TerminalError) {
522
- // All good, this is a recorded failure
523
- throw e;
524
- }
525
- // Not good, this is a retryable error.
526
- this.handleInvocationEndError(e);
527
- return await pendingPromise();
528
- }
529
- }
530
- // This function triggers a read on the input reader,
531
- // and will notify the caller that a read was executed
532
- // and the result was piped in the state machine.
533
- awaitNextRead() {
534
- if (this.currentRead === undefined) {
535
- // Register a new read
536
- this.currentRead = this.readNext().finally(() => {
537
- this.currentRead = undefined;
538
- });
539
- }
540
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
541
- return new Promise((resolve) => this.currentRead?.finally(resolve));
542
- }
543
- async readNext() {
544
- // Take input, and notify it to the vm
545
- let nextValue;
546
- try {
547
- nextValue = await this.inputReader.read();
548
- }
549
- catch (e) {
550
- this.handleInvocationEndError(e);
551
- return pendingPromise();
552
- }
553
- if (nextValue.value !== undefined) {
554
- this.coreVm.notify_input(nextValue.value);
555
- }
556
- if (nextValue.done) {
557
- this.coreVm.notify_input_closed();
324
+ return new RestatePendingPromise(this);
558
325
  }
326
+ return new RestateSinglePromise(this, handle, completer);
559
327
  }
560
328
  handleInvocationEndError(e) {
561
329
  const error = ensureError(e);
562
330
  if (!(error instanceof RestateError) ||
563
331
  error.code !== SUSPENDED_ERROR_CODE) {
564
- this.console.warn("Function completed with an error.\n", error);
332
+ this.vmLogger.warn("Error when processing a Restate context operation.\n", error);
565
333
  }
566
334
  this.coreVm.notify_error(error.message, error.stack);
567
335
  // From now on, no progress will be made.
568
336
  this.invocationEndPromise.resolve();
569
337
  }
570
338
  }
571
- function unpack(a, b) {
339
+ function unpackRunParameters(a, b) {
572
340
  if (typeof a === "string") {
573
341
  if (typeof b !== "function") {
574
342
  throw new TypeError("");
@@ -583,12 +351,6 @@ function unpack(a, b) {
583
351
  }
584
352
  return { action: a };
585
353
  }
586
- const RESTATE_CTX_SYMBOL = Symbol("restateContext");
587
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
588
- function extractContext(n) {
589
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
590
- return n[RESTATE_CTX_SYMBOL];
591
- }
592
354
  class DurablePromiseImpl {
593
355
  ctx;
594
356
  name;
@@ -609,108 +371,117 @@ class DurablePromiseImpl {
609
371
  }
610
372
  [Symbol.toStringTag] = "DurablePromise";
611
373
  get() {
612
- return this.ctx.processCompletableEntry((vm) => vm.sys_get_promise(this.name), (asyncResultValue) => {
613
- if (typeof asyncResultValue === "object" &&
614
- "Success" in asyncResultValue) {
615
- return this.serde.deserialize(asyncResultValue.Success);
616
- }
617
- else if (typeof asyncResultValue === "object" &&
618
- "Failure" in asyncResultValue) {
619
- throw new TerminalError(asyncResultValue.Failure.message, {
620
- errorCode: asyncResultValue.Failure.code,
621
- });
622
- }
623
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
624
- });
374
+ return this.ctx.processCompletableEntry((vm) => vm.sys_get_promise(this.name), completeUsing(SuccessWithSerde(this.serde), Failure));
625
375
  }
626
376
  peek() {
627
- return this.ctx.processCompletableEntry((vm) => vm.sys_peek_promise(this.name), (asyncResultValue) => {
628
- if (asyncResultValue === "Empty") {
629
- return undefined;
630
- }
631
- else if (typeof asyncResultValue === "object" &&
632
- "Success" in asyncResultValue) {
633
- return this.serde.deserialize(asyncResultValue.Success);
634
- }
635
- else if (typeof asyncResultValue === "object" &&
636
- "Failure" in asyncResultValue) {
637
- throw new TerminalError(asyncResultValue.Failure.message, {
638
- errorCode: asyncResultValue.Failure.code,
639
- });
640
- }
641
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
642
- });
377
+ return this.ctx.processCompletableEntry((vm) => vm.sys_peek_promise(this.name), completeUsing(VoidAsUndefined, SuccessWithSerde(this.serde), Failure));
643
378
  }
644
379
  resolve(value) {
645
- return this.ctx.processCompletableEntry((vm) => vm.sys_complete_promise_success(this.name, this.serde.serialize(value)), (asyncResultValue) => {
646
- if (asyncResultValue === "Empty") {
647
- return undefined;
648
- }
649
- else if (typeof asyncResultValue === "object" &&
650
- "Failure" in asyncResultValue) {
651
- throw new TerminalError(asyncResultValue.Failure.message, {
652
- errorCode: asyncResultValue.Failure.code,
653
- });
654
- }
655
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
656
- });
380
+ return this.ctx.processCompletableEntry((vm) => vm.sys_complete_promise_success(this.name, this.serde.serialize(value)), completeUsing(VoidAsUndefined, Failure));
657
381
  }
658
382
  reject(errorMsg) {
659
383
  return this.ctx.processCompletableEntry((vm) => vm.sys_complete_promise_failure(this.name, {
660
384
  code: INTERNAL_ERROR_CODE,
661
385
  message: errorMsg,
662
- }), (asyncResultValue) => {
663
- if (asyncResultValue === "Empty") {
664
- return undefined;
665
- }
666
- else if (typeof asyncResultValue === "object" &&
667
- "Failure" in asyncResultValue) {
668
- throw new TerminalError(asyncResultValue.Failure.message, {
669
- errorCode: asyncResultValue.Failure.code,
670
- });
671
- }
672
- throw new Error(`Unexpected variant in async result: ${JSON.stringify(asyncResultValue)}`);
673
- });
386
+ }), completeUsing(VoidAsUndefined, Failure));
674
387
  }
675
388
  }
676
- class LazyPromise {
677
- executor;
678
- _promise;
679
- constructor(executor) {
680
- this.executor = executor;
681
- }
682
- then(onfulfilled, onrejected) {
683
- this._promise = this._promise || this.executor();
684
- return this._promise.then(onfulfilled, onrejected);
685
- }
686
- catch(onrejected) {
687
- this._promise = this._promise || this.executor();
688
- return this._promise.catch(onrejected);
389
+ /// Tracker of run closures to run
390
+ export class RunClosuresTracker {
391
+ currentRunWaitPoint;
392
+ runsToExecute = new Map();
393
+ executeRun(handle) {
394
+ const runClosure = this.runsToExecute.get(handle);
395
+ if (runClosure === undefined) {
396
+ throw new Error(`Handle ${handle} doesn't exist`);
397
+ }
398
+ runClosure()
399
+ .finally(() => {
400
+ this.unblockCurrentRunWaitPoint();
401
+ })
402
+ .catch(() => { });
403
+ }
404
+ registerRunClosure(handle, runClosure) {
405
+ this.runsToExecute.set(handle, runClosure);
406
+ }
407
+ awaitNextCompletedRun() {
408
+ if (this.currentRunWaitPoint === undefined) {
409
+ this.currentRunWaitPoint = new CompletablePromise();
410
+ }
411
+ return this.currentRunWaitPoint.promise;
689
412
  }
690
- finally(onfinally) {
691
- this._promise = this._promise || this.executor();
692
- return this._promise.finally(onfinally);
413
+ unblockCurrentRunWaitPoint() {
414
+ if (this.currentRunWaitPoint !== undefined) {
415
+ const p = this.currentRunWaitPoint;
416
+ this.currentRunWaitPoint = undefined;
417
+ p.resolve();
418
+ }
693
419
  }
694
- [Symbol.toStringTag] = "LazyPromise";
695
420
  }
696
- class LazyContextPromise extends LazyPromise {
697
- asyncResultHandle;
698
- [RESTATE_CTX_SYMBOL];
699
- constructor(asyncResultHandle, ctx, executor) {
700
- super(executor);
701
- this.asyncResultHandle = asyncResultHandle;
702
- this[RESTATE_CTX_SYMBOL] = ctx;
703
- }
704
- orTimeout(millis) {
705
- return ContextImpl.createCombinator("OrTimeout", [
706
- this,
707
- this[RESTATE_CTX_SYMBOL].sleep(millis),
708
- ]);
709
- }
421
+ function completeUsing(...completers) {
422
+ return (value, prom) => {
423
+ for (const completer of completers) {
424
+ if (completer(value, prom)) {
425
+ return;
426
+ }
427
+ }
428
+ throw new Error(`Unexpected variant in async result: ${JSON.stringify(value)}`);
429
+ };
710
430
  }
711
- // A promise that is never completed
712
- function pendingPromise() {
713
- // eslint-disable-next-line @typescript-eslint/no-empty-function
714
- return new Promise(() => { });
431
+ const VoidAsNull = (value, prom) => {
432
+ if (value === "Empty") {
433
+ prom.resolve(null);
434
+ return true;
435
+ }
436
+ return false;
437
+ };
438
+ const VoidAsUndefined = (value, prom) => {
439
+ if (value === "Empty") {
440
+ prom.resolve(undefined);
441
+ return true;
442
+ }
443
+ return false;
444
+ };
445
+ function SuccessWithSerde(serde, transform) {
446
+ return (value, prom) => {
447
+ if (typeof value !== "object" || !("Success" in value)) {
448
+ return false;
449
+ }
450
+ let val;
451
+ if (serde) {
452
+ val = serde.deserialize(value.Success);
453
+ }
454
+ else {
455
+ val = defaultSerde().deserialize(value.Success);
456
+ }
457
+ if (transform) {
458
+ val = transform(val);
459
+ }
460
+ prom.resolve(val);
461
+ return true;
462
+ };
715
463
  }
464
+ const Failure = (value, prom) => {
465
+ if (typeof value === "object" && "Failure" in value) {
466
+ prom.reject(new TerminalError(value.Failure.message, {
467
+ errorCode: value.Failure.code,
468
+ }));
469
+ return true;
470
+ }
471
+ return false;
472
+ };
473
+ const StateKeys = (value, prom) => {
474
+ if (typeof value === "object" && "StateKeys" in value) {
475
+ prom.resolve(value.StateKeys);
476
+ return true;
477
+ }
478
+ return false;
479
+ };
480
+ const InvocationIdCompleter = (value, prom) => {
481
+ if (typeof value === "object" && "InvocationId" in value) {
482
+ prom.resolve(value.InvocationId);
483
+ return true;
484
+ }
485
+ return false;
486
+ };
716
487
  //# sourceMappingURL=context_impl.js.map