@restatedev/restate-sdk-testcontainers 1.11.0 → 1.12.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.
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/restate-sdk/src/common_api.d.ts +3 -1
- package/dist/restate-sdk/src/context.d.cts +16 -2
- package/dist/restate-sdk/src/context.d.cts.map +1 -1
- package/dist/restate-sdk/src/context.d.ts +16 -2
- package/dist/restate-sdk/src/context.d.ts.map +1 -1
- package/dist/restate-sdk/src/context_impl.d.ts +2 -0
- package/dist/restate-sdk/src/endpoint/endpoint.d.ts +1 -0
- package/dist/restate-sdk/src/endpoint/handlers/types.d.ts +1 -0
- package/dist/restate-sdk/src/endpoint/types.d.cts +1 -2
- package/dist/restate-sdk/src/endpoint/types.d.cts.map +1 -1
- package/dist/restate-sdk/src/endpoint/types.d.ts +1 -1
- package/dist/restate-sdk/src/endpoint.d.cts +87 -5
- package/dist/restate-sdk/src/endpoint.d.cts.map +1 -1
- package/dist/restate-sdk/src/endpoint.d.ts +87 -5
- package/dist/restate-sdk/src/endpoint.d.ts.map +1 -1
- package/dist/restate-sdk/src/hooks.d.cts +87 -0
- package/dist/restate-sdk/src/hooks.d.cts.map +1 -0
- package/dist/restate-sdk/src/hooks.d.ts +87 -0
- package/dist/restate-sdk/src/hooks.d.ts.map +1 -0
- package/dist/restate-sdk/src/index.d.cts +2 -1
- package/dist/restate-sdk/src/index.d.ts +2 -1
- package/dist/restate-sdk/src/io.d.ts +1 -0
- package/dist/restate-sdk/src/node.d.cts +2 -2
- package/dist/restate-sdk/src/node.d.ts +2 -1
- package/dist/restate-sdk/src/promises.d.ts +2 -0
- package/dist/restate-sdk/src/types/errors.d.cts +0 -4
- package/dist/restate-sdk/src/types/errors.d.cts.map +1 -1
- package/dist/restate-sdk/src/types/errors.d.ts +0 -4
- package/dist/restate-sdk/src/types/errors.d.ts.map +1 -1
- package/dist/restate-sdk/src/types/rpc.d.cts +63 -0
- package/dist/restate-sdk/src/types/rpc.d.cts.map +1 -1
- package/dist/restate-sdk/src/types/rpc.d.ts +63 -0
- package/dist/restate-sdk/src/types/rpc.d.ts.map +1 -1
- package/dist/restate_test_environment.cjs +32 -2
- package/dist/restate_test_environment.d.cts +29 -3
- package/dist/restate_test_environment.d.cts.map +1 -1
- package/dist/restate_test_environment.d.ts +29 -3
- package/dist/restate_test_environment.d.ts.map +1 -1
- package/dist/restate_test_environment.js +32 -2
- package/dist/restate_test_environment.js.map +1 -1
- package/package.json +4 -4
- package/dist/restate-sdk/src/common_api.d.cts +0 -10
|
@@ -3,10 +3,10 @@ import { Serde } from "../../restate-sdk-core/src/serde_api.cjs";
|
|
|
3
3
|
import { Duration } from "../../restate-sdk-core/src/duration.cjs";
|
|
4
4
|
import { JournalValueCodec } from "../../restate-sdk-core/src/entry_codec.cjs";
|
|
5
5
|
import { RestateError, TerminalError } from "./types/errors.cjs";
|
|
6
|
+
import { Hooks, HooksProvider, Interceptor } from "./hooks.cjs";
|
|
6
7
|
import { ObjectOptions, RetryPolicy, ServiceOptions, WorkflowOptions } from "./types/rpc.cjs";
|
|
7
|
-
import { Request, TypedState, UntypedState } from "./context.cjs";
|
|
8
|
+
import { InvocationId, Request, Target, TypedState, UntypedState } from "./context.cjs";
|
|
8
9
|
import { LogMetadata, LogSource, LoggerContext, LoggerTransport, RestateLogLevel } from "./logging/logger_transport.cjs";
|
|
9
10
|
import { DefaultServiceOptions, RestateEndpoint, RestateEndpointBase } from "./endpoint.cjs";
|
|
10
11
|
import { EndpointOptions } from "./endpoint/types.cjs";
|
|
11
|
-
import "./common_api.cjs";
|
|
12
12
|
import "http2";
|
|
@@ -3,8 +3,9 @@ import { Serde } from "../../restate-sdk-core/src/serde_api.js";
|
|
|
3
3
|
import { Duration } from "../../restate-sdk-core/src/duration.js";
|
|
4
4
|
import { JournalValueCodec } from "../../restate-sdk-core/src/entry_codec.js";
|
|
5
5
|
import { RestateError, TerminalError } from "./types/errors.js";
|
|
6
|
+
import { Hooks, HooksProvider, Interceptor } from "./hooks.js";
|
|
6
7
|
import { ObjectOptions, RetryPolicy, ServiceOptions, WorkflowOptions } from "./types/rpc.js";
|
|
7
|
-
import { Request, TypedState, UntypedState } from "./context.js";
|
|
8
|
+
import { InvocationId, Request, Target, TypedState, UntypedState } from "./context.js";
|
|
8
9
|
import { LogMetadata, LogSource, LoggerContext, LoggerTransport, RestateLogLevel } from "./logging/logger_transport.js";
|
|
9
10
|
import { DefaultServiceOptions, RestateEndpoint, RestateEndpointBase } from "./endpoint.js";
|
|
10
11
|
import { EndpointOptions } from "./endpoint/types.js";
|
|
@@ -21,10 +21,6 @@ declare class TerminalError extends RestateError {
|
|
|
21
21
|
* Error code. This should be an HTTP status code, and in case the service was invoked from the ingress, this will be propagated back to the caller.
|
|
22
22
|
*/
|
|
23
23
|
errorCode?: number;
|
|
24
|
-
/**
|
|
25
|
-
* @deprecated YOU MUST NOT USE THIS FIELD, AS IT WON'T BE RECORDED AND CAN LEAD TO NON-DETERMINISM! From the next SDK version, the constructor won't accept this field anymore.
|
|
26
|
-
*/
|
|
27
|
-
cause?: any;
|
|
28
24
|
/**
|
|
29
25
|
* Metadata key-value pairs to attach to the terminal error.
|
|
30
26
|
* These will be recorded together with error message and code.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.cts","names":[],"sources":["../../../../../restate-sdk/src/types/errors.ts"],"sourcesContent":[],"mappings":";;cA6Ea,YAAA,SAAqB,KAAA;;;;;;;;;;;;;cAerB,aAAA,SAAsB,YAAA;;sBAEN
|
|
1
|
+
{"version":3,"file":"errors.d.cts","names":[],"sources":["../../../../../restate-sdk/src/types/errors.ts"],"sourcesContent":[],"mappings":";;cA6Ea,YAAA,SAAqB,KAAA;;;;;;;;;;;;;cAerB,aAAA,SAAsB,YAAA;;sBAEN;;;;;;;;;;;;eAeZ"}
|
|
@@ -21,10 +21,6 @@ declare class TerminalError extends RestateError {
|
|
|
21
21
|
* Error code. This should be an HTTP status code, and in case the service was invoked from the ingress, this will be propagated back to the caller.
|
|
22
22
|
*/
|
|
23
23
|
errorCode?: number;
|
|
24
|
-
/**
|
|
25
|
-
* @deprecated YOU MUST NOT USE THIS FIELD, AS IT WON'T BE RECORDED AND CAN LEAD TO NON-DETERMINISM! From the next SDK version, the constructor won't accept this field anymore.
|
|
26
|
-
*/
|
|
27
|
-
cause?: any;
|
|
28
24
|
/**
|
|
29
25
|
* Metadata key-value pairs to attach to the terminal error.
|
|
30
26
|
* These will be recorded together with error message and code.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","names":[],"sources":["../../../../../restate-sdk/src/types/errors.ts"],"sourcesContent":[],"mappings":";;cA6Ea,YAAA,SAAqB,KAAA;;;;;;;;;;;;;cAerB,aAAA,SAAsB,YAAA;;sBAEN
|
|
1
|
+
{"version":3,"file":"errors.d.ts","names":[],"sources":["../../../../../restate-sdk/src/types/errors.ts"],"sourcesContent":[],"mappings":";;cA6Ea,YAAA,SAAqB,KAAA;;;;;;;;;;;;;cAerB,aAAA,SAAsB,YAAA;;sBAEN;;;;;;;;;;;;eAeZ"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Serde } from "../../../restate-sdk-core/src/serde_api.cjs";
|
|
2
2
|
import { Duration } from "../../../restate-sdk-core/src/duration.cjs";
|
|
3
3
|
import { TerminalError } from "./errors.cjs";
|
|
4
|
+
import { HooksProvider } from "../hooks.cjs";
|
|
4
5
|
|
|
5
6
|
//#region ../restate-sdk/src/types/rpc.d.ts
|
|
6
7
|
|
|
@@ -129,6 +130,68 @@ type ServiceOptions = {
|
|
|
129
130
|
* If not provided, defaults to `serde.json`.
|
|
130
131
|
*/
|
|
131
132
|
serde?: Serde<any>;
|
|
133
|
+
/**
|
|
134
|
+
* Hooks providers for this service. Service-level hooks wrap outermost —
|
|
135
|
+
* they run before handler-level hooks. Both levels are merged: service
|
|
136
|
+
* hooks first, then handler hooks. Within each level, hooks execute in
|
|
137
|
+
* array order: for `[A, B]`: A before → B before → handler → B after → A after.
|
|
138
|
+
*
|
|
139
|
+
* The `handler` interceptor fires on every attempt. The `run` interceptor
|
|
140
|
+
* fires only when the `ctx.run()` closure actually executes — replayed
|
|
141
|
+
* runs (already in the journal) are skipped.
|
|
142
|
+
*
|
|
143
|
+
* Errors thrown at any point (before or after `next()`) affect the invocation:
|
|
144
|
+
* {@link TerminalError} fails immediately, any other error triggers a retry.
|
|
145
|
+
* On suspension or pause, `next()` also rejects — do any cleanup and rethrow.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* const myService = restate.service({
|
|
150
|
+
* name: "MyService",
|
|
151
|
+
* handlers: { greet: async (ctx, name) => `Hello, ${name}!` },
|
|
152
|
+
* options: {
|
|
153
|
+
* hooks: [
|
|
154
|
+
* (ctx) => ({
|
|
155
|
+
* interceptor: {
|
|
156
|
+
* handler: async (next) => {
|
|
157
|
+
* console.log(`before ${ctx.request.target}`);
|
|
158
|
+
* try {
|
|
159
|
+
* await next();
|
|
160
|
+
* console.log(`after ${ctx.request.target}`);
|
|
161
|
+
* } catch (e) {
|
|
162
|
+
* console.log(`error ${ctx.request.target}: ${e}`);
|
|
163
|
+
* // Always rethrow — swallowing the error changes the
|
|
164
|
+
* // invocation outcome. You can also throw a different
|
|
165
|
+
* // error (e.g. TerminalError to fail immediately).
|
|
166
|
+
* throw e;
|
|
167
|
+
* }
|
|
168
|
+
* },
|
|
169
|
+
* run: async (name, next) => {
|
|
170
|
+
* console.log(` before run "${name}"`);
|
|
171
|
+
* try {
|
|
172
|
+
* await next();
|
|
173
|
+
* console.log(` after run "${name}"`);
|
|
174
|
+
* } catch (e) {
|
|
175
|
+
* console.log(` error run "${name}": ${e}`);
|
|
176
|
+
* throw e;
|
|
177
|
+
* }
|
|
178
|
+
* },
|
|
179
|
+
* },
|
|
180
|
+
* }),
|
|
181
|
+
* ],
|
|
182
|
+
* },
|
|
183
|
+
* });
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
hooks?: HooksProvider[];
|
|
187
|
+
/**
|
|
188
|
+
* When set to `true`, the SDK will stop automatically propagating cancellations when awaiting {@link RestatePromise}s.
|
|
189
|
+
*
|
|
190
|
+
* Instead, the user code must explicitly listen to cancellations using the `ctx.cancellation()` API in {@link ContextInternal}.
|
|
191
|
+
*
|
|
192
|
+
* @experimental
|
|
193
|
+
*/
|
|
194
|
+
explicitCancellation?: boolean;
|
|
132
195
|
};
|
|
133
196
|
type ObjectOptions = ServiceOptions & {
|
|
134
197
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.d.cts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rpc.d.cts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":";;;;;;;KA+0BY,WAAA;;;;;;;;;;;;;;;;;;;;oBAsBQ;;;;;;;gBAQJ;;;;;;KAQJ,cAAA;;;;;;yBAMa;;;;;;;;qBASJ;;;;;;;;;;;sBAYC;;;;;;;;;;;;iBAaL;;;;;;;;;;;gBAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAsCoB;;;;;;UAO1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuDA;;;;;;;;;;KAuGE,aAAA,GAAgB;;;;;;;;;KA4HhB,eAAA,GAAkB;;;;;;;sBAOR"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Serde } from "../../../restate-sdk-core/src/serde_api.js";
|
|
2
2
|
import { Duration } from "../../../restate-sdk-core/src/duration.js";
|
|
3
3
|
import { TerminalError } from "./errors.js";
|
|
4
|
+
import { HooksProvider } from "../hooks.js";
|
|
4
5
|
|
|
5
6
|
//#region ../restate-sdk/src/types/rpc.d.ts
|
|
6
7
|
|
|
@@ -129,6 +130,68 @@ type ServiceOptions = {
|
|
|
129
130
|
* If not provided, defaults to `serde.json`.
|
|
130
131
|
*/
|
|
131
132
|
serde?: Serde<any>;
|
|
133
|
+
/**
|
|
134
|
+
* Hooks providers for this service. Service-level hooks wrap outermost —
|
|
135
|
+
* they run before handler-level hooks. Both levels are merged: service
|
|
136
|
+
* hooks first, then handler hooks. Within each level, hooks execute in
|
|
137
|
+
* array order: for `[A, B]`: A before → B before → handler → B after → A after.
|
|
138
|
+
*
|
|
139
|
+
* The `handler` interceptor fires on every attempt. The `run` interceptor
|
|
140
|
+
* fires only when the `ctx.run()` closure actually executes — replayed
|
|
141
|
+
* runs (already in the journal) are skipped.
|
|
142
|
+
*
|
|
143
|
+
* Errors thrown at any point (before or after `next()`) affect the invocation:
|
|
144
|
+
* {@link TerminalError} fails immediately, any other error triggers a retry.
|
|
145
|
+
* On suspension or pause, `next()` also rejects — do any cleanup and rethrow.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* const myService = restate.service({
|
|
150
|
+
* name: "MyService",
|
|
151
|
+
* handlers: { greet: async (ctx, name) => `Hello, ${name}!` },
|
|
152
|
+
* options: {
|
|
153
|
+
* hooks: [
|
|
154
|
+
* (ctx) => ({
|
|
155
|
+
* interceptor: {
|
|
156
|
+
* handler: async (next) => {
|
|
157
|
+
* console.log(`before ${ctx.request.target}`);
|
|
158
|
+
* try {
|
|
159
|
+
* await next();
|
|
160
|
+
* console.log(`after ${ctx.request.target}`);
|
|
161
|
+
* } catch (e) {
|
|
162
|
+
* console.log(`error ${ctx.request.target}: ${e}`);
|
|
163
|
+
* // Always rethrow — swallowing the error changes the
|
|
164
|
+
* // invocation outcome. You can also throw a different
|
|
165
|
+
* // error (e.g. TerminalError to fail immediately).
|
|
166
|
+
* throw e;
|
|
167
|
+
* }
|
|
168
|
+
* },
|
|
169
|
+
* run: async (name, next) => {
|
|
170
|
+
* console.log(` before run "${name}"`);
|
|
171
|
+
* try {
|
|
172
|
+
* await next();
|
|
173
|
+
* console.log(` after run "${name}"`);
|
|
174
|
+
* } catch (e) {
|
|
175
|
+
* console.log(` error run "${name}": ${e}`);
|
|
176
|
+
* throw e;
|
|
177
|
+
* }
|
|
178
|
+
* },
|
|
179
|
+
* },
|
|
180
|
+
* }),
|
|
181
|
+
* ],
|
|
182
|
+
* },
|
|
183
|
+
* });
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
hooks?: HooksProvider[];
|
|
187
|
+
/**
|
|
188
|
+
* When set to `true`, the SDK will stop automatically propagating cancellations when awaiting {@link RestatePromise}s.
|
|
189
|
+
*
|
|
190
|
+
* Instead, the user code must explicitly listen to cancellations using the `ctx.cancellation()` API in {@link ContextInternal}.
|
|
191
|
+
*
|
|
192
|
+
* @experimental
|
|
193
|
+
*/
|
|
194
|
+
explicitCancellation?: boolean;
|
|
132
195
|
};
|
|
133
196
|
type ObjectOptions = ServiceOptions & {
|
|
134
197
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.d.ts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rpc.d.ts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":";;;;;;;KA+0BY,WAAA;;;;;;;;;;;;;;;;;;;;oBAsBQ;;;;;;;gBAQJ;;;;;;KAQJ,cAAA;;;;;;yBAMa;;;;;;;;qBASJ;;;;;;;;;;;sBAYC;;;;;;;;;;;;iBAaL;;;;;;;;;;;gBAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAsCoB;;;;;;UAO1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuDA;;;;;;;;;;KAuGE,aAAA,GAAgB;;;;;;;;;KA4HhB,eAAA,GAAkB;;;;;;;sBAOR"}
|
|
@@ -110,15 +110,45 @@ var RestateTestEnvironment = class RestateTestEnvironment {
|
|
|
110
110
|
await this.startedRestateContainer.stop();
|
|
111
111
|
this.startedRestateHttpServer.close();
|
|
112
112
|
}
|
|
113
|
-
static async start(param, restateContainerFactory
|
|
113
|
+
static async start(param, restateContainerFactory) {
|
|
114
|
+
let containerFactory;
|
|
115
|
+
if (restateContainerFactory) containerFactory = restateContainerFactory;
|
|
116
|
+
else if (typeof param !== "function") containerFactory = () => {
|
|
117
|
+
const container = new RestateContainer();
|
|
118
|
+
if (param.alwaysReplay) container.alwaysReplay();
|
|
119
|
+
if (param.disableRetries) container.disableRetries();
|
|
120
|
+
return container;
|
|
121
|
+
};
|
|
122
|
+
else containerFactory = () => new RestateContainer();
|
|
114
123
|
const startedRestateHttpServer = typeof param === "function" ? await prepareRestateEndpoint(param) : await prepareRestateEndpoint(param);
|
|
115
|
-
return new RestateTestEnvironment(startedRestateHttpServer, await prepareRestateTestContainer(startedRestateHttpServer.address().port,
|
|
124
|
+
return new RestateTestEnvironment(startedRestateHttpServer, await prepareRestateTestContainer(startedRestateHttpServer.address().port, containerFactory));
|
|
116
125
|
}
|
|
117
126
|
};
|
|
118
127
|
var RestateContainer = class extends testcontainers.GenericContainer {
|
|
119
128
|
constructor(version = "latest") {
|
|
120
129
|
super(`docker.io/restatedev/restate:${version}`);
|
|
121
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Forces restate-server to always replay on a suspension point.
|
|
133
|
+
* This is useful to hunt non-deterministic bugs that might prevent
|
|
134
|
+
* your code from replaying correctly.
|
|
135
|
+
*/
|
|
136
|
+
alwaysReplay() {
|
|
137
|
+
this.withEnvironment({ RESTATE_WORKER__INVOKER__INACTIVITY_TIMEOUT: "0s" });
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Disables retries in the restate-server invoker.
|
|
142
|
+
* This is useful in tests so that failures surface immediately
|
|
143
|
+
* instead of hanging through retry backoff.
|
|
144
|
+
*/
|
|
145
|
+
disableRetries() {
|
|
146
|
+
this.withEnvironment({
|
|
147
|
+
RESTATE_DEFAULT_RETRY_POLICY__MAX_ATTEMPTS: "1",
|
|
148
|
+
RESTATE_DEFAULT_RETRY_POLICY__ON_MAX_ATTEMPTS: "kill"
|
|
149
|
+
});
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
122
152
|
};
|
|
123
153
|
var StateProxy = class {
|
|
124
154
|
constructor(adminAPIBaseUrl, service, serviceKey) {
|
|
@@ -8,6 +8,20 @@ import * as http2 from "http2";
|
|
|
8
8
|
import { GenericContainer, StartedTestContainer } from "testcontainers";
|
|
9
9
|
|
|
10
10
|
//#region src/restate_test_environment.d.ts
|
|
11
|
+
interface TestEnvironmentOptions extends EndpointOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Forces restate-server to always replay on a suspension point.
|
|
14
|
+
* This is useful to hunt non-deterministic bugs that might prevent
|
|
15
|
+
* your code from replaying correctly.
|
|
16
|
+
*/
|
|
17
|
+
alwaysReplay?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Disables retries in the restate-server invoker.
|
|
20
|
+
* This is useful in tests so that failures surface immediately
|
|
21
|
+
* instead of hanging through retry backoff.
|
|
22
|
+
*/
|
|
23
|
+
disableRetries?: boolean;
|
|
24
|
+
}
|
|
11
25
|
declare class RestateTestEnvironment {
|
|
12
26
|
readonly startedRestateHttpServer: http2.Http2Server;
|
|
13
27
|
readonly startedRestateContainer: StartedTestContainer;
|
|
@@ -18,17 +32,29 @@ declare class RestateTestEnvironment {
|
|
|
18
32
|
stop(): Promise<void>;
|
|
19
33
|
/**
|
|
20
34
|
*
|
|
21
|
-
* @deprecated Please use {@link
|
|
35
|
+
* @deprecated Please use {@link TestEnvironmentOptions} instead of this.
|
|
22
36
|
* @example
|
|
23
37
|
* ```
|
|
24
38
|
* RestateTestEnvironment.start({ services: [mysService] })
|
|
25
39
|
* ```
|
|
26
40
|
*/
|
|
27
41
|
static start(mountServicesFn: (server: RestateEndpoint) => void, restateContainerFactory?: () => GenericContainer): Promise<RestateTestEnvironment>;
|
|
28
|
-
static start(options:
|
|
42
|
+
static start(options: TestEnvironmentOptions, restateContainerFactory?: () => GenericContainer): Promise<RestateTestEnvironment>;
|
|
29
43
|
}
|
|
30
44
|
declare class RestateContainer extends GenericContainer {
|
|
31
45
|
constructor(version?: string);
|
|
46
|
+
/**
|
|
47
|
+
* Forces restate-server to always replay on a suspension point.
|
|
48
|
+
* This is useful to hunt non-deterministic bugs that might prevent
|
|
49
|
+
* your code from replaying correctly.
|
|
50
|
+
*/
|
|
51
|
+
alwaysReplay(): this;
|
|
52
|
+
/**
|
|
53
|
+
* Disables retries in the restate-server invoker.
|
|
54
|
+
* This is useful in tests so that failures surface immediately
|
|
55
|
+
* instead of hanging through retry backoff.
|
|
56
|
+
*/
|
|
57
|
+
disableRetries(): this;
|
|
32
58
|
}
|
|
33
59
|
declare class StateProxy<TState extends TypedState> {
|
|
34
60
|
private adminAPIBaseUrl;
|
|
@@ -43,5 +69,5 @@ declare class StateProxy<TState extends TypedState> {
|
|
|
43
69
|
private setAllRaw;
|
|
44
70
|
}
|
|
45
71
|
//#endregion
|
|
46
|
-
export { RestateContainer, RestateTestEnvironment, StateProxy };
|
|
72
|
+
export { RestateContainer, RestateTestEnvironment, StateProxy, TestEnvironmentOptions };
|
|
47
73
|
//# sourceMappingURL=restate_test_environment.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restate_test_environment.d.cts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"restate_test_environment.d.cts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -8,6 +8,20 @@ import { GenericContainer, StartedTestContainer } from "testcontainers";
|
|
|
8
8
|
import * as http2 from "http2";
|
|
9
9
|
|
|
10
10
|
//#region src/restate_test_environment.d.ts
|
|
11
|
+
interface TestEnvironmentOptions extends EndpointOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Forces restate-server to always replay on a suspension point.
|
|
14
|
+
* This is useful to hunt non-deterministic bugs that might prevent
|
|
15
|
+
* your code from replaying correctly.
|
|
16
|
+
*/
|
|
17
|
+
alwaysReplay?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Disables retries in the restate-server invoker.
|
|
20
|
+
* This is useful in tests so that failures surface immediately
|
|
21
|
+
* instead of hanging through retry backoff.
|
|
22
|
+
*/
|
|
23
|
+
disableRetries?: boolean;
|
|
24
|
+
}
|
|
11
25
|
declare class RestateTestEnvironment {
|
|
12
26
|
readonly startedRestateHttpServer: http2.Http2Server;
|
|
13
27
|
readonly startedRestateContainer: StartedTestContainer;
|
|
@@ -18,17 +32,29 @@ declare class RestateTestEnvironment {
|
|
|
18
32
|
stop(): Promise<void>;
|
|
19
33
|
/**
|
|
20
34
|
*
|
|
21
|
-
* @deprecated Please use {@link
|
|
35
|
+
* @deprecated Please use {@link TestEnvironmentOptions} instead of this.
|
|
22
36
|
* @example
|
|
23
37
|
* ```
|
|
24
38
|
* RestateTestEnvironment.start({ services: [mysService] })
|
|
25
39
|
* ```
|
|
26
40
|
*/
|
|
27
41
|
static start(mountServicesFn: (server: RestateEndpoint) => void, restateContainerFactory?: () => GenericContainer): Promise<RestateTestEnvironment>;
|
|
28
|
-
static start(options:
|
|
42
|
+
static start(options: TestEnvironmentOptions, restateContainerFactory?: () => GenericContainer): Promise<RestateTestEnvironment>;
|
|
29
43
|
}
|
|
30
44
|
declare class RestateContainer extends GenericContainer {
|
|
31
45
|
constructor(version?: string);
|
|
46
|
+
/**
|
|
47
|
+
* Forces restate-server to always replay on a suspension point.
|
|
48
|
+
* This is useful to hunt non-deterministic bugs that might prevent
|
|
49
|
+
* your code from replaying correctly.
|
|
50
|
+
*/
|
|
51
|
+
alwaysReplay(): this;
|
|
52
|
+
/**
|
|
53
|
+
* Disables retries in the restate-server invoker.
|
|
54
|
+
* This is useful in tests so that failures surface immediately
|
|
55
|
+
* instead of hanging through retry backoff.
|
|
56
|
+
*/
|
|
57
|
+
disableRetries(): this;
|
|
32
58
|
}
|
|
33
59
|
declare class StateProxy<TState extends TypedState> {
|
|
34
60
|
private adminAPIBaseUrl;
|
|
@@ -43,5 +69,5 @@ declare class StateProxy<TState extends TypedState> {
|
|
|
43
69
|
private setAllRaw;
|
|
44
70
|
}
|
|
45
71
|
//#endregion
|
|
46
|
-
export { RestateContainer, RestateTestEnvironment, StateProxy };
|
|
72
|
+
export { RestateContainer, RestateTestEnvironment, StateProxy, TestEnvironmentOptions };
|
|
47
73
|
//# sourceMappingURL=restate_test_environment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restate_test_environment.d.ts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"restate_test_environment.d.ts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -105,15 +105,45 @@ var RestateTestEnvironment = class RestateTestEnvironment {
|
|
|
105
105
|
await this.startedRestateContainer.stop();
|
|
106
106
|
this.startedRestateHttpServer.close();
|
|
107
107
|
}
|
|
108
|
-
static async start(param, restateContainerFactory
|
|
108
|
+
static async start(param, restateContainerFactory) {
|
|
109
|
+
let containerFactory;
|
|
110
|
+
if (restateContainerFactory) containerFactory = restateContainerFactory;
|
|
111
|
+
else if (typeof param !== "function") containerFactory = () => {
|
|
112
|
+
const container = new RestateContainer();
|
|
113
|
+
if (param.alwaysReplay) container.alwaysReplay();
|
|
114
|
+
if (param.disableRetries) container.disableRetries();
|
|
115
|
+
return container;
|
|
116
|
+
};
|
|
117
|
+
else containerFactory = () => new RestateContainer();
|
|
109
118
|
const startedRestateHttpServer = typeof param === "function" ? await prepareRestateEndpoint(param) : await prepareRestateEndpoint(param);
|
|
110
|
-
return new RestateTestEnvironment(startedRestateHttpServer, await prepareRestateTestContainer(startedRestateHttpServer.address().port,
|
|
119
|
+
return new RestateTestEnvironment(startedRestateHttpServer, await prepareRestateTestContainer(startedRestateHttpServer.address().port, containerFactory));
|
|
111
120
|
}
|
|
112
121
|
};
|
|
113
122
|
var RestateContainer = class extends GenericContainer {
|
|
114
123
|
constructor(version = "latest") {
|
|
115
124
|
super(`docker.io/restatedev/restate:${version}`);
|
|
116
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Forces restate-server to always replay on a suspension point.
|
|
128
|
+
* This is useful to hunt non-deterministic bugs that might prevent
|
|
129
|
+
* your code from replaying correctly.
|
|
130
|
+
*/
|
|
131
|
+
alwaysReplay() {
|
|
132
|
+
this.withEnvironment({ RESTATE_WORKER__INVOKER__INACTIVITY_TIMEOUT: "0s" });
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Disables retries in the restate-server invoker.
|
|
137
|
+
* This is useful in tests so that failures surface immediately
|
|
138
|
+
* instead of hanging through retry backoff.
|
|
139
|
+
*/
|
|
140
|
+
disableRetries() {
|
|
141
|
+
this.withEnvironment({
|
|
142
|
+
RESTATE_DEFAULT_RETRY_POLICY__MAX_ATTEMPTS: "1",
|
|
143
|
+
RESTATE_DEFAULT_RETRY_POLICY__ON_MAX_ATTEMPTS: "kill"
|
|
144
|
+
});
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
117
147
|
};
|
|
118
148
|
var StateProxy = class {
|
|
119
149
|
constructor(adminAPIBaseUrl, service, serviceKey) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restate_test_environment.js","names":["handler: (\n request: http2.Http2ServerRequest,\n response: http2.Http2ServerResponse\n ) => void","startedRestateHttpServer: http2.Http2Server","startedRestateContainer: StartedTestContainer","adminAPIBaseUrl: string","service: string","serviceKey: string","serde","value"],"sources":["../src/restate_test_environment.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport {\n endpoint,\n createEndpointHandler,\n serde,\n} from \"@restatedev/restate-sdk\";\nimport type {\n TypedState,\n UntypedState,\n Serde,\n RestateEndpoint,\n VirtualObjectDefinition,\n WorkflowDefinition,\n EndpointOptions,\n} from \"@restatedev/restate-sdk\";\n\nimport {\n GenericContainer,\n type StartedTestContainer,\n TestContainers,\n Wait,\n type WaitStrategy,\n type BoundPorts,\n getContainerRuntimeClient,\n} from \"testcontainers\";\nimport { tableFromIPC } from \"apache-arrow\";\nimport * as http2 from \"http2\";\nimport type * as net from \"net\";\n\n/**\n * Custom wait strategy that waits for Restate partitions to be ready by\n * executing a SQL query against the admin API. This ensures all partitions\n * are initialized and queryable before the container is considered ready.\n */\nclass PartitionsReadyWaitStrategy implements WaitStrategy {\n private startupTimeoutMs = 60_000;\n private startupTimeoutSet = false;\n private readonly port: number;\n private readonly pollIntervalMs: number;\n\n constructor(port = 9070, pollIntervalMs = 200) {\n this.port = port;\n this.pollIntervalMs = pollIntervalMs;\n }\n\n public withStartupTimeout(startupTimeoutMs: number): this {\n this.startupTimeoutMs = startupTimeoutMs;\n this.startupTimeoutSet = true;\n return this;\n }\n\n public isStartupTimeoutSet(): boolean {\n return this.startupTimeoutSet;\n }\n\n public getStartupTimeout(): number {\n return this.startupTimeoutMs;\n }\n\n public async waitUntilReady(\n container: { id: string },\n boundPorts: BoundPorts\n ): Promise<void> {\n const client = await getContainerRuntimeClient();\n const host = client.info.containerRuntime.host;\n const mappedPort = boundPorts.getBinding(this.port);\n const adminUrl = `http://${host}:${mappedPort}`;\n\n const startTime = Date.now();\n\n while (Date.now() - startTime < this.startupTimeoutMs) {\n try {\n const res = await fetch(`${adminUrl}/query`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: \"SELECT count(1) FROM sys_invocation\",\n }),\n });\n\n if (res.ok) {\n // Partitions are ready\n return;\n }\n } catch {\n // Ignore errors, keep polling\n }\n\n await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));\n }\n\n throw new Error(\n `Restate partitions not ready after ${this.startupTimeoutMs}ms`\n );\n }\n}\n\n// Prepare the restate server\nasync function prepareRestateEndpoint(\n param: (server: RestateEndpoint) => void\n): Promise<http2.Http2Server>;\nasync function prepareRestateEndpoint(\n param: EndpointOptions\n): Promise<http2.Http2Server>;\nasync function prepareRestateEndpoint(\n param: EndpointOptions | ((server: RestateEndpoint) => void)\n): Promise<http2.Http2Server> {\n // Prepare RestateServer\n let handler: (\n request: http2.Http2ServerRequest,\n response: http2.Http2ServerResponse\n ) => void;\n if (typeof param === \"function\") {\n const restateEndpoint = endpoint();\n param(restateEndpoint);\n handler = restateEndpoint.http2Handler();\n } else {\n handler = createEndpointHandler(param);\n }\n\n // Start HTTP2 server on random port\n const restateHttpServer = http2.createServer(handler);\n await new Promise((resolve, reject) => {\n restateHttpServer\n .listen(0)\n .once(\"listening\", resolve)\n .once(\"error\", reject);\n });\n const restateServerPort = (restateHttpServer.address() as net.AddressInfo)\n .port;\n console.info(`Restate container listening on port ${restateServerPort}`);\n\n return restateHttpServer;\n}\n\n// Prepare the restate testcontainer\nasync function prepareRestateTestContainer(\n restateServerPort: number,\n restateContainerFactory: () => GenericContainer\n): Promise<StartedTestContainer> {\n const restateContainer = restateContainerFactory()\n // Expose ports\n .withExposedPorts(8080, 9070)\n // Wait start on health checks and partition readiness\n .withWaitStrategy(\n Wait.forAll([\n Wait.forHttp(\"/restate/health\", 8080),\n Wait.forHttp(\"/health\", 9070),\n new PartitionsReadyWaitStrategy(),\n ])\n );\n\n // This MUST be executed before starting the restate container\n // Expose host port to access the restate server\n await TestContainers.exposeHostPorts(restateServerPort);\n\n // Start restate container\n const startedRestateContainer = await restateContainer.start();\n\n // From now on, if something fails, stop the container to cleanup the environment\n try {\n console.info(\n `Registering services at http://host.testcontainers.internal:${restateServerPort}...`\n );\n\n // Register this service endpoint\n const res = await fetch(\n `http://${startedRestateContainer.getHost()}:${startedRestateContainer.getMappedPort(\n 9070\n )}/deployments`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n // See https://node.testcontainers.org/features/networking/#expose-host-ports-to-container\n uri: `http://host.testcontainers.internal:${restateServerPort}`,\n }),\n }\n );\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(\n `Error ${res.status} during registration: ${badResponse}`\n );\n }\n\n const resp = (await res.json()) as { services: { name: string }[] };\n console.info(\n \"Registered services:\",\n resp?.services?.map((s) => s.name)\n );\n return startedRestateContainer;\n } catch (e) {\n await startedRestateContainer.stop();\n throw e;\n }\n}\n\nexport class RestateTestEnvironment {\n constructor(\n readonly startedRestateHttpServer: http2.Http2Server,\n readonly startedRestateContainer: StartedTestContainer\n ) {}\n\n public baseUrl(): string {\n return `http://${this.startedRestateContainer.getHost()}:${this.startedRestateContainer.getMappedPort(\n 8080\n )}`;\n }\n\n public adminAPIBaseUrl(): string {\n return `http://${this.startedRestateContainer.getHost()}:${this.startedRestateContainer.getMappedPort(\n 9070\n )}`;\n }\n\n // Create a handle that allows read/write of state under a given Virtual Object/Workflow key.\n public stateOf<TState extends TypedState = UntypedState>(\n service:\n | VirtualObjectDefinition<string, unknown>\n | WorkflowDefinition<string, unknown>,\n key: string\n ): StateProxy<TState> {\n return new StateProxy(this.adminAPIBaseUrl(), service.name, key);\n }\n\n public async stop() {\n await this.startedRestateContainer.stop();\n this.startedRestateHttpServer.close();\n }\n\n /**\n *\n * @deprecated Please use {@link EndpointOptions} instead of this.\n * @example\n * ```\n * RestateTestEnvironment.start({ services: [mysService] })\n * ```\n */\n public static async start(\n mountServicesFn: (server: RestateEndpoint) => void,\n restateContainerFactory?: () => GenericContainer\n ): Promise<RestateTestEnvironment>;\n public static async start(\n options: EndpointOptions,\n restateContainerFactory?: () => GenericContainer\n ): Promise<RestateTestEnvironment>;\n public static async start(\n param: EndpointOptions | ((server: RestateEndpoint) => void),\n restateContainerFactory: () => GenericContainer = () =>\n new RestateContainer()\n ): Promise<RestateTestEnvironment> {\n const startedRestateHttpServer =\n typeof param === \"function\"\n ? await prepareRestateEndpoint(param)\n : await prepareRestateEndpoint(param);\n const startedRestateContainer = await prepareRestateTestContainer(\n (startedRestateHttpServer.address() as net.AddressInfo).port,\n restateContainerFactory\n );\n return new RestateTestEnvironment(\n startedRestateHttpServer,\n startedRestateContainer\n );\n }\n}\n\nexport class RestateContainer extends GenericContainer {\n constructor(version = \"latest\") {\n super(`docker.io/restatedev/restate:${version}`);\n }\n}\nexport class StateProxy<TState extends TypedState> {\n constructor(\n private adminAPIBaseUrl: string,\n private service: string,\n private serviceKey: string\n ) {}\n\n // Read a single value from state under a given Virtual Object or Workflow key\n public async get<TValue, TKey extends keyof TState = string>(\n name: TState extends UntypedState ? string : TKey,\n serde?: Serde<TState extends UntypedState ? TValue : TState[TKey]>\n ): Promise<(TState extends UntypedState ? TValue : TState[TKey]) | null> {\n serde = serde ?? defaultSerde();\n\n const res = await fetch(`${this.adminAPIBaseUrl}/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query: `SELECT value from state where service_name = '${\n this.service\n }' and service_key = '${this.serviceKey}' and key = '${String(name)}';`,\n }),\n });\n\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(`Error ${res.status} during read state: ${badResponse}`);\n }\n\n // eslint-disable-next-line @typescript-eslint/await-thenable\n const table = (await tableFromIPC(res.body)).toArray() as {\n key: string;\n value: Uint8Array;\n }[];\n\n if (table.length === 0) {\n return null;\n }\n\n return serde.deserialize(table[0]!.value);\n }\n\n // Read all values from state under a given Virtual Object or Workflow key\n public async getAll<TValues extends TypedState>(\n serde?: Serde<\n TState extends UntypedState\n ? TValues[keyof TValues]\n : TState[keyof TState]\n >\n ): Promise<TState extends UntypedState ? TValues : TState> {\n serde = serde ?? defaultSerde();\n\n const items = await this.getAllRaw();\n\n return Object.fromEntries(\n items.map(({ key, value }) => {\n return [key, serde.deserialize(value)];\n })\n ) as TState extends UntypedState ? TValues : TState;\n }\n\n private async getAllRaw(): Promise<{ key: string; value: Uint8Array }[]> {\n const res = await fetch(`${this.adminAPIBaseUrl}/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query: `SELECT key, value from state where service_name = '${this.service}' and service_key = '${this.serviceKey}';`,\n }),\n });\n\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(`Error ${res.status} during read state: ${badResponse}`);\n }\n\n // eslint-disable-next-line @typescript-eslint/await-thenable\n const table = (await tableFromIPC(res.body)).toArray() as {\n key: string;\n value: Uint8Array;\n }[];\n\n return table;\n }\n\n // Asynchronously set a single value from state under a given Virtual Object or Workflow key.\n // This will first read all values, then insert the update and submit the new set of values to Restate;\n // as such it is possible to overwrite changes that happened between the read and the mutation being applied.\n // A successful return from this function does not imply that the set has finished, only that the mutation\n // was submitted to Restate for processing.\n public async set<TValue, TKey extends keyof TState = string>(\n name: TState extends UntypedState ? string : TKey,\n value: TState extends UntypedState ? TValue : TState[TKey],\n serde?: Serde<TState extends UntypedState ? TValue : TState[TKey]>\n ): Promise<void> {\n serde = serde ?? defaultSerde();\n const serialisedValue = serde.serialize(value);\n\n const items = await this.getAllRaw();\n\n items.push({ key: String(name), value: serialisedValue });\n\n await this.setAllRaw(items.map(({ key, value }) => [key, value]));\n }\n\n // Asynchronously set all state values under a given Virtual Object or Workflow key.\n // A successful return from this function does not imply that the set has finished,\n // only that the mutation was submitted to Restate for processing.\n public async setAll<TValues extends TypedState>(\n values: TState extends UntypedState ? TValues : TState,\n serde?: Serde<\n TState extends UntypedState\n ? TValues[keyof TValues]\n : TState[keyof TState]\n >\n ) {\n serde = serde ?? defaultSerde();\n\n return this.setAllRaw(\n Object.entries<\n TState extends UntypedState\n ? TValues[keyof TValues]\n : TState[keyof TState]\n >(values).map(([key, value]) => {\n return [key, serde.serialize(value)];\n })\n );\n }\n\n private async setAllRaw(\n entries: [key: string, value: Uint8Array][],\n version?: string\n ) {\n const res = await fetch(\n `${this.adminAPIBaseUrl}/services/${this.service}/state`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(\n {\n version,\n object_key: this.serviceKey,\n new_state: Object.fromEntries(entries),\n },\n (key, value) => {\n if (value instanceof Uint8Array) {\n return Array.from(value);\n } else {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value;\n }\n }\n ),\n }\n );\n\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(\n `Error ${res.status} during modify state: ${badResponse}`\n );\n }\n }\n}\n\nexport const defaultSerde = <T>(): Serde<T> => {\n return serde.json as Serde<T>;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"restate_test_environment.js","names":["handler: (\n request: http2.Http2ServerRequest,\n response: http2.Http2ServerResponse\n ) => void","startedRestateHttpServer: http2.Http2Server","startedRestateContainer: StartedTestContainer","containerFactory: () => GenericContainer","adminAPIBaseUrl: string","service: string","serviceKey: string","serde","value"],"sources":["../src/restate_test_environment.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport {\n endpoint,\n createEndpointHandler,\n serde,\n} from \"@restatedev/restate-sdk\";\nimport type {\n TypedState,\n UntypedState,\n Serde,\n RestateEndpoint,\n VirtualObjectDefinition,\n WorkflowDefinition,\n EndpointOptions,\n} from \"@restatedev/restate-sdk\";\n\nimport {\n GenericContainer,\n type StartedTestContainer,\n TestContainers,\n Wait,\n type WaitStrategy,\n type BoundPorts,\n getContainerRuntimeClient,\n} from \"testcontainers\";\nimport { tableFromIPC } from \"apache-arrow\";\nimport * as http2 from \"http2\";\nimport type * as net from \"net\";\n\n/**\n * Custom wait strategy that waits for Restate partitions to be ready by\n * executing a SQL query against the admin API. This ensures all partitions\n * are initialized and queryable before the container is considered ready.\n */\nclass PartitionsReadyWaitStrategy implements WaitStrategy {\n private startupTimeoutMs = 60_000;\n private startupTimeoutSet = false;\n private readonly port: number;\n private readonly pollIntervalMs: number;\n\n constructor(port = 9070, pollIntervalMs = 200) {\n this.port = port;\n this.pollIntervalMs = pollIntervalMs;\n }\n\n public withStartupTimeout(startupTimeoutMs: number): this {\n this.startupTimeoutMs = startupTimeoutMs;\n this.startupTimeoutSet = true;\n return this;\n }\n\n public isStartupTimeoutSet(): boolean {\n return this.startupTimeoutSet;\n }\n\n public getStartupTimeout(): number {\n return this.startupTimeoutMs;\n }\n\n public async waitUntilReady(\n container: { id: string },\n boundPorts: BoundPorts\n ): Promise<void> {\n const client = await getContainerRuntimeClient();\n const host = client.info.containerRuntime.host;\n const mappedPort = boundPorts.getBinding(this.port);\n const adminUrl = `http://${host}:${mappedPort}`;\n\n const startTime = Date.now();\n\n while (Date.now() - startTime < this.startupTimeoutMs) {\n try {\n const res = await fetch(`${adminUrl}/query`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: \"SELECT count(1) FROM sys_invocation\",\n }),\n });\n\n if (res.ok) {\n // Partitions are ready\n return;\n }\n } catch {\n // Ignore errors, keep polling\n }\n\n await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));\n }\n\n throw new Error(\n `Restate partitions not ready after ${this.startupTimeoutMs}ms`\n );\n }\n}\n\n// Prepare the restate server\nasync function prepareRestateEndpoint(\n param: (server: RestateEndpoint) => void\n): Promise<http2.Http2Server>;\nasync function prepareRestateEndpoint(\n param: EndpointOptions\n): Promise<http2.Http2Server>;\nasync function prepareRestateEndpoint(\n param: EndpointOptions | ((server: RestateEndpoint) => void)\n): Promise<http2.Http2Server> {\n // Prepare RestateServer\n let handler: (\n request: http2.Http2ServerRequest,\n response: http2.Http2ServerResponse\n ) => void;\n if (typeof param === \"function\") {\n const restateEndpoint = endpoint();\n param(restateEndpoint);\n handler = restateEndpoint.http2Handler();\n } else {\n handler = createEndpointHandler(param);\n }\n\n // Start HTTP2 server on random port\n const restateHttpServer = http2.createServer(handler);\n await new Promise((resolve, reject) => {\n restateHttpServer\n .listen(0)\n .once(\"listening\", resolve)\n .once(\"error\", reject);\n });\n const restateServerPort = (restateHttpServer.address() as net.AddressInfo)\n .port;\n console.info(`Restate container listening on port ${restateServerPort}`);\n\n return restateHttpServer;\n}\n\n// Prepare the restate testcontainer\nasync function prepareRestateTestContainer(\n restateServerPort: number,\n restateContainerFactory: () => GenericContainer\n): Promise<StartedTestContainer> {\n const restateContainer = restateContainerFactory()\n // Expose ports\n .withExposedPorts(8080, 9070)\n // Wait start on health checks and partition readiness\n .withWaitStrategy(\n Wait.forAll([\n Wait.forHttp(\"/restate/health\", 8080),\n Wait.forHttp(\"/health\", 9070),\n new PartitionsReadyWaitStrategy(),\n ])\n );\n\n // This MUST be executed before starting the restate container\n // Expose host port to access the restate server\n await TestContainers.exposeHostPorts(restateServerPort);\n\n // Start restate container\n const startedRestateContainer = await restateContainer.start();\n\n // From now on, if something fails, stop the container to cleanup the environment\n try {\n console.info(\n `Registering services at http://host.testcontainers.internal:${restateServerPort}...`\n );\n\n // Register this service endpoint\n const res = await fetch(\n `http://${startedRestateContainer.getHost()}:${startedRestateContainer.getMappedPort(\n 9070\n )}/deployments`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n // See https://node.testcontainers.org/features/networking/#expose-host-ports-to-container\n uri: `http://host.testcontainers.internal:${restateServerPort}`,\n }),\n }\n );\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(\n `Error ${res.status} during registration: ${badResponse}`\n );\n }\n\n const resp = (await res.json()) as { services: { name: string }[] };\n console.info(\n \"Registered services:\",\n resp?.services?.map((s) => s.name)\n );\n return startedRestateContainer;\n } catch (e) {\n await startedRestateContainer.stop();\n throw e;\n }\n}\n\nexport interface TestEnvironmentOptions extends EndpointOptions {\n /**\n * Forces restate-server to always replay on a suspension point.\n * This is useful to hunt non-deterministic bugs that might prevent\n * your code from replaying correctly.\n */\n alwaysReplay?: boolean;\n\n /**\n * Disables retries in the restate-server invoker.\n * This is useful in tests so that failures surface immediately\n * instead of hanging through retry backoff.\n */\n disableRetries?: boolean;\n}\n\nexport class RestateTestEnvironment {\n constructor(\n readonly startedRestateHttpServer: http2.Http2Server,\n readonly startedRestateContainer: StartedTestContainer\n ) {}\n\n public baseUrl(): string {\n return `http://${this.startedRestateContainer.getHost()}:${this.startedRestateContainer.getMappedPort(\n 8080\n )}`;\n }\n\n public adminAPIBaseUrl(): string {\n return `http://${this.startedRestateContainer.getHost()}:${this.startedRestateContainer.getMappedPort(\n 9070\n )}`;\n }\n\n // Create a handle that allows read/write of state under a given Virtual Object/Workflow key.\n public stateOf<TState extends TypedState = UntypedState>(\n service:\n | VirtualObjectDefinition<string, unknown>\n | WorkflowDefinition<string, unknown>,\n key: string\n ): StateProxy<TState> {\n return new StateProxy(this.adminAPIBaseUrl(), service.name, key);\n }\n\n public async stop() {\n await this.startedRestateContainer.stop();\n this.startedRestateHttpServer.close();\n }\n\n /**\n *\n * @deprecated Please use {@link TestEnvironmentOptions} instead of this.\n * @example\n * ```\n * RestateTestEnvironment.start({ services: [mysService] })\n * ```\n */\n public static async start(\n mountServicesFn: (server: RestateEndpoint) => void,\n restateContainerFactory?: () => GenericContainer\n ): Promise<RestateTestEnvironment>;\n public static async start(\n options: TestEnvironmentOptions,\n restateContainerFactory?: () => GenericContainer\n ): Promise<RestateTestEnvironment>;\n public static async start(\n param: TestEnvironmentOptions | ((server: RestateEndpoint) => void),\n restateContainerFactory?: () => GenericContainer\n ): Promise<RestateTestEnvironment> {\n let containerFactory: () => GenericContainer;\n if (restateContainerFactory) {\n containerFactory = restateContainerFactory;\n } else if (typeof param !== \"function\") {\n containerFactory = () => {\n const container = new RestateContainer();\n if (param.alwaysReplay) {\n container.alwaysReplay();\n }\n if (param.disableRetries) {\n container.disableRetries();\n }\n return container;\n };\n } else {\n containerFactory = () => new RestateContainer();\n }\n\n const startedRestateHttpServer =\n typeof param === \"function\"\n ? await prepareRestateEndpoint(param)\n : await prepareRestateEndpoint(param);\n const startedRestateContainer = await prepareRestateTestContainer(\n (startedRestateHttpServer.address() as net.AddressInfo).port,\n containerFactory\n );\n return new RestateTestEnvironment(\n startedRestateHttpServer,\n startedRestateContainer\n );\n }\n}\n\nexport class RestateContainer extends GenericContainer {\n constructor(version = \"latest\") {\n super(`docker.io/restatedev/restate:${version}`);\n }\n\n /**\n * Forces restate-server to always replay on a suspension point.\n * This is useful to hunt non-deterministic bugs that might prevent\n * your code from replaying correctly.\n */\n alwaysReplay(): this {\n this.withEnvironment({\n RESTATE_WORKER__INVOKER__INACTIVITY_TIMEOUT: \"0s\",\n });\n return this;\n }\n\n /**\n * Disables retries in the restate-server invoker.\n * This is useful in tests so that failures surface immediately\n * instead of hanging through retry backoff.\n */\n disableRetries(): this {\n this.withEnvironment({\n RESTATE_DEFAULT_RETRY_POLICY__MAX_ATTEMPTS: \"1\",\n RESTATE_DEFAULT_RETRY_POLICY__ON_MAX_ATTEMPTS: \"kill\",\n });\n return this;\n }\n}\nexport class StateProxy<TState extends TypedState> {\n constructor(\n private adminAPIBaseUrl: string,\n private service: string,\n private serviceKey: string\n ) {}\n\n // Read a single value from state under a given Virtual Object or Workflow key\n public async get<TValue, TKey extends keyof TState = string>(\n name: TState extends UntypedState ? string : TKey,\n serde?: Serde<TState extends UntypedState ? TValue : TState[TKey]>\n ): Promise<(TState extends UntypedState ? TValue : TState[TKey]) | null> {\n serde = serde ?? defaultSerde();\n\n const res = await fetch(`${this.adminAPIBaseUrl}/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query: `SELECT value from state where service_name = '${\n this.service\n }' and service_key = '${this.serviceKey}' and key = '${String(name)}';`,\n }),\n });\n\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(`Error ${res.status} during read state: ${badResponse}`);\n }\n\n // eslint-disable-next-line @typescript-eslint/await-thenable\n const table = (await tableFromIPC(res.body)).toArray() as {\n key: string;\n value: Uint8Array;\n }[];\n\n if (table.length === 0) {\n return null;\n }\n\n return serde.deserialize(table[0]!.value);\n }\n\n // Read all values from state under a given Virtual Object or Workflow key\n public async getAll<TValues extends TypedState>(\n serde?: Serde<\n TState extends UntypedState\n ? TValues[keyof TValues]\n : TState[keyof TState]\n >\n ): Promise<TState extends UntypedState ? TValues : TState> {\n serde = serde ?? defaultSerde();\n\n const items = await this.getAllRaw();\n\n return Object.fromEntries(\n items.map(({ key, value }) => {\n return [key, serde.deserialize(value)];\n })\n ) as TState extends UntypedState ? TValues : TState;\n }\n\n private async getAllRaw(): Promise<{ key: string; value: Uint8Array }[]> {\n const res = await fetch(`${this.adminAPIBaseUrl}/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query: `SELECT key, value from state where service_name = '${this.service}' and service_key = '${this.serviceKey}';`,\n }),\n });\n\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(`Error ${res.status} during read state: ${badResponse}`);\n }\n\n // eslint-disable-next-line @typescript-eslint/await-thenable\n const table = (await tableFromIPC(res.body)).toArray() as {\n key: string;\n value: Uint8Array;\n }[];\n\n return table;\n }\n\n // Asynchronously set a single value from state under a given Virtual Object or Workflow key.\n // This will first read all values, then insert the update and submit the new set of values to Restate;\n // as such it is possible to overwrite changes that happened between the read and the mutation being applied.\n // A successful return from this function does not imply that the set has finished, only that the mutation\n // was submitted to Restate for processing.\n public async set<TValue, TKey extends keyof TState = string>(\n name: TState extends UntypedState ? string : TKey,\n value: TState extends UntypedState ? TValue : TState[TKey],\n serde?: Serde<TState extends UntypedState ? TValue : TState[TKey]>\n ): Promise<void> {\n serde = serde ?? defaultSerde();\n const serialisedValue = serde.serialize(value);\n\n const items = await this.getAllRaw();\n\n items.push({ key: String(name), value: serialisedValue });\n\n await this.setAllRaw(items.map(({ key, value }) => [key, value]));\n }\n\n // Asynchronously set all state values under a given Virtual Object or Workflow key.\n // A successful return from this function does not imply that the set has finished,\n // only that the mutation was submitted to Restate for processing.\n public async setAll<TValues extends TypedState>(\n values: TState extends UntypedState ? TValues : TState,\n serde?: Serde<\n TState extends UntypedState\n ? TValues[keyof TValues]\n : TState[keyof TState]\n >\n ) {\n serde = serde ?? defaultSerde();\n\n return this.setAllRaw(\n Object.entries<\n TState extends UntypedState\n ? TValues[keyof TValues]\n : TState[keyof TState]\n >(values).map(([key, value]) => {\n return [key, serde.serialize(value)];\n })\n );\n }\n\n private async setAllRaw(\n entries: [key: string, value: Uint8Array][],\n version?: string\n ) {\n const res = await fetch(\n `${this.adminAPIBaseUrl}/services/${this.service}/state`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(\n {\n version,\n object_key: this.serviceKey,\n new_state: Object.fromEntries(entries),\n },\n (key, value) => {\n if (value instanceof Uint8Array) {\n return Array.from(value);\n } else {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value;\n }\n }\n ),\n }\n );\n\n if (!res.ok) {\n const badResponse = await res.text();\n throw new Error(\n `Error ${res.status} during modify state: ${badResponse}`\n );\n }\n }\n}\n\nexport const defaultSerde = <T>(): Serde<T> => {\n return serde.json as Serde<T>;\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@restatedev/restate-sdk-testcontainers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Typescript SDK for Restate",
|
|
5
5
|
"author": "Restate Developers",
|
|
6
6
|
"email": "code@restate.dev",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"apache-arrow": "^18.0.0",
|
|
36
|
-
"testcontainers": "^
|
|
37
|
-
"@restatedev/restate-sdk": "1.
|
|
38
|
-
"@restatedev/restate-sdk-clients": "1.
|
|
36
|
+
"testcontainers": "^11.12.0",
|
|
37
|
+
"@restatedev/restate-sdk": "1.12.0",
|
|
38
|
+
"@restatedev/restate-sdk-clients": "1.12.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {},
|
|
41
41
|
"scripts": {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { ServiceDefinition, VirtualObjectDefinition, WorkflowDefinition } from "../../restate-sdk-core/src/core.cjs";
|
|
2
|
-
import { Serde } from "../../restate-sdk-core/src/serde_api.cjs";
|
|
3
|
-
import { Duration } from "../../restate-sdk-core/src/duration.cjs";
|
|
4
|
-
import { JournalValueCodec } from "../../restate-sdk-core/src/entry_codec.cjs";
|
|
5
|
-
import { RestateError, TerminalError } from "./types/errors.cjs";
|
|
6
|
-
import { ObjectOptions, RetryPolicy, ServiceOptions, WorkflowOptions } from "./types/rpc.cjs";
|
|
7
|
-
import { Request, TypedState, UntypedState } from "./context.cjs";
|
|
8
|
-
import { LogMetadata, LogSource, LoggerContext, LoggerTransport, RestateLogLevel } from "./logging/logger_transport.cjs";
|
|
9
|
-
import { DefaultServiceOptions, RestateEndpoint, RestateEndpointBase } from "./endpoint.cjs";
|
|
10
|
-
import { EndpointOptions } from "./endpoint/types.cjs";
|