diff --git a/lib/restate-constructs/register-service-handler/index.mts b/lib/restate-constructs/register-service-handler/index.mts index b912d81..999ece5 100644 --- a/lib/restate-constructs/register-service-handler/index.mts +++ b/lib/restate-constructs/register-service-handler/index.mts @@ -62,6 +62,33 @@ export interface RegistrationProperties { /** Maximum number of drained deployments to prune per run. */ maxPrunedPerRun?: number; + + /** + * Whether to force deployment registration, overwriting any existing deployment at the same endpoint and allowing + * breaking changes such as removing handlers. + * + * When `force` is true, both breaking changes (e.g., removing handlers) and overwriting existing deployments are + * allowed. This is the most permissive option but may cause issues with in-flight invocations. + * + * Consider using `breaking` instead if you only need to allow schema changes without overwriting deployments. + * + * @see breaking for a safer alternative that allows breaking changes without overwriting + */ + force?: "true" | "false"; + + /** + * Whether to allow breaking schema changes (e.g., removing handlers, changing service types) without overwriting + * the existing deployment. This is safer than `force` because it won't affect in-flight invocations that are + * pinned to the existing deployment. + * + * Use this when evolving services that may have breaking API changes but you want to preserve existing deployment + * versions for in-progress work. + * + * Note: If `force` is set to true, it takes precedence and both breaking changes and overwrites are allowed. + * + * @see force for allowing both breaking changes and overwrites + */ + breaking?: "true" | "false"; } type RegisterDeploymentResponse = { @@ -223,6 +250,8 @@ export const handler = async function (event: CloudFormationCustomResourceEvent) const registrationRequest = JSON.stringify({ arn: props.serviceLambdaArn, assume_role_arn: props.invokeRoleArn, + force: props.force === "true", + breaking: props.breaking === "true", }); let failureReason; diff --git a/lib/restate-constructs/service-deployer.ts b/lib/restate-constructs/service-deployer.ts index 3d0d29a..6eac279 100644 --- a/lib/restate-constructs/service-deployer.ts +++ b/lib/restate-constructs/service-deployer.ts @@ -105,6 +105,49 @@ export interface ServiceRegistrationProps { * Default: 10 */ maxPrunedPerRun?: number; + + /** + * Force deployment registration, overwriting any existing deployment at the same endpoint. This allows + * breaking changes such as removing service handlers. + * + * When enabled, both breaking schema changes and deployment overwrites are allowed. This is the most + * permissive option but may cause issues with in-flight invocations that are pinned to the existing + * deployment. + * + * | Setting | Breaking changes | Overwrites | + * |-----------------------|------------------|------------| + * | `force: true` | Allowed | Allowed | + * | `breaking: true` | Allowed | Forbidden | + * | Neither | Forbidden | Forbidden | + * + * Note: If both `force` and `breaking` are set, `force` takes precedence. + * + * @see breaking for a safer alternative that allows breaking changes without overwriting + * @default false + */ + force?: boolean; + + /** + * Allow breaking schema changes (e.g., removing handlers, changing service types) without overwriting + * the existing deployment. This is safer than `force` because existing deployments are preserved, + * allowing in-flight invocations to complete on the previous version. + * + * Use this when evolving services that may have breaking API changes but you want to preserve existing + * deployment versions for in-progress work. + * + * | Setting | Breaking changes | Overwrites | + * |-----------------------|------------------|------------| + * | `force: true` | Allowed | Allowed | + * | `breaking: true` | Allowed | Forbidden | + * | Neither | Forbidden | Forbidden | + * + * Note: If both `force` and `breaking` are set, `force` takes precedence. To use the safer `breaking` + * behavior, set `force: false` and `breaking: true`. + * + * @see force for allowing both breaking changes and overwrites + * @default false + */ + breaking?: boolean; } /** @@ -256,6 +299,8 @@ export class ServiceDeployer extends Construct { pruneDrainedDeployments: (options?.pruneDrainedDeployments ?? false).toString() as "true" | "false", revisionHistoryLimit: options?.revisionHistoryLimit ?? 0, maxPrunedPerRun: options?.maxPrunedPerRun ?? 10, + force: (options?.force ?? false).toString() as "true" | "false", + breaking: (options?.breaking ?? false).toString() as "true" | "false", } satisfies RegistrationProperties, }); diff --git a/package-lock.json b/package-lock.json index 77addc8..29b696d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,18 +76,18 @@ }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.1", + "extraneous": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": "*" } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.7.2", + "extraneous": true, "inBundle": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, diff --git a/package.json b/package.json index 3a02ea5..db4c097 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,9 @@ "zx": "^8.8.5" }, "peerDependencies": { - "aws-cdk": "^2.236.0", - "aws-cdk-lib": "^2.236.0", - "constructs": "^10.4.5", + "aws-cdk": "^2.215.0", + "aws-cdk-lib": "^2.215.0", + "constructs": "^10.3.0", "node-fetch": "^3.3.2" }, "directories": { diff --git a/test/__snapshots__/restate-constructs.test.ts.snap b/test/__snapshots__/restate-constructs.test.ts.snap index 6aea441..0e0efd0 100644 --- a/test/__snapshots__/restate-constructs.test.ts.snap +++ b/test/__snapshots__/restate-constructs.test.ts.snap @@ -1322,6 +1322,8 @@ exports[`Restate constructs Deploy a Lambda service handler to Restate Cloud env pruneDrainedDeployments: 'false' revisionHistoryLimit: 0 maxPrunedPerRun: 10 + force: 'false' + breaking: 'false' DependsOn: - ServiceDeployerInvocationPolicyD09B639D UpdateReplacePolicy: Delete @@ -1518,6 +1520,8 @@ exports[`Restate constructs Deploy a Lambda service handler to existing Restate pruneDrainedDeployments: 'false' revisionHistoryLimit: 0 maxPrunedPerRun: 10 + force: 'false' + breaking: 'false' DependsOn: - ServiceDeployerInvocationPolicyD09B639D UpdateReplacePolicy: Delete @@ -1728,6 +1732,8 @@ exports[`Restate constructs Restate Cloud Environment construct with role refere pruneDrainedDeployments: 'false' revisionHistoryLimit: 0 maxPrunedPerRun: 10 + force: 'false' + breaking: 'false' DependsOn: - ServiceDeployerInvocationPolicyD09B639D UpdateReplacePolicy: Delete @@ -1845,7 +1851,7 @@ exports[`Restate constructs Service Deployer overrides 1`] = ` - arm64 Code: S3Bucket: cdk-hnb659fds-assets-account-id-region - S3Key: 3e8c1652eeb2e525a478419d4009a3ed4e021bf98fc70eac7f4294d66f5910bc.zip + S3Key: 8ed2b7e40aa8b43d18da885b2d5ec8673277d0810b888d6becb080a6f280a64f.zip Description: Restate custom registration handler Handler: entrypoint.handler MemorySize: 128 @@ -1944,6 +1950,8 @@ exports[`Restate constructs Service Deployer with removal policy and pruning opt pruneDrainedDeployments: 'true' revisionHistoryLimit: 5 maxPrunedPerRun: 10 + force: 'false' + breaking: 'false' DependsOn: - ServiceDeployerInvocationPolicyD09B639D UpdateReplacePolicy: Delete