@restatedev/restate-sdk-testcontainers 1.10.2 → 1.10.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"rpc.d.cts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":";;;;;;KA4uBY,WAAA;;;;;;;;;;;;;;;;;;;;oBAsBQ;;;;;;;gBAQJ;;;;;;KAQJ,cAAA;;;;;;yBAMa;;;;;;;;qBASJ;;;;;;;;;;;sBAYC;;;;;;;;;;;;iBAaL;;;;;;;;;;;gBAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAsCoB;;;;;;UAO1B;;KA8FE,aAAA,GAAgB;;;;;;;;;KA4HhB,eAAA,GAAkB;;;;;;;sBAOR"}
1
+ {"version":3,"file":"rpc.d.cts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":";;;;;;KAwvBY,WAAA;;;;;;;;;;;;;;;;;;;;oBAsBQ;;;;;;;gBAQJ;;;;;;KAQJ,cAAA;;;;;;yBAMa;;;;;;;;qBASJ;;;;;;;;;;;sBAYC;;;;;;;;;;;;iBAaL;;;;;;;;;;;gBAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAsCoB;;;;;;UAO1B;;KA8FE,aAAA,GAAgB;;;;;;;;;KA4HhB,eAAA,GAAkB;;;;;;;sBAOR"}
@@ -1 +1 @@
1
- {"version":3,"file":"rpc.d.ts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":";;;;;;KA4uBY,WAAA;;;;;;;;;;;;;;;;;;;;oBAsBQ;;;;;;;gBAQJ;;;;;;KAQJ,cAAA;;;;;;yBAMa;;;;;;;;qBASJ;;;;;;;;;;;sBAYC;;;;;;;;;;;;iBAaL;;;;;;;;;;;gBAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAsCoB;;;;;;UAO1B;;KA8FE,aAAA,GAAgB;;;;;;;;;KA4HhB,eAAA,GAAkB;;;;;;;sBAOR"}
1
+ {"version":3,"file":"rpc.d.ts","names":[],"sources":["../../../../../restate-sdk/src/types/rpc.ts"],"sourcesContent":[],"mappings":";;;;;;KAwvBY,WAAA;;;;;;;;;;;;;;;;;;;;oBAsBQ;;;;;;;gBAQJ;;;;;;KAQJ,cAAA;;;;;;yBAMa;;;;;;;;qBASJ;;;;;;;;;;;sBAYC;;;;;;;;;;;;iBAaL;;;;;;;;;;;gBAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAsCoB;;;;;;UAO1B;;KA8FE,aAAA,GAAgB;;;;;;;;;KA4HhB,eAAA,GAAkB;;;;;;;sBAOR"}
@@ -9,6 +9,47 @@ let http2 = require("http2");
9
9
  http2 = require_rolldown_runtime.__toESM(http2);
10
10
 
11
11
  //#region src/restate_test_environment.ts
12
+ /**
13
+ * Custom wait strategy that waits for Restate partitions to be ready by
14
+ * executing a SQL query against the admin API. This ensures all partitions
15
+ * are initialized and queryable before the container is considered ready.
16
+ */
17
+ var PartitionsReadyWaitStrategy = class {
18
+ startupTimeoutMs = 6e4;
19
+ startupTimeoutSet = false;
20
+ port;
21
+ pollIntervalMs;
22
+ constructor(port = 9070, pollIntervalMs = 200) {
23
+ this.port = port;
24
+ this.pollIntervalMs = pollIntervalMs;
25
+ }
26
+ withStartupTimeout(startupTimeoutMs) {
27
+ this.startupTimeoutMs = startupTimeoutMs;
28
+ this.startupTimeoutSet = true;
29
+ return this;
30
+ }
31
+ isStartupTimeoutSet() {
32
+ return this.startupTimeoutSet;
33
+ }
34
+ getStartupTimeout() {
35
+ return this.startupTimeoutMs;
36
+ }
37
+ async waitUntilReady(container, boundPorts) {
38
+ const adminUrl = `http://${(await (0, testcontainers.getContainerRuntimeClient)()).info.containerRuntime.host}:${boundPorts.getBinding(this.port)}`;
39
+ const startTime = Date.now();
40
+ while (Date.now() - startTime < this.startupTimeoutMs) {
41
+ try {
42
+ if ((await fetch(`${adminUrl}/query`, {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: JSON.stringify({ query: "SELECT count(1) FROM sys_invocation" })
46
+ })).ok) return;
47
+ } catch {}
48
+ await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
49
+ }
50
+ throw new Error(`Restate partitions not ready after ${this.startupTimeoutMs}ms`);
51
+ }
52
+ };
12
53
  async function prepareRestateEndpoint(param) {
13
54
  let handler;
14
55
  if (typeof param === "function") {
@@ -25,7 +66,11 @@ async function prepareRestateEndpoint(param) {
25
66
  return restateHttpServer;
26
67
  }
27
68
  async function prepareRestateTestContainer(restateServerPort, restateContainerFactory) {
28
- const restateContainer = restateContainerFactory().withExposedPorts(8080, 9070).withWaitStrategy(testcontainers.Wait.forAll([testcontainers.Wait.forHttp("/restate/health", 8080), testcontainers.Wait.forHttp("/health", 9070)]));
69
+ const restateContainer = restateContainerFactory().withExposedPorts(8080, 9070).withWaitStrategy(testcontainers.Wait.forAll([
70
+ testcontainers.Wait.forHttp("/restate/health", 8080),
71
+ testcontainers.Wait.forHttp("/health", 9070),
72
+ new PartitionsReadyWaitStrategy()
73
+ ]));
29
74
  await testcontainers.TestContainers.exposeHostPorts(restateServerPort);
30
75
  const startedRestateContainer = await restateContainer.start();
31
76
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"restate_test_environment.d.cts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cA0Ia,sBAAA;qCAE0B,KAAA,CAAM;oCACP;wCADC,KAAA,CAAM,sCACP;;;yBAgBN,aAAa,uBAErC,2CACA,mDAEH,WAAW;EAxBH,IAAA,CAAA,CAAA,EA4BM,OA5BN,CAAA,IAAA,CAAA;EAE0B;;;;;;;;EAsBvB,OAAA,KAAA,CAAA,eAAA,EAAA,CAAA,MAAA,EAkBc,eAlBd,EAAA,GAAA,IAAA,EAAA,uBAAA,CAAA,EAAA,GAAA,GAmBoB,gBAnBpB,CAAA,EAoBX,OApBW,CAoBH,sBApBG,CAAA;EAAX,OAAA,KAAA,CAAA,OAAA,EAsBQ,eAtBR,EAAA,uBAAA,CAAA,EAAA,GAAA,GAuB+B,gBAvB/B,CAAA,EAwBA,OAxBA,CAwBQ,sBAxBR,CAAA;;AAkByB,cA2BjB,gBAAA,SAAyB,gBAAA,CA3BR;EACM,WAAA,CAAA,OAAA,CAAA,EAAA,MAAA;;AAC/B,cA8BQ,UA9BR,CAAA,eA8BkC,UA9BlC,CAAA,CAAA;EAEQ,QAAA,eAAA;EACuB,QAAA,OAAA;EACvB,QAAA,UAAA;EAAR,WAAA,CAAA,eAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA;EAAO,GAAA,CAAA,MAAA,EAAA,aAAA,MAkCkC,MAlClC,GAAA,MAAA,CAAA,CAAA,IAAA,EAmCF,MAnCE,SAmCa,YAnCb,GAAA,MAAA,GAmCqC,IAnCrC,EAAA,KAAA,CAAA,EAoCA,KApCA,CAoCM,MApCN,SAoCqB,YApCrB,GAoCoC,MApCpC,GAoC6C,MApC7C,CAoCoD,IApCpD,CAAA,CAAA,CAAA,EAqCP,OArCO,CAAA,CAqCE,MArCF,SAqCiB,YArCjB,GAqCgC,MArChC,GAqCyC,MArCzC,CAqCgD,IArChD,CAAA,CAAA,GAAA,IAAA,CAAA;EAqBC,MAAA,CAAA,gBAkDyB,UAlDA,CAAA,CAAA,KAAgB,CAAhB,EAmD1B,KAnD0C,CAoDhD,MApDgD,SAoDjC,YApDiC,GAqD5C,OArD4C,CAAA,MAqD9B,OArD8B,CAAA,GAsD5C,MAtD4C,CAAA,MAsD/B,MAtD+B,CAAA,CAAA,CAAA,EAwDjD,OAxDiD,CAwDzC,MAxDyC,SAwD1B,YAxD0B,GAwDX,OAxDW,GAwDD,MAxDC,CAAA;EAKzC,QAAA,SAAU;EAAgB,GAAA,CAAA,MAAA,EAAA,aAAA,MA6FO,MA7FP,GAAA,MAAA,CAAA,CAAA,IAAA,EA8F7B,MA9F6B,SA8Fd,YA9Fc,GAAA,MAAA,GA8FU,IA9FV,EAAA,KAAA,EA+F5B,MA/F4B,SA+Fb,YA/Fa,GA+FE,MA/FF,GA+FW,MA/FX,CA+FkB,IA/FlB,CAAA,EAAA,KAAA,CAAA,EAgG3B,KAhG2B,CAgGrB,MAhGqB,SAgGN,YAhGM,GAgGS,MAhGT,GAgGkB,MAhGlB,CAgGyB,IAhGzB,CAAA,CAAA,CAAA,EAiGlC,OAjGkC,CAAA,IAAA,CAAA;EAQO,MAAA,CAAA,gBAuGR,UAvGQ,CAAA,CAAA,MAAA,EAwGlC,MAxGkC,SAwGnB,YAxGmB,GAwGJ,OAxGI,GAwGM,MAxGN,EAAA,KAAA,CAAA,EAyGlC,KAzGkC,CA0GxC,MA1GwC,SA0GzB,YA1GyB,GA2GpC,OA3GoC,CAAA,MA2GtB,OA3GsB,CAAA,GA4GpC,MA5GoC,CAAA,MA4GvB,MA5GuB,CAAA,CAAA,CAAA,EA6GzC,OA7GyC,CAAA,IAAA,CAAA;EACpC,QAAA,SAAA"}
1
+ {"version":3,"file":"restate_test_environment.d.cts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAkNa,sBAAA;qCAE0B,KAAA,CAAM;oCACP;wCADC,KAAA,CAAM,sCACP;;;yBAgBN,aAAa,uBAErC,2CACA,mDAEH,WAAW;EAxBH,IAAA,CAAA,CAAA,EA4BM,OA5BN,CAAA,IAAA,CAAA;EAE0B;;;;;;;;EAsBvB,OAAA,KAAA,CAAA,eAAA,EAAA,CAAA,MAAA,EAkBc,eAlBd,EAAA,GAAA,IAAA,EAAA,uBAAA,CAAA,EAAA,GAAA,GAmBoB,gBAnBpB,CAAA,EAoBX,OApBW,CAoBH,sBApBG,CAAA;EAAX,OAAA,KAAA,CAAA,OAAA,EAsBQ,eAtBR,EAAA,uBAAA,CAAA,EAAA,GAAA,GAuB+B,gBAvB/B,CAAA,EAwBA,OAxBA,CAwBQ,sBAxBR,CAAA;;AAkByB,cA2BjB,gBAAA,SAAyB,gBAAA,CA3BR;EACM,WAAA,CAAA,OAAA,CAAA,EAAA,MAAA;;AAC/B,cA8BQ,UA9BR,CAAA,eA8BkC,UA9BlC,CAAA,CAAA;EAEQ,QAAA,eAAA;EACuB,QAAA,OAAA;EACvB,QAAA,UAAA;EAAR,WAAA,CAAA,eAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA;EAAO,GAAA,CAAA,MAAA,EAAA,aAAA,MAkCkC,MAlClC,GAAA,MAAA,CAAA,CAAA,IAAA,EAmCF,MAnCE,SAmCa,YAnCb,GAAA,MAAA,GAmCqC,IAnCrC,EAAA,KAAA,CAAA,EAoCA,KApCA,CAoCM,MApCN,SAoCqB,YApCrB,GAoCoC,MApCpC,GAoC6C,MApC7C,CAoCoD,IApCpD,CAAA,CAAA,CAAA,EAqCP,OArCO,CAAA,CAqCE,MArCF,SAqCiB,YArCjB,GAqCgC,MArChC,GAqCyC,MArCzC,CAqCgD,IArChD,CAAA,CAAA,GAAA,IAAA,CAAA;EAqBC,MAAA,CAAA,gBAkDyB,UAlDA,CAAA,CAAA,KAAgB,CAAhB,EAmD1B,KAnD0C,CAoDhD,MApDgD,SAoDjC,YApDiC,GAqD5C,OArD4C,CAAA,MAqD9B,OArD8B,CAAA,GAsD5C,MAtD4C,CAAA,MAsD/B,MAtD+B,CAAA,CAAA,CAAA,EAwDjD,OAxDiD,CAwDzC,MAxDyC,SAwD1B,YAxD0B,GAwDX,OAxDW,GAwDD,MAxDC,CAAA;EAKzC,QAAA,SAAU;EAAgB,GAAA,CAAA,MAAA,EAAA,aAAA,MA6FO,MA7FP,GAAA,MAAA,CAAA,CAAA,IAAA,EA8F7B,MA9F6B,SA8Fd,YA9Fc,GAAA,MAAA,GA8FU,IA9FV,EAAA,KAAA,EA+F5B,MA/F4B,SA+Fb,YA/Fa,GA+FE,MA/FF,GA+FW,MA/FX,CA+FkB,IA/FlB,CAAA,EAAA,KAAA,CAAA,EAgG3B,KAhG2B,CAgGrB,MAhGqB,SAgGN,YAhGM,GAgGS,MAhGT,GAgGkB,MAhGlB,CAgGyB,IAhGzB,CAAA,CAAA,CAAA,EAiGlC,OAjGkC,CAAA,IAAA,CAAA;EAQO,MAAA,CAAA,gBAuGR,UAvGQ,CAAA,CAAA,MAAA,EAwGlC,MAxGkC,SAwGnB,YAxGmB,GAwGJ,OAxGI,GAwGM,MAxGN,EAAA,KAAA,CAAA,EAyGlC,KAzGkC,CA0GxC,MA1GwC,SA0GzB,YA1GyB,GA2GpC,OA3GoC,CAAA,MA2GtB,OA3GsB,CAAA,GA4GpC,MA5GoC,CAAA,MA4GvB,MA5GuB,CAAA,CAAA,CAAA,EA6GzC,OA7GyC,CAAA,IAAA,CAAA;EACpC,QAAA,SAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"restate_test_environment.d.ts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cA0Ia,sBAAA;qCAE0B,KAAA,CAAM;oCACP;wCADC,KAAA,CAAM,sCACP;;;yBAgBN,aAAa,uBAErC,2CACA,mDAEH,WAAW;EAxBH,IAAA,CAAA,CAAA,EA4BM,OA5BN,CAAA,IAAA,CAAA;EAE0B;;;;;;;;EAsBvB,OAAA,KAAA,CAAA,eAAA,EAAA,CAAA,MAAA,EAkBc,eAlBd,EAAA,GAAA,IAAA,EAAA,uBAAA,CAAA,EAAA,GAAA,GAmBoB,gBAnBpB,CAAA,EAoBX,OApBW,CAoBH,sBApBG,CAAA;EAAX,OAAA,KAAA,CAAA,OAAA,EAsBQ,eAtBR,EAAA,uBAAA,CAAA,EAAA,GAAA,GAuB+B,gBAvB/B,CAAA,EAwBA,OAxBA,CAwBQ,sBAxBR,CAAA;;AAkByB,cA2BjB,gBAAA,SAAyB,gBAAA,CA3BR;EACM,WAAA,CAAA,OAAA,CAAA,EAAA,MAAA;;AAC/B,cA8BQ,UA9BR,CAAA,eA8BkC,UA9BlC,CAAA,CAAA;EAEQ,QAAA,eAAA;EACuB,QAAA,OAAA;EACvB,QAAA,UAAA;EAAR,WAAA,CAAA,eAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA;EAAO,GAAA,CAAA,MAAA,EAAA,aAAA,MAkCkC,MAlClC,GAAA,MAAA,CAAA,CAAA,IAAA,EAmCF,MAnCE,SAmCa,YAnCb,GAAA,MAAA,GAmCqC,IAnCrC,EAAA,KAAA,CAAA,EAoCA,KApCA,CAoCM,MApCN,SAoCqB,YApCrB,GAoCoC,MApCpC,GAoC6C,MApC7C,CAoCoD,IApCpD,CAAA,CAAA,CAAA,EAqCP,OArCO,CAAA,CAqCE,MArCF,SAqCiB,YArCjB,GAqCgC,MArChC,GAqCyC,MArCzC,CAqCgD,IArChD,CAAA,CAAA,GAAA,IAAA,CAAA;EAqBC,MAAA,CAAA,gBAkDyB,UAlDA,CAAA,CAAA,KAAgB,CAAhB,EAmD1B,KAnD0C,CAoDhD,MApDgD,SAoDjC,YApDiC,GAqD5C,OArD4C,CAAA,MAqD9B,OArD8B,CAAA,GAsD5C,MAtD4C,CAAA,MAsD/B,MAtD+B,CAAA,CAAA,CAAA,EAwDjD,OAxDiD,CAwDzC,MAxDyC,SAwD1B,YAxD0B,GAwDX,OAxDW,GAwDD,MAxDC,CAAA;EAKzC,QAAA,SAAU;EAAgB,GAAA,CAAA,MAAA,EAAA,aAAA,MA6FO,MA7FP,GAAA,MAAA,CAAA,CAAA,IAAA,EA8F7B,MA9F6B,SA8Fd,YA9Fc,GAAA,MAAA,GA8FU,IA9FV,EAAA,KAAA,EA+F5B,MA/F4B,SA+Fb,YA/Fa,GA+FE,MA/FF,GA+FW,MA/FX,CA+FkB,IA/FlB,CAAA,EAAA,KAAA,CAAA,EAgG3B,KAhG2B,CAgGrB,MAhGqB,SAgGN,YAhGM,GAgGS,MAhGT,GAgGkB,MAhGlB,CAgGyB,IAhGzB,CAAA,CAAA,CAAA,EAiGlC,OAjGkC,CAAA,IAAA,CAAA;EAQO,MAAA,CAAA,gBAuGR,UAvGQ,CAAA,CAAA,MAAA,EAwGlC,MAxGkC,SAwGnB,YAxGmB,GAwGJ,OAxGI,GAwGM,MAxGN,EAAA,KAAA,CAAA,EAyGlC,KAzGkC,CA0GxC,MA1GwC,SA0GzB,YA1GyB,GA2GpC,OA3GoC,CAAA,MA2GtB,OA3GsB,CAAA,GA4GpC,MA5GoC,CAAA,MA4GvB,MA5GuB,CAAA,CAAA,CAAA,EA6GzC,OA7GyC,CAAA,IAAA,CAAA;EACpC,QAAA,SAAA"}
1
+ {"version":3,"file":"restate_test_environment.d.ts","names":[],"sources":["../src/restate_test_environment.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAkNa,sBAAA;qCAE0B,KAAA,CAAM;oCACP;wCADC,KAAA,CAAM,sCACP;;;yBAgBN,aAAa,uBAErC,2CACA,mDAEH,WAAW;EAxBH,IAAA,CAAA,CAAA,EA4BM,OA5BN,CAAA,IAAA,CAAA;EAE0B;;;;;;;;EAsBvB,OAAA,KAAA,CAAA,eAAA,EAAA,CAAA,MAAA,EAkBc,eAlBd,EAAA,GAAA,IAAA,EAAA,uBAAA,CAAA,EAAA,GAAA,GAmBoB,gBAnBpB,CAAA,EAoBX,OApBW,CAoBH,sBApBG,CAAA;EAAX,OAAA,KAAA,CAAA,OAAA,EAsBQ,eAtBR,EAAA,uBAAA,CAAA,EAAA,GAAA,GAuB+B,gBAvB/B,CAAA,EAwBA,OAxBA,CAwBQ,sBAxBR,CAAA;;AAkByB,cA2BjB,gBAAA,SAAyB,gBAAA,CA3BR;EACM,WAAA,CAAA,OAAA,CAAA,EAAA,MAAA;;AAC/B,cA8BQ,UA9BR,CAAA,eA8BkC,UA9BlC,CAAA,CAAA;EAEQ,QAAA,eAAA;EACuB,QAAA,OAAA;EACvB,QAAA,UAAA;EAAR,WAAA,CAAA,eAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA;EAAO,GAAA,CAAA,MAAA,EAAA,aAAA,MAkCkC,MAlClC,GAAA,MAAA,CAAA,CAAA,IAAA,EAmCF,MAnCE,SAmCa,YAnCb,GAAA,MAAA,GAmCqC,IAnCrC,EAAA,KAAA,CAAA,EAoCA,KApCA,CAoCM,MApCN,SAoCqB,YApCrB,GAoCoC,MApCpC,GAoC6C,MApC7C,CAoCoD,IApCpD,CAAA,CAAA,CAAA,EAqCP,OArCO,CAAA,CAqCE,MArCF,SAqCiB,YArCjB,GAqCgC,MArChC,GAqCyC,MArCzC,CAqCgD,IArChD,CAAA,CAAA,GAAA,IAAA,CAAA;EAqBC,MAAA,CAAA,gBAkDyB,UAlDA,CAAA,CAAA,KAAgB,CAAhB,EAmD1B,KAnD0C,CAoDhD,MApDgD,SAoDjC,YApDiC,GAqD5C,OArD4C,CAAA,MAqD9B,OArD8B,CAAA,GAsD5C,MAtD4C,CAAA,MAsD/B,MAtD+B,CAAA,CAAA,CAAA,EAwDjD,OAxDiD,CAwDzC,MAxDyC,SAwD1B,YAxD0B,GAwDX,OAxDW,GAwDD,MAxDC,CAAA;EAKzC,QAAA,SAAU;EAAgB,GAAA,CAAA,MAAA,EAAA,aAAA,MA6FO,MA7FP,GAAA,MAAA,CAAA,CAAA,IAAA,EA8F7B,MA9F6B,SA8Fd,YA9Fc,GAAA,MAAA,GA8FU,IA9FV,EAAA,KAAA,EA+F5B,MA/F4B,SA+Fb,YA/Fa,GA+FE,MA/FF,GA+FW,MA/FX,CA+FkB,IA/FlB,CAAA,EAAA,KAAA,CAAA,EAgG3B,KAhG2B,CAgGrB,MAhGqB,SAgGN,YAhGM,GAgGS,MAhGT,GAgGkB,MAhGlB,CAgGyB,IAhGzB,CAAA,CAAA,CAAA,EAiGlC,OAjGkC,CAAA,IAAA,CAAA;EAQO,MAAA,CAAA,gBAuGR,UAvGQ,CAAA,CAAA,MAAA,EAwGlC,MAxGkC,SAwGnB,YAxGmB,GAwGJ,OAxGI,GAwGM,MAxGN,EAAA,KAAA,CAAA,EAyGlC,KAzGkC,CA0GxC,MA1GwC,SA0GzB,YA1GyB,GA2GpC,OA3GoC,CAAA,MA2GtB,OA3GsB,CAAA,GA4GpC,MA5GoC,CAAA,MA4GvB,MA5GuB,CAAA,CAAA,CAAA,EA6GzC,OA7GyC,CAAA,IAAA,CAAA;EACpC,QAAA,SAAA"}
@@ -1,9 +1,50 @@
1
1
  import { createEndpointHandler, endpoint, serde } from "@restatedev/restate-sdk";
2
- import { GenericContainer, TestContainers, Wait } from "testcontainers";
2
+ import { GenericContainer, TestContainers, Wait, getContainerRuntimeClient } from "testcontainers";
3
3
  import { tableFromIPC } from "apache-arrow";
4
4
  import * as http2 from "http2";
5
5
 
6
6
  //#region src/restate_test_environment.ts
7
+ /**
8
+ * Custom wait strategy that waits for Restate partitions to be ready by
9
+ * executing a SQL query against the admin API. This ensures all partitions
10
+ * are initialized and queryable before the container is considered ready.
11
+ */
12
+ var PartitionsReadyWaitStrategy = class {
13
+ startupTimeoutMs = 6e4;
14
+ startupTimeoutSet = false;
15
+ port;
16
+ pollIntervalMs;
17
+ constructor(port = 9070, pollIntervalMs = 200) {
18
+ this.port = port;
19
+ this.pollIntervalMs = pollIntervalMs;
20
+ }
21
+ withStartupTimeout(startupTimeoutMs) {
22
+ this.startupTimeoutMs = startupTimeoutMs;
23
+ this.startupTimeoutSet = true;
24
+ return this;
25
+ }
26
+ isStartupTimeoutSet() {
27
+ return this.startupTimeoutSet;
28
+ }
29
+ getStartupTimeout() {
30
+ return this.startupTimeoutMs;
31
+ }
32
+ async waitUntilReady(container, boundPorts) {
33
+ const adminUrl = `http://${(await getContainerRuntimeClient()).info.containerRuntime.host}:${boundPorts.getBinding(this.port)}`;
34
+ const startTime = Date.now();
35
+ while (Date.now() - startTime < this.startupTimeoutMs) {
36
+ try {
37
+ if ((await fetch(`${adminUrl}/query`, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json" },
40
+ body: JSON.stringify({ query: "SELECT count(1) FROM sys_invocation" })
41
+ })).ok) return;
42
+ } catch {}
43
+ await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
44
+ }
45
+ throw new Error(`Restate partitions not ready after ${this.startupTimeoutMs}ms`);
46
+ }
47
+ };
7
48
  async function prepareRestateEndpoint(param) {
8
49
  let handler;
9
50
  if (typeof param === "function") {
@@ -20,7 +61,11 @@ async function prepareRestateEndpoint(param) {
20
61
  return restateHttpServer;
21
62
  }
22
63
  async function prepareRestateTestContainer(restateServerPort, restateContainerFactory) {
23
- const restateContainer = restateContainerFactory().withExposedPorts(8080, 9070).withWaitStrategy(Wait.forAll([Wait.forHttp("/restate/health", 8080), Wait.forHttp("/health", 9070)]));
64
+ const restateContainer = restateContainerFactory().withExposedPorts(8080, 9070).withWaitStrategy(Wait.forAll([
65
+ Wait.forHttp("/restate/health", 8080),
66
+ Wait.forHttp("/health", 9070),
67
+ new PartitionsReadyWaitStrategy()
68
+ ]));
24
69
  await TestContainers.exposeHostPorts(restateServerPort);
25
70
  const startedRestateContainer = await restateContainer.start();
26
71
  try {
@@ -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} from \"testcontainers\";\nimport { tableFromIPC } from \"apache-arrow\";\nimport * as http2 from \"http2\";\nimport type * as net from \"net\";\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\n .withWaitStrategy(\n Wait.forAll([\n Wait.forHttp(\"/restate/health\", 8080),\n Wait.forHttp(\"/health\", 9070),\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":";;;;;;AA2CA,eAAe,uBACb,OAC4B;CAE5B,IAAIA;AAIJ,KAAI,OAAO,UAAU,YAAY;EAC/B,MAAM,kBAAkB,UAAU;AAClC,QAAM,gBAAgB;AACtB,YAAU,gBAAgB,cAAc;OAExC,WAAU,sBAAsB,MAAM;CAIxC,MAAM,oBAAoB,MAAM,aAAa,QAAQ;AACrD,OAAM,IAAI,SAAS,SAAS,WAAW;AACrC,oBACG,OAAO,EAAE,CACT,KAAK,aAAa,QAAQ,CAC1B,KAAK,SAAS,OAAO;GACxB;CACF,MAAM,oBAAqB,kBAAkB,SAAS,CACnD;AACH,SAAQ,KAAK,uCAAuC,oBAAoB;AAExE,QAAO;;AAIT,eAAe,4BACb,mBACA,yBAC+B;CAC/B,MAAM,mBAAmB,yBAAyB,CAE/C,iBAAiB,MAAM,KAAK,CAE5B,iBACC,KAAK,OAAO,CACV,KAAK,QAAQ,mBAAmB,KAAK,EACrC,KAAK,QAAQ,WAAW,KAAK,CAC9B,CAAC,CACH;AAIH,OAAM,eAAe,gBAAgB,kBAAkB;CAGvD,MAAM,0BAA0B,MAAM,iBAAiB,OAAO;AAG9D,KAAI;AACF,UAAQ,KACN,+DAA+D,kBAAkB,KAClF;EAGD,MAAM,MAAM,MAAM,MAChB,UAAU,wBAAwB,SAAS,CAAC,GAAG,wBAAwB,cACrE,KACD,CAAC,eACF;GACE,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UAAU,EAEnB,KAAK,uCAAuC,qBAC7C,CAAC;GACH,CACF;AACD,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MACR,SAAS,IAAI,OAAO,wBAAwB,cAC7C;;EAGH,MAAM,OAAQ,MAAM,IAAI,MAAM;AAC9B,UAAQ,KACN,wBACA,MAAM,UAAU,KAAK,MAAM,EAAE,KAAK,CACnC;AACD,SAAO;UACA,GAAG;AACV,QAAM,wBAAwB,MAAM;AACpC,QAAM;;;AAIV,IAAa,yBAAb,MAAa,uBAAuB;CAClC,YACE,AAASC,0BACT,AAASC,yBACT;EAFS;EACA;;CAGX,AAAO,UAAkB;AACvB,SAAO,UAAU,KAAK,wBAAwB,SAAS,CAAC,GAAG,KAAK,wBAAwB,cACtF,KACD;;CAGH,AAAO,kBAA0B;AAC/B,SAAO,UAAU,KAAK,wBAAwB,SAAS,CAAC,GAAG,KAAK,wBAAwB,cACtF,KACD;;CAIH,AAAO,QACL,SAGA,KACoB;AACpB,SAAO,IAAI,WAAW,KAAK,iBAAiB,EAAE,QAAQ,MAAM,IAAI;;CAGlE,MAAa,OAAO;AAClB,QAAM,KAAK,wBAAwB,MAAM;AACzC,OAAK,yBAAyB,OAAO;;CAmBvC,aAAoB,MAClB,OACA,gCACE,IAAI,kBAAkB,EACS;EACjC,MAAM,2BACJ,OAAO,UAAU,aACb,MAAM,uBAAuB,MAAM,GACnC,MAAM,uBAAuB,MAAM;AAKzC,SAAO,IAAI,uBACT,0BAL8B,MAAM,4BACnC,yBAAyB,SAAS,CAAqB,MACxD,wBACD,CAIA;;;AAIL,IAAa,mBAAb,cAAsC,iBAAiB;CACrD,YAAY,UAAU,UAAU;AAC9B,QAAM,gCAAgC,UAAU;;;AAGpD,IAAa,aAAb,MAAmD;CACjD,YACE,AAAQC,iBACR,AAAQC,SACR,AAAQC,YACR;EAHQ;EACA;EACA;;CAIV,MAAa,IACX,MACA,SACuE;AACvE,YAAQC,WAAS,cAAc;EAE/B,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,gBAAgB,SAAS;GACvD,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UAAU,EACnB,OAAO,iDACL,KAAK,QACN,uBAAuB,KAAK,WAAW,eAAe,OAAO,KAAK,CAAC,KACrE,CAAC;GACH,CAAC;AAEF,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MAAM,SAAS,IAAI,OAAO,sBAAsB,cAAc;;EAI1E,MAAM,SAAS,MAAM,aAAa,IAAI,KAAK,EAAE,SAAS;AAKtD,MAAI,MAAM,WAAW,EACnB,QAAO;AAGT,SAAOA,QAAM,YAAY,MAAM,GAAI,MAAM;;CAI3C,MAAa,OACX,SAKyD;AACzD,YAAQA,WAAS,cAAc;EAE/B,MAAM,QAAQ,MAAM,KAAK,WAAW;AAEpC,SAAO,OAAO,YACZ,MAAM,KAAK,EAAE,KAAK,YAAY;AAC5B,UAAO,CAAC,KAAKA,QAAM,YAAY,MAAM,CAAC;IACtC,CACH;;CAGH,MAAc,YAA2D;EACvE,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,gBAAgB,SAAS;GACvD,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UAAU,EACnB,OAAO,sDAAsD,KAAK,QAAQ,uBAAuB,KAAK,WAAW,KAClH,CAAC;GACH,CAAC;AAEF,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MAAM,SAAS,IAAI,OAAO,sBAAsB,cAAc;;AAS1E,UALe,MAAM,aAAa,IAAI,KAAK,EAAE,SAAS;;CAaxD,MAAa,IACX,MACA,OACA,SACe;AACf,YAAQA,WAAS,cAAc;EAC/B,MAAM,kBAAkBA,QAAM,UAAU,MAAM;EAE9C,MAAM,QAAQ,MAAM,KAAK,WAAW;AAEpC,QAAM,KAAK;GAAE,KAAK,OAAO,KAAK;GAAE,OAAO;GAAiB,CAAC;AAEzD,QAAM,KAAK,UAAU,MAAM,KAAK,EAAE,KAAK,qBAAY,CAAC,KAAKC,QAAM,CAAC,CAAC;;CAMnE,MAAa,OACX,QACA,SAKA;AACA,YAAQD,WAAS,cAAc;AAE/B,SAAO,KAAK,UACV,OAAO,QAIL,OAAO,CAAC,KAAK,CAAC,KAAK,WAAW;AAC9B,UAAO,CAAC,KAAKA,QAAM,UAAU,MAAM,CAAC;IACpC,CACH;;CAGH,MAAc,UACZ,SACA,SACA;EACA,MAAM,MAAM,MAAM,MAChB,GAAG,KAAK,gBAAgB,YAAY,KAAK,QAAQ,SACjD;GACE,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UACT;IACE;IACA,YAAY,KAAK;IACjB,WAAW,OAAO,YAAY,QAAQ;IACvC,GACA,KAAK,UAAU;AACd,QAAI,iBAAiB,WACnB,QAAO,MAAM,KAAK,MAAM;QAGxB,QAAO;KAGZ;GACF,CACF;AAED,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MACR,SAAS,IAAI,OAAO,wBAAwB,cAC7C;;;;AAKP,MAAa,qBAAkC;AAC7C,QAAO,MAAM"}
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":";;;;;;;;;;;AA4CA,IAAM,8BAAN,MAA0D;CACxD,AAAQ,mBAAmB;CAC3B,AAAQ,oBAAoB;CAC5B,AAAiB;CACjB,AAAiB;CAEjB,YAAY,OAAO,MAAM,iBAAiB,KAAK;AAC7C,OAAK,OAAO;AACZ,OAAK,iBAAiB;;CAGxB,AAAO,mBAAmB,kBAAgC;AACxD,OAAK,mBAAmB;AACxB,OAAK,oBAAoB;AACzB,SAAO;;CAGT,AAAO,sBAA+B;AACpC,SAAO,KAAK;;CAGd,AAAO,oBAA4B;AACjC,SAAO,KAAK;;CAGd,MAAa,eACX,WACA,YACe;EAIf,MAAM,WAAW,WAHF,MAAM,2BAA2B,EAC5B,KAAK,iBAAiB,KAEV,GADb,WAAW,WAAW,KAAK,KAAK;EAGnD,MAAM,YAAY,KAAK,KAAK;AAE5B,SAAO,KAAK,KAAK,GAAG,YAAY,KAAK,kBAAkB;AACrD,OAAI;AASF,SARY,MAAM,MAAM,GAAG,SAAS,SAAS;KAC3C,QAAQ;KACR,SAAS,EAAE,gBAAgB,oBAAoB;KAC/C,MAAM,KAAK,UAAU,EACnB,OAAO,uCACR,CAAC;KACH,CAAC,EAEM,GAEN;WAEI;AAIR,SAAM,IAAI,SAAS,YAAY,WAAW,SAAS,KAAK,eAAe,CAAC;;AAG1E,QAAM,IAAI,MACR,sCAAsC,KAAK,iBAAiB,IAC7D;;;AAWL,eAAe,uBACb,OAC4B;CAE5B,IAAIA;AAIJ,KAAI,OAAO,UAAU,YAAY;EAC/B,MAAM,kBAAkB,UAAU;AAClC,QAAM,gBAAgB;AACtB,YAAU,gBAAgB,cAAc;OAExC,WAAU,sBAAsB,MAAM;CAIxC,MAAM,oBAAoB,MAAM,aAAa,QAAQ;AACrD,OAAM,IAAI,SAAS,SAAS,WAAW;AACrC,oBACG,OAAO,EAAE,CACT,KAAK,aAAa,QAAQ,CAC1B,KAAK,SAAS,OAAO;GACxB;CACF,MAAM,oBAAqB,kBAAkB,SAAS,CACnD;AACH,SAAQ,KAAK,uCAAuC,oBAAoB;AAExE,QAAO;;AAIT,eAAe,4BACb,mBACA,yBAC+B;CAC/B,MAAM,mBAAmB,yBAAyB,CAE/C,iBAAiB,MAAM,KAAK,CAE5B,iBACC,KAAK,OAAO;EACV,KAAK,QAAQ,mBAAmB,KAAK;EACrC,KAAK,QAAQ,WAAW,KAAK;EAC7B,IAAI,6BAA6B;EAClC,CAAC,CACH;AAIH,OAAM,eAAe,gBAAgB,kBAAkB;CAGvD,MAAM,0BAA0B,MAAM,iBAAiB,OAAO;AAG9D,KAAI;AACF,UAAQ,KACN,+DAA+D,kBAAkB,KAClF;EAGD,MAAM,MAAM,MAAM,MAChB,UAAU,wBAAwB,SAAS,CAAC,GAAG,wBAAwB,cACrE,KACD,CAAC,eACF;GACE,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UAAU,EAEnB,KAAK,uCAAuC,qBAC7C,CAAC;GACH,CACF;AACD,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MACR,SAAS,IAAI,OAAO,wBAAwB,cAC7C;;EAGH,MAAM,OAAQ,MAAM,IAAI,MAAM;AAC9B,UAAQ,KACN,wBACA,MAAM,UAAU,KAAK,MAAM,EAAE,KAAK,CACnC;AACD,SAAO;UACA,GAAG;AACV,QAAM,wBAAwB,MAAM;AACpC,QAAM;;;AAIV,IAAa,yBAAb,MAAa,uBAAuB;CAClC,YACE,AAASC,0BACT,AAASC,yBACT;EAFS;EACA;;CAGX,AAAO,UAAkB;AACvB,SAAO,UAAU,KAAK,wBAAwB,SAAS,CAAC,GAAG,KAAK,wBAAwB,cACtF,KACD;;CAGH,AAAO,kBAA0B;AAC/B,SAAO,UAAU,KAAK,wBAAwB,SAAS,CAAC,GAAG,KAAK,wBAAwB,cACtF,KACD;;CAIH,AAAO,QACL,SAGA,KACoB;AACpB,SAAO,IAAI,WAAW,KAAK,iBAAiB,EAAE,QAAQ,MAAM,IAAI;;CAGlE,MAAa,OAAO;AAClB,QAAM,KAAK,wBAAwB,MAAM;AACzC,OAAK,yBAAyB,OAAO;;CAmBvC,aAAoB,MAClB,OACA,gCACE,IAAI,kBAAkB,EACS;EACjC,MAAM,2BACJ,OAAO,UAAU,aACb,MAAM,uBAAuB,MAAM,GACnC,MAAM,uBAAuB,MAAM;AAKzC,SAAO,IAAI,uBACT,0BAL8B,MAAM,4BACnC,yBAAyB,SAAS,CAAqB,MACxD,wBACD,CAIA;;;AAIL,IAAa,mBAAb,cAAsC,iBAAiB;CACrD,YAAY,UAAU,UAAU;AAC9B,QAAM,gCAAgC,UAAU;;;AAGpD,IAAa,aAAb,MAAmD;CACjD,YACE,AAAQC,iBACR,AAAQC,SACR,AAAQC,YACR;EAHQ;EACA;EACA;;CAIV,MAAa,IACX,MACA,SACuE;AACvE,YAAQC,WAAS,cAAc;EAE/B,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,gBAAgB,SAAS;GACvD,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UAAU,EACnB,OAAO,iDACL,KAAK,QACN,uBAAuB,KAAK,WAAW,eAAe,OAAO,KAAK,CAAC,KACrE,CAAC;GACH,CAAC;AAEF,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MAAM,SAAS,IAAI,OAAO,sBAAsB,cAAc;;EAI1E,MAAM,SAAS,MAAM,aAAa,IAAI,KAAK,EAAE,SAAS;AAKtD,MAAI,MAAM,WAAW,EACnB,QAAO;AAGT,SAAOA,QAAM,YAAY,MAAM,GAAI,MAAM;;CAI3C,MAAa,OACX,SAKyD;AACzD,YAAQA,WAAS,cAAc;EAE/B,MAAM,QAAQ,MAAM,KAAK,WAAW;AAEpC,SAAO,OAAO,YACZ,MAAM,KAAK,EAAE,KAAK,YAAY;AAC5B,UAAO,CAAC,KAAKA,QAAM,YAAY,MAAM,CAAC;IACtC,CACH;;CAGH,MAAc,YAA2D;EACvE,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,gBAAgB,SAAS;GACvD,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UAAU,EACnB,OAAO,sDAAsD,KAAK,QAAQ,uBAAuB,KAAK,WAAW,KAClH,CAAC;GACH,CAAC;AAEF,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MAAM,SAAS,IAAI,OAAO,sBAAsB,cAAc;;AAS1E,UALe,MAAM,aAAa,IAAI,KAAK,EAAE,SAAS;;CAaxD,MAAa,IACX,MACA,OACA,SACe;AACf,YAAQA,WAAS,cAAc;EAC/B,MAAM,kBAAkBA,QAAM,UAAU,MAAM;EAE9C,MAAM,QAAQ,MAAM,KAAK,WAAW;AAEpC,QAAM,KAAK;GAAE,KAAK,OAAO,KAAK;GAAE,OAAO;GAAiB,CAAC;AAEzD,QAAM,KAAK,UAAU,MAAM,KAAK,EAAE,KAAK,qBAAY,CAAC,KAAKC,QAAM,CAAC,CAAC;;CAMnE,MAAa,OACX,QACA,SAKA;AACA,YAAQD,WAAS,cAAc;AAE/B,SAAO,KAAK,UACV,OAAO,QAIL,OAAO,CAAC,KAAK,CAAC,KAAK,WAAW;AAC9B,UAAO,CAAC,KAAKA,QAAM,UAAU,MAAM,CAAC;IACpC,CACH;;CAGH,MAAc,UACZ,SACA,SACA;EACA,MAAM,MAAM,MAAM,MAChB,GAAG,KAAK,gBAAgB,YAAY,KAAK,QAAQ,SACjD;GACE,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACD,MAAM,KAAK,UACT;IACE;IACA,YAAY,KAAK;IACjB,WAAW,OAAO,YAAY,QAAQ;IACvC,GACA,KAAK,UAAU;AACd,QAAI,iBAAiB,WACnB,QAAO,MAAM,KAAK,MAAM;QAGxB,QAAO;KAGZ;GACF,CACF;AAED,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,cAAc,MAAM,IAAI,MAAM;AACpC,SAAM,IAAI,MACR,SAAS,IAAI,OAAO,wBAAwB,cAC7C;;;;AAKP,MAAa,qBAAkC;AAC7C,QAAO,MAAM"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@restatedev/restate-sdk-testcontainers",
3
- "version": "1.10.2",
3
+ "version": "1.10.4",
4
4
  "description": "Typescript SDK for Restate",
5
5
  "author": "Restate Developers",
6
6
  "email": "code@restate.dev",
@@ -34,8 +34,8 @@
34
34
  "dependencies": {
35
35
  "apache-arrow": "^18.0.0",
36
36
  "testcontainers": "^10.24.1",
37
- "@restatedev/restate-sdk": "1.10.2",
38
- "@restatedev/restate-sdk-clients": "1.10.2"
37
+ "@restatedev/restate-sdk": "1.10.4",
38
+ "@restatedev/restate-sdk-clients": "1.10.4"
39
39
  },
40
40
  "devDependencies": {},
41
41
  "scripts": {