From b0e60be58a946023698e8c8d06b734495e24d48c Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Thu, 21 Aug 2025 10:40:50 +0200 Subject: [PATCH 1/4] Enable Lambda Compression Bump Node minimum version to 20.19 --- README.md | 2 +- package-lock.json | 156 ++++++++++-------- package.json | 8 +- packages/restate-e2e-services/package.json | 4 +- packages/restate-sdk-clients/package.json | 2 +- .../package.json | 2 +- packages/restate-sdk-core/package.json | 2 +- packages/restate-sdk-examples/package.json | 2 +- .../restate-sdk-testcontainers/package.json | 2 +- packages/restate-sdk-zod/package.json | 2 +- packages/restate-sdk/package.json | 2 +- .../restate-sdk/src/endpoint/discovery.ts | 4 + .../src/endpoint/fetch_endpoint.ts | 3 +- .../src/endpoint/handlers/generic.ts | 42 +++-- .../src/endpoint/handlers/lambda.ts | 92 +++++++++-- .../src/endpoint/lambda_endpoint.ts | 25 ++- .../restate-sdk/src/endpoint/node_endpoint.ts | 2 +- packages/restate-sdk/src/lambda.ts | 17 +- 18 files changed, 247 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 39755813..ee200108 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ restate.serve({ services: [greeter], port: 9080 }); ## Using the SDK Prerequisites: -- [NodeJS](https://nodejs.org/en/) >= v18.17.1 or [Bun](https://bun.sh/docs/installation) or [Deno](https://deno.land/#installation) +- [NodeJS](https://nodejs.org/en/) >= v20.19 or [Bun](https://bun.sh/docs/installation) or [Deno](https://deno.land/#installation) - [npm CLI](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) >= 9.6.7 To use this SDK, add the dependency to your project: diff --git a/package-lock.json b/package-lock.json index faf23636..367a93ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "root", - "version": "1.8.0", + "version": "1.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "root", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "workspaces": [ "packages/restate-sdk-core", @@ -22,7 +22,7 @@ "@arethetypeswrong/cli": "^0.15.3", "@microsoft/api-extractor": "^7.52.8", "@release-it-plugins/workspaces": "^4.2.0", - "@types/node": "^20.10.4", + "@types/node": "^20.19.11", "@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/parser": "^7.13.0", "eslint": "^8.57.0", @@ -31,14 +31,14 @@ "eslint-plugin-require-extensions": "^0.1.3", "prettier": "^2.8.4", "release-it": "^17.11.0", - "typedoc": "^0.28.7", - "typescript": "^5.4.5", + "typedoc": "^0.28.10", + "typescript": "^5.9.2", "vitest": "^3.0.9", "wasm-pack": "^0.0.0", "wasm-pack-inline": "^0.1.2" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" } }, "node_modules/@andrewbranch/untar.js": { @@ -713,16 +713,16 @@ } }, "node_modules/@gerrit0/mini-shiki": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.7.0.tgz", - "integrity": "sha512-7iY9wg4FWXmeoFJpUL2u+tsmh0d0jcEJHAIzVxl3TG4KL493JNnisdLAILZ77zcD+z3J0keEXZ+lFzUgzQzPDg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.11.0.tgz", + "integrity": "sha512-ooCDMAOKv71O7MszbXjSQGcI6K5T6NKlemQZOBHLq7Sv/oXCRfYbZ7UgbzFdl20lSXju6Juds4I3y30R6rHA4Q==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^3.7.0", - "@shikijs/langs": "^3.7.0", - "@shikijs/themes": "^3.7.0", - "@shikijs/types": "^3.7.0", + "@shikijs/engine-oniguruma": "^3.11.0", + "@shikijs/langs": "^3.11.0", + "@shikijs/themes": "^3.11.0", + "@shikijs/types": "^3.11.0", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -998,6 +998,20 @@ "node": ">=10" } }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@microsoft/tsdoc": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", @@ -1881,40 +1895,40 @@ "license": "BSD-3-Clause" }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz", - "integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.11.0.tgz", + "integrity": "sha512-4DwIjIgETK04VneKbfOE4WNm4Q7WC1wo95wv82PoHKdqX4/9qLRUwrfKlmhf0gAuvT6GHy0uc7t9cailk6Tbhw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.7.0", + "@shikijs/types": "3.11.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz", - "integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.11.0.tgz", + "integrity": "sha512-Njg/nFL4HDcf/ObxcK2VeyidIq61EeLmocrwTHGGpOQx0BzrPWM1j55XtKQ1LvvDWH15cjQy7rg96aJ1/l63uw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.7.0" + "@shikijs/types": "3.11.0" } }, "node_modules/@shikijs/themes": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz", - "integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.11.0.tgz", + "integrity": "sha512-BhhWRzCTEk2CtWt4S4bgsOqPJRkapvxdsifAwqP+6mk5uxboAQchc0etiJ0iIasxnMsb764qGD24DK9albcU9Q==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.7.0" + "@shikijs/types": "3.11.0" } }, "node_modules/@shikijs/types": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz", - "integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.11.0.tgz", + "integrity": "sha512-RB7IMo2E7NZHyfkqAuaf4CofyY8bPzjWPjJRzn6SEak3b46fIQyG6Vx5fG/obqkfppQ+g8vEsiD7Uc6lqQt32Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2017,12 +2031,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.17.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.4.tgz", - "integrity": "sha512-Fi1Bj8qTJr4f1FDdHFR7oMlOawEYSzkHNdBJK+aRjcDDNHwEV3jPPjuZP2Lh2QNgXeqzM8Y+U6b6urKAog2rZw==", + "version": "20.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", + "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, "node_modules/@types/ssh2": { @@ -9309,13 +9323,13 @@ } }, "node_modules/typedoc": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.7.tgz", - "integrity": "sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==", + "version": "0.28.10", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.10.tgz", + "integrity": "sha512-zYvpjS2bNJ30SoNYfHSRaFpBMZAsL7uwKbWwqoCNFWjcPnI3e/mPLh2SneH9mX7SJxtDpvDgvd9/iZxGbo7daw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@gerrit0/mini-shiki": "^3.7.0", + "@gerrit0/mini-shiki": "^3.9.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", @@ -9329,7 +9343,7 @@ "pnpm": ">= 10" }, "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" } }, "node_modules/typedoc/node_modules/brace-expansion": { @@ -9359,9 +9373,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9416,9 +9430,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { @@ -10244,99 +10258,99 @@ }, "packages/restate-e2e-services": { "name": "@restatedev/restate-e2e-services", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "dependencies": { - "@restatedev/restate-sdk": "^1.8.0", - "@restatedev/restate-sdk-clients": "^1.8.0", + "@restatedev/restate-sdk": "^1.8.3", + "@restatedev/restate-sdk-clients": "^1.8.3", "heapdump": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@types/heapdump": "^0.3.4", - "@types/node": "^20.17.4", + "@types/node": "^20.19.11", "@types/uuid": "^10.0.0", "tsx": "^4.15.7" }, "engines": { - "node": ">= 18.13" + "node": ">= 22.15" } }, "packages/restate-sdk": { "name": "@restatedev/restate-sdk", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "dependencies": { - "@restatedev/restate-sdk-core": "^1.8.0" + "@restatedev/restate-sdk-core": "^1.8.3" }, "devDependencies": { "@types/aws-lambda": "^8.10.115" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" } }, "packages/restate-sdk-clients": { "name": "@restatedev/restate-sdk-clients", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "dependencies": { - "@restatedev/restate-sdk-core": "^1.8.0" + "@restatedev/restate-sdk-core": "^1.8.3" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" } }, "packages/restate-sdk-cloudflare-workers": { "name": "@restatedev/restate-sdk-cloudflare-workers", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "dependencies": { - "@restatedev/restate-sdk-core": "^1.8.0" + "@restatedev/restate-sdk-core": "^1.8.3" }, "devDependencies": { "@types/aws-lambda": "^8.10.115" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" } }, "packages/restate-sdk-core": { "name": "@restatedev/restate-sdk-core", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "engines": { - "node": ">= 18.13" + "node": ">= 20.19" } }, "packages/restate-sdk-examples": { "name": "@restatedev/restate-sdk-examples", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "dependencies": { - "@restatedev/restate-sdk": "^1.8.0", - "@restatedev/restate-sdk-clients": "^1.8.0" + "@restatedev/restate-sdk": "^1.8.3", + "@restatedev/restate-sdk-clients": "^1.8.3" }, "devDependencies": { - "@restatedev/restate-sdk-testcontainers": "^1.8.0", + "@restatedev/restate-sdk-testcontainers": "^1.8.3", "tsx": "^4.15.7" }, "engines": { - "node": ">= 18.13" + "node": ">= 22.15" } }, "packages/restate-sdk-testcontainers": { "name": "@restatedev/restate-sdk-testcontainers", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "dependencies": { - "@restatedev/restate-sdk": "^1.8.0", - "@restatedev/restate-sdk-clients": "^1.8.0", + "@restatedev/restate-sdk": "^1.8.3", + "@restatedev/restate-sdk-clients": "^1.8.3", "apache-arrow": "^18.0.0", "testcontainers": "^10.24.1" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" } }, "packages/restate-sdk-testcontainers/node_modules/@types/dockerode": { @@ -10470,17 +10484,17 @@ }, "packages/restate-sdk-zod": { "name": "@restatedev/restate-sdk-zod", - "version": "1.8.0", + "version": "1.8.3", "license": "MIT", "dependencies": { "zod": "^3.24.1", "zod-to-json-schema": "3.24.3" }, "devDependencies": { - "@restatedev/restate-sdk-core": "^1.8.0" + "@restatedev/restate-sdk-core": "^1.8.3" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" } } } diff --git a/package.json b/package.json index 058cc70d..67941000 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@arethetypeswrong/cli": "^0.15.3", "@microsoft/api-extractor": "^7.52.8", "@release-it-plugins/workspaces": "^4.2.0", - "@types/node": "^20.10.4", + "@types/node": "^20.19.11", "@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/parser": "^7.13.0", "eslint": "^8.57.0", @@ -50,14 +50,14 @@ "eslint-plugin-require-extensions": "^0.1.3", "prettier": "^2.8.4", "release-it": "^17.11.0", - "typedoc": "^0.28.7", - "typescript": "^5.4.5", + "typedoc": "^0.28.10", + "typescript": "^5.9.2", "vitest": "^3.0.9", "wasm-pack": "^0.0.0", "wasm-pack-inline": "^0.1.2" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" }, "publishConfig": { "@restatedev:registry": "https://registry.npmjs.org", diff --git a/packages/restate-e2e-services/package.json b/packages/restate-e2e-services/package.json index b35c5fc1..331e08b1 100644 --- a/packages/restate-e2e-services/package.json +++ b/packages/restate-e2e-services/package.json @@ -37,11 +37,11 @@ }, "devDependencies": { "@types/heapdump": "^0.3.4", - "@types/node": "^20.17.4", + "@types/node": "^20.19.11", "@types/uuid": "^10.0.0", "tsx": "^4.15.7" }, "engines": { - "node": ">= 18.13" + "node": ">= 22.15" } } diff --git a/packages/restate-sdk-clients/package.json b/packages/restate-sdk-clients/package.json index 658104ae..7bcfae51 100644 --- a/packages/restate-sdk-clients/package.json +++ b/packages/restate-sdk-clients/package.json @@ -50,7 +50,7 @@ "@restatedev/restate-sdk-core": "^1.8.3" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" }, "directories": { "test": "test" diff --git a/packages/restate-sdk-cloudflare-workers/package.json b/packages/restate-sdk-cloudflare-workers/package.json index ec31559f..78fe0a8f 100644 --- a/packages/restate-sdk-cloudflare-workers/package.json +++ b/packages/restate-sdk-cloudflare-workers/package.json @@ -74,7 +74,7 @@ "@types/aws-lambda": "^8.10.115" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" }, "publishConfig": { "@restatedev:registry": "https://registry.npmjs.org" diff --git a/packages/restate-sdk-core/package.json b/packages/restate-sdk-core/package.json index 050f0d0e..f893cfca 100644 --- a/packages/restate-sdk-core/package.json +++ b/packages/restate-sdk-core/package.json @@ -46,7 +46,7 @@ "release": "release-it" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" }, "directories": { "example": "examples", diff --git a/packages/restate-sdk-examples/package.json b/packages/restate-sdk-examples/package.json index b7e97c74..2feb1be8 100644 --- a/packages/restate-sdk-examples/package.json +++ b/packages/restate-sdk-examples/package.json @@ -44,6 +44,6 @@ "@restatedev/restate-sdk-testcontainers": "^1.8.3" }, "engines": { - "node": ">= 18.13" + "node": ">= 22.15" } } diff --git a/packages/restate-sdk-testcontainers/package.json b/packages/restate-sdk-testcontainers/package.json index 1fd9ee09..5f768f09 100644 --- a/packages/restate-sdk-testcontainers/package.json +++ b/packages/restate-sdk-testcontainers/package.json @@ -53,7 +53,7 @@ "testcontainers": "^10.24.1" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" }, "directories": { "test": "test" diff --git a/packages/restate-sdk-zod/package.json b/packages/restate-sdk-zod/package.json index 94971f4d..5c301ee6 100644 --- a/packages/restate-sdk-zod/package.json +++ b/packages/restate-sdk-zod/package.json @@ -53,7 +53,7 @@ "release": "release-it" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" }, "directories": { "example": "examples", diff --git a/packages/restate-sdk/package.json b/packages/restate-sdk/package.json index 3c93fb88..22dab62b 100644 --- a/packages/restate-sdk/package.json +++ b/packages/restate-sdk/package.json @@ -99,7 +99,7 @@ "@types/aws-lambda": "^8.10.115" }, "engines": { - "node": ">= 18.13" + "node": ">= 20.19" }, "directories": { "example": "examples", diff --git a/packages/restate-sdk/src/endpoint/discovery.ts b/packages/restate-sdk/src/endpoint/discovery.ts index 1d204268..cb6dee0a 100644 --- a/packages/restate-sdk/src/endpoint/discovery.ts +++ b/packages/restate-sdk/src/endpoint/discovery.ts @@ -25,6 +25,10 @@ export interface Endpoint { * Maximum supported protocol version */ maxProtocolVersion: number; + /** + * Compression used when the endpoint is a Lambda. This is unsupported if the endpoint is a regular HTTP endpoint. + */ + lambdaCompression?: "zstd"; services: Service[]; } export interface Service { diff --git a/packages/restate-sdk/src/endpoint/fetch_endpoint.ts b/packages/restate-sdk/src/endpoint/fetch_endpoint.ts index 707ab1ce..9e93e4b2 100644 --- a/packages/restate-sdk/src/endpoint/fetch_endpoint.ts +++ b/packages/restate-sdk/src/endpoint/fetch_endpoint.ts @@ -102,7 +102,8 @@ export class FetchEndpointImpl implements FetchEndpoint { } { const genericHandler = new GenericHandler( this.builder.build(), - this.protocolMode + this.protocolMode, + {} ); return fetcher(genericHandler); } diff --git a/packages/restate-sdk/src/endpoint/handlers/generic.ts b/packages/restate-sdk/src/endpoint/handlers/generic.ts index 2a5afc56..69cb5588 100644 --- a/packages/restate-sdk/src/endpoint/handlers/generic.ts +++ b/packages/restate-sdk/src/endpoint/handlers/generic.ts @@ -16,7 +16,10 @@ import { RetryableError, TerminalError, } from "../../types/errors.js"; -import type { ProtocolMode } from "../discovery.js"; +import type { + ProtocolMode, + Endpoint as EndpointManifest, +} from "../discovery.js"; import type { Component, ComponentHandler } from "../components.js"; import { parseUrlComponents } from "../components.js"; import { X_RESTATE_SERVER } from "../../user_agent.js"; @@ -78,6 +81,7 @@ export interface RestateHandler { const ENDPOINT_MANIFEST_V2 = "application/vnd.restate.endpointmanifest.v2+json"; const ENDPOINT_MANIFEST_V3 = "application/vnd.restate.endpointmanifest.v3+json"; +const ENDPOINT_MANIFEST_V4 = "application/vnd.restate.endpointmanifest.v4+json"; /** * This is an internal API to support 'fetch' like handlers. @@ -93,7 +97,8 @@ export class GenericHandler implements RestateHandler { constructor( readonly endpoint: Endpoint, - private readonly protocolMode: ProtocolMode + private readonly protocolMode: ProtocolMode, + private readonly additionalDiscoveryFields: Partial ) { // Setup identity verifier if ( @@ -489,7 +494,9 @@ export class GenericHandler implements RestateHandler { // Negotiate version to use let manifestVersion; - if (acceptVersionsString.includes(ENDPOINT_MANIFEST_V3)) { + if (acceptVersionsString.includes(ENDPOINT_MANIFEST_V4)) { + manifestVersion = 4; + } else if (acceptVersionsString.includes(ENDPOINT_MANIFEST_V3)) { manifestVersion = 3; } else if (acceptVersionsString.includes(ENDPOINT_MANIFEST_V2)) { manifestVersion = 2; @@ -501,20 +508,9 @@ export class GenericHandler implements RestateHandler { const discovery = { ...this.endpoint.discoveryMetadata, + ...this.additionalDiscoveryFields, protocolMode: this.protocolMode, }; - const body = JSON.stringify(discovery); - - // type AllowedNames = { [K in keyof T]: T[K] extends U ? K : never; }[keyof T]; - // - // const checkUnsupportedFeature = (obj: Record, fields: Array)=> { - // for (const field of fields) { - // if (field in obj && obj[field] !== undefined) { - // return this.toErrorResponse(500, `The code uses the new discovery feature '${field}' but the runtime doesn't support it yet. Either remove the usage of this feature, or upgrade the runtime.`); - // } - // } - // return; - // } const checkUnsupportedFeature = ( obj: T, @@ -526,7 +522,7 @@ export class GenericHandler implements RestateHandler { 500, `The code uses the new discovery feature '${String( field - )}' but the runtime doesn't support it yet. Either remove the usage of this feature, or upgrade the runtime.` + )}' but the runtime doesn't support it yet (discovery protocol negotiated version ${manifestVersion}). Either remove the usage of this feature, or upgrade the runtime.` ); } } @@ -566,10 +562,22 @@ export class GenericHandler implements RestateHandler { } } + if (manifestVersion < 4) { + const error = checkUnsupportedFeature(discovery, "lambdaCompression"); + if (error !== undefined) { + return error; + } + } + + const body = JSON.stringify(discovery); return { headers: { "content-type": - manifestVersion === 2 ? ENDPOINT_MANIFEST_V2 : ENDPOINT_MANIFEST_V3, + manifestVersion === 2 + ? ENDPOINT_MANIFEST_V2 + : manifestVersion === 3 + ? ENDPOINT_MANIFEST_V3 + : ENDPOINT_MANIFEST_V4, "x-restate-server": X_RESTATE_SERVER, }, statusCode: 200, diff --git a/packages/restate-sdk/src/endpoint/handlers/lambda.ts b/packages/restate-sdk/src/endpoint/handlers/lambda.ts index b9258aeb..0c828a62 100644 --- a/packages/restate-sdk/src/endpoint/handlers/lambda.ts +++ b/packages/restate-sdk/src/endpoint/handlers/lambda.ts @@ -26,9 +26,15 @@ import { WritableStream, type ReadableStream } from "node:stream/web"; import { OnceStream } from "../../utils/streams.js"; import { X_RESTATE_SERVER } from "../../user_agent.js"; import { ensureError } from "../../types/errors.js"; +import * as zlib from "node:zlib"; export class LambdaHandler { - constructor(private readonly handler: GenericHandler) {} + constructor(private readonly handler: GenericHandler, compression: boolean) { + // If compression is enabled, let's check we're running a node version that supports it + if (compression) { + checkCompressionSupported(); + } + } async handleRequest( event: APIGatewayProxyEvent | APIGatewayProxyEventV2, @@ -39,33 +45,69 @@ export class LambdaHandler { // const path = "path" in event ? event.path : event.rawPath; + // Deal with content-encoding + let requestContentEncoding; + let requestAcceptEncoding; + for (const [key, value] of Object.entries(event.headers)) { + if ( + key.localeCompare("content-encoding", undefined, { + sensitivity: "accent", + }) + ) { + requestContentEncoding = value; + continue; + } + if ( + key.localeCompare("accept-encoding", undefined, { + sensitivity: "accent", + }) + ) { + requestAcceptEncoding = value; + } + } + // // Convert the request body to a Uint8Array stream // Lambda functions receive the body as base64 encoded string // - let body: ReadableStream | null; + let bodyStream: ReadableStream | null; if (!event.body) { - body = null; - } else if (event.isBase64Encoded) { - body = OnceStream(Buffer.from(event.body, "base64")); + bodyStream = null; } else { - body = OnceStream(new TextEncoder().encode(event.body)); + let bodyBuffer: Buffer | undefined; + if (event.isBase64Encoded) { + bodyBuffer = Buffer.from(event.body, "base64"); + } else { + bodyBuffer = Buffer.from(new TextEncoder().encode(event.body)); + } + + // Now decode if needed + if (requestContentEncoding && requestContentEncoding.includes("zstd")) { + checkCompressionSupported(); + // Input encoded with zstd, let's decode it! + bodyBuffer = ( + zlib as unknown as { zstdDecompressSync: (b: Buffer) => Buffer } + ).zstdDecompressSync(bodyBuffer); + } + + // Prep the stream to pass through the endpoint handler + bodyStream = OnceStream(bodyBuffer); } const abortController = new AbortController(); const request: RestateRequest = { - body, + body: bodyStream, headers: event.headers, url: path, extraArgs: [context], abortSignal: abortController.signal, }; - let resp: RestateResponse; + let response: RestateResponse; try { - resp = await this.handler.handle(request, { + response = await this.handler.handle(request, { AWSRequestId: context.awsRequestId, }); } catch (e) { @@ -76,7 +118,7 @@ export class LambdaHandler { const chunks: Uint8Array[] = []; try { - await resp.body.pipeTo( + await response.body.pipeTo( new WritableStream({ write: (chunk) => { chunks.push(chunk); @@ -103,11 +145,35 @@ export class LambdaHandler { abortController.abort(); } + const responseBodyBuffer = Buffer.concat(chunks); + let responseBody; + + // Now let's encode if we need to. + if (requestAcceptEncoding && requestAcceptEncoding.includes("zstd")) { + checkCompressionSupported(); + response.headers["content-encoding"] = "zstd"; + + responseBody = ( + zlib as unknown as { zstdCompressSync: (b: Buffer) => Buffer } + ) + .zstdCompressSync(responseBodyBuffer) + .toString("base64"); + } else { + responseBody = responseBodyBuffer.toString("base64"); + } return { - headers: resp.headers, - statusCode: resp.statusCode, + headers: response.headers, + statusCode: response.statusCode, isBase64Encoded: true, - body: Buffer.concat(chunks).toString("base64"), + body: responseBody, }; } } + +function checkCompressionSupported() { + if (!("zstdDecompressSync" in zlib) || !("zstdCompressSync" in zlib)) { + throw new Error( + "Compression is enabled, but you're running a node version that doesn't support zstd compression. Please use Node.js >= 22." + ); + } +} diff --git a/packages/restate-sdk/src/endpoint/lambda_endpoint.ts b/packages/restate-sdk/src/endpoint/lambda_endpoint.ts index 6e032bd0..d44ade3d 100644 --- a/packages/restate-sdk/src/endpoint/lambda_endpoint.ts +++ b/packages/restate-sdk/src/endpoint/lambda_endpoint.ts @@ -39,6 +39,15 @@ import type { LoggerTransport } from "../logging/logger_transport.js"; * .handler(); */ export interface LambdaEndpoint extends RestateEndpointBase { + /** + * Enable compression of Lambda requests/responses using zstd. + * + * NOTE: This feature is supported only from Restate 1.5 onward. + * + * @default false + */ + enableCompression(): LambdaEndpoint; + // eslint-disable-next-line @typescript-eslint/no-explicit-any handler(): (event: any, ctx: any) => Promise; } @@ -46,6 +55,8 @@ export interface LambdaEndpoint extends RestateEndpointBase { export class LambdaEndpointImpl implements LambdaEndpoint { private builder: EndpointBuilder = new EndpointBuilder(); + constructor(private compression: boolean) {} + public bind

( definition: | ServiceDefinition @@ -78,13 +89,23 @@ export class LambdaEndpointImpl implements LambdaEndpoint { return this; } + public enableCompression(): LambdaEndpoint { + this.compression = true; + return this; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any handler(): (event: any, ctx: any) => Promise { const genericHandler = new GenericHandler( this.builder.build(), - "REQUEST_RESPONSE" + "REQUEST_RESPONSE", + this.compression + ? { + lambdaCompression: "zstd", + } + : {} ); - const lambdaHandler = new LambdaHandler(genericHandler); + const lambdaHandler = new LambdaHandler(genericHandler, this.compression); return lambdaHandler.handleRequest.bind(lambdaHandler); } } diff --git a/packages/restate-sdk/src/endpoint/node_endpoint.ts b/packages/restate-sdk/src/endpoint/node_endpoint.ts index acac53f0..b01d5033 100644 --- a/packages/restate-sdk/src/endpoint/node_endpoint.ts +++ b/packages/restate-sdk/src/endpoint/node_endpoint.ts @@ -111,7 +111,7 @@ export class NodeEndpoint implements RestateEndpoint { function nodeHttp2Handler( endpoint: Endpoint ): (request: Http2ServerRequest, response: Http2ServerResponse) => void { - const handler = new GenericHandler(endpoint, "BIDI_STREAM"); + const handler = new GenericHandler(endpoint, "BIDI_STREAM", {}); return (request, response) => { (async () => { diff --git a/packages/restate-sdk/src/lambda.ts b/packages/restate-sdk/src/lambda.ts index 388db976..97be99a3 100644 --- a/packages/restate-sdk/src/lambda.ts +++ b/packages/restate-sdk/src/lambda.ts @@ -22,7 +22,18 @@ import { withOptions } from "./endpoint/withOptions.js"; * Create a new {@link LambdaEndpoint}. */ export function endpoint(): LambdaEndpoint { - return new LambdaEndpointImpl(); + return new LambdaEndpointImpl(false); +} + +interface LambdaEndpointOptions extends EndpointOptions { + /** + * Enable compression of Lambda requests/responses using zstd. + * + * NOTE: This feature is supported only from Restate 1.5 onward. + * + * @default false + */ + enableCompression?: boolean; } /** @@ -38,9 +49,9 @@ export function endpoint(): LambdaEndpoint { * * export const handler = createEndpointHandler({ services: [myService] }) */ -export function createEndpointHandler(options: EndpointOptions) { +export function createEndpointHandler(options: LambdaEndpointOptions) { return withOptions( - new LambdaEndpointImpl(), + new LambdaEndpointImpl(options.enableCompression ?? false), options ).handler(); } From 30b3609cb9ea57c5dde784ca9bcc504bd19627bf Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Fri, 22 Aug 2025 10:26:19 +0200 Subject: [PATCH 2/4] Encode only if the response compression threshold is reached. Hardcode it to 3MB --- packages/restate-sdk/src/endpoint/handlers/lambda.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/restate-sdk/src/endpoint/handlers/lambda.ts b/packages/restate-sdk/src/endpoint/handlers/lambda.ts index 0c828a62..fc346e79 100644 --- a/packages/restate-sdk/src/endpoint/handlers/lambda.ts +++ b/packages/restate-sdk/src/endpoint/handlers/lambda.ts @@ -28,6 +28,8 @@ import { X_RESTATE_SERVER } from "../../user_agent.js"; import { ensureError } from "../../types/errors.js"; import * as zlib from "node:zlib"; +const RESPONSE_COMPRESSION_THRESHOLD = 3 * 1024 * 1024; + export class LambdaHandler { constructor(private readonly handler: GenericHandler, compression: boolean) { // If compression is enabled, let's check we're running a node version that supports it @@ -149,7 +151,11 @@ export class LambdaHandler { let responseBody; // Now let's encode if we need to. - if (requestAcceptEncoding && requestAcceptEncoding.includes("zstd")) { + if ( + responseBodyBuffer.length > RESPONSE_COMPRESSION_THRESHOLD && + requestAcceptEncoding && + requestAcceptEncoding.includes("zstd") + ) { checkCompressionSupported(); response.headers["content-encoding"] = "zstd"; From d395d80232fa02990dbfdd5fc050513ed119f4bd Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Fri, 22 Aug 2025 15:31:01 +0200 Subject: [PATCH 3/4] Few changes after feedback. No need to fail anymore if the runtime is not 1.4, remove the explicit flag to enable compression. This will be enabled by default when using Node > 22. --- .../src/endpoint/handlers/generic.ts | 6 ++--- .../src/endpoint/handlers/lambda.ts | 27 +++++++++---------- .../src/endpoint/lambda_endpoint.ts | 24 ++++------------- packages/restate-sdk/src/lambda.ts | 17 +++--------- 4 files changed, 23 insertions(+), 51 deletions(-) diff --git a/packages/restate-sdk/src/endpoint/handlers/generic.ts b/packages/restate-sdk/src/endpoint/handlers/generic.ts index 69cb5588..448ab8fa 100644 --- a/packages/restate-sdk/src/endpoint/handlers/generic.ts +++ b/packages/restate-sdk/src/endpoint/handlers/generic.ts @@ -563,10 +563,8 @@ export class GenericHandler implements RestateHandler { } if (manifestVersion < 4) { - const error = checkUnsupportedFeature(discovery, "lambdaCompression"); - if (error !== undefined) { - return error; - } + // Blank the lambda compression field. No need to fail in this case. + discovery.lambdaCompression = undefined; } const body = JSON.stringify(discovery); diff --git a/packages/restate-sdk/src/endpoint/handlers/lambda.ts b/packages/restate-sdk/src/endpoint/handlers/lambda.ts index fc346e79..ddc26321 100644 --- a/packages/restate-sdk/src/endpoint/handlers/lambda.ts +++ b/packages/restate-sdk/src/endpoint/handlers/lambda.ts @@ -31,12 +31,10 @@ import * as zlib from "node:zlib"; const RESPONSE_COMPRESSION_THRESHOLD = 3 * 1024 * 1024; export class LambdaHandler { - constructor(private readonly handler: GenericHandler, compression: boolean) { - // If compression is enabled, let's check we're running a node version that supports it - if (compression) { - checkCompressionSupported(); - } - } + constructor( + private readonly handler: GenericHandler, + private readonly compressionSupported: boolean + ) {} async handleRequest( event: APIGatewayProxyEvent | APIGatewayProxyEventV2, @@ -85,7 +83,12 @@ export class LambdaHandler { // Now decode if needed if (requestContentEncoding && requestContentEncoding.includes("zstd")) { - checkCompressionSupported(); + if (!this.compressionSupported) { + throw new Error( + "The input is compressed using zstd, but this lambda deployment doesn't support compression. Make sure to deploy the Lambda using Node > 22" + ); + } + // Input encoded with zstd, let's decode it! bodyBuffer = ( zlib as unknown as { zstdDecompressSync: (b: Buffer) => Buffer } @@ -152,11 +155,11 @@ export class LambdaHandler { // Now let's encode if we need to. if ( + this.compressionSupported && responseBodyBuffer.length > RESPONSE_COMPRESSION_THRESHOLD && requestAcceptEncoding && requestAcceptEncoding.includes("zstd") ) { - checkCompressionSupported(); response.headers["content-encoding"] = "zstd"; responseBody = ( @@ -176,10 +179,6 @@ export class LambdaHandler { } } -function checkCompressionSupported() { - if (!("zstdDecompressSync" in zlib) || !("zstdCompressSync" in zlib)) { - throw new Error( - "Compression is enabled, but you're running a node version that doesn't support zstd compression. Please use Node.js >= 22." - ); - } +export function isCompressionSupported() { + return "zstdDecompressSync" in zlib && "zstdCompressSync" in zlib; } diff --git a/packages/restate-sdk/src/endpoint/lambda_endpoint.ts b/packages/restate-sdk/src/endpoint/lambda_endpoint.ts index d44ade3d..ce2dbc59 100644 --- a/packages/restate-sdk/src/endpoint/lambda_endpoint.ts +++ b/packages/restate-sdk/src/endpoint/lambda_endpoint.ts @@ -21,7 +21,7 @@ import type { RestateEndpointBase, } from "../endpoint.js"; import { GenericHandler } from "./handlers/generic.js"; -import { LambdaHandler } from "./handlers/lambda.js"; +import { isCompressionSupported, LambdaHandler } from "./handlers/lambda.js"; import type { LoggerTransport } from "../logging/logger_transport.js"; /** @@ -39,15 +39,6 @@ import type { LoggerTransport } from "../logging/logger_transport.js"; * .handler(); */ export interface LambdaEndpoint extends RestateEndpointBase { - /** - * Enable compression of Lambda requests/responses using zstd. - * - * NOTE: This feature is supported only from Restate 1.5 onward. - * - * @default false - */ - enableCompression(): LambdaEndpoint; - // eslint-disable-next-line @typescript-eslint/no-explicit-any handler(): (event: any, ctx: any) => Promise; } @@ -55,8 +46,6 @@ export interface LambdaEndpoint extends RestateEndpointBase { export class LambdaEndpointImpl implements LambdaEndpoint { private builder: EndpointBuilder = new EndpointBuilder(); - constructor(private compression: boolean) {} - public bind

( definition: | ServiceDefinition @@ -89,23 +78,20 @@ export class LambdaEndpointImpl implements LambdaEndpoint { return this; } - public enableCompression(): LambdaEndpoint { - this.compression = true; - return this; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any handler(): (event: any, ctx: any) => Promise { + const compressionEnabled = isCompressionSupported(); + const genericHandler = new GenericHandler( this.builder.build(), "REQUEST_RESPONSE", - this.compression + compressionEnabled ? { lambdaCompression: "zstd", } : {} ); - const lambdaHandler = new LambdaHandler(genericHandler, this.compression); + const lambdaHandler = new LambdaHandler(genericHandler, compressionEnabled); return lambdaHandler.handleRequest.bind(lambdaHandler); } } diff --git a/packages/restate-sdk/src/lambda.ts b/packages/restate-sdk/src/lambda.ts index 97be99a3..388db976 100644 --- a/packages/restate-sdk/src/lambda.ts +++ b/packages/restate-sdk/src/lambda.ts @@ -22,18 +22,7 @@ import { withOptions } from "./endpoint/withOptions.js"; * Create a new {@link LambdaEndpoint}. */ export function endpoint(): LambdaEndpoint { - return new LambdaEndpointImpl(false); -} - -interface LambdaEndpointOptions extends EndpointOptions { - /** - * Enable compression of Lambda requests/responses using zstd. - * - * NOTE: This feature is supported only from Restate 1.5 onward. - * - * @default false - */ - enableCompression?: boolean; + return new LambdaEndpointImpl(); } /** @@ -49,9 +38,9 @@ interface LambdaEndpointOptions extends EndpointOptions { * * export const handler = createEndpointHandler({ services: [myService] }) */ -export function createEndpointHandler(options: LambdaEndpointOptions) { +export function createEndpointHandler(options: EndpointOptions) { return withOptions( - new LambdaEndpointImpl(options.enableCompression ?? false), + new LambdaEndpointImpl(), options ).handler(); } From 5d22490606d4af65785a2f0e3fca1498a9549381 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Mon, 25 Aug 2025 08:44:11 +0200 Subject: [PATCH 4/4] Fix node engine supported version --- package-lock.json | 2 +- packages/restate-e2e-services/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 367a93ed..a3e20196 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10273,7 +10273,7 @@ "tsx": "^4.15.7" }, "engines": { - "node": ">= 22.15" + "node": ">= 20.19" } }, "packages/restate-sdk": { diff --git a/packages/restate-e2e-services/package.json b/packages/restate-e2e-services/package.json index 331e08b1..d53b6457 100644 --- a/packages/restate-e2e-services/package.json +++ b/packages/restate-e2e-services/package.json @@ -42,6 +42,6 @@ "tsx": "^4.15.7" }, "engines": { - "node": ">= 22.15" + "node": ">= 20.19" } }