diff --git a/packages/util-waiter/.gitignore b/packages/util-waiter/.gitignore new file mode 100644 index 000000000000..3d1714c9806e --- /dev/null +++ b/packages/util-waiter/.gitignore @@ -0,0 +1,8 @@ +/node_modules/ +/build/ +/coverage/ +/docs/ +*.tsbuildinfo +*.tgz +*.log +package-lock.json diff --git a/packages/util-waiter/.npmignore b/packages/util-waiter/.npmignore new file mode 100644 index 000000000000..8b11c24b117f --- /dev/null +++ b/packages/util-waiter/.npmignore @@ -0,0 +1,17 @@ +/coverage/ +tsconfig.test.json +*.tsbuildinfo +jest.config.js + +*.spec.js +*.spec.ts +*.spec.d.ts +*.spec.js.map + +*.mock.js +*.mock.d.ts +*.mock.js.map + +*.fixture.js +*.fixture.d.ts +*.fixture.js.map diff --git a/packages/util-waiter/CHANGELOG.md b/packages/util-waiter/CHANGELOG.md new file mode 100644 index 000000000000..e4d87c4d45c4 --- /dev/null +++ b/packages/util-waiter/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/packages/util-waiter/LICENSE b/packages/util-waiter/LICENSE new file mode 100644 index 000000000000..7b6491ba7876 --- /dev/null +++ b/packages/util-waiter/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/util-waiter/README.md b/packages/util-waiter/README.md new file mode 100644 index 000000000000..933e5fe1fb08 --- /dev/null +++ b/packages/util-waiter/README.md @@ -0,0 +1,10 @@ +# @aws-sdk/util-waiter + +[![NPM version](https://img.shields.io/npm/v/@aws-sdk/util-waiter/rc.svg)](https://www.npmjs.com/package/@aws-sdk/util-waiter) +[![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/util-waiter.svg)](https://www.npmjs.com/package/@aws-sdk/util-waiter) + +> An internal package + +## Usage + +You probably shouldn't, at least directly. diff --git a/packages/util-waiter/jest.config.js b/packages/util-waiter/jest.config.js new file mode 100644 index 000000000000..a8d1c2e49912 --- /dev/null +++ b/packages/util-waiter/jest.config.js @@ -0,0 +1,5 @@ +const base = require("../../jest.config.base.js"); + +module.exports = { + ...base, +}; diff --git a/packages/util-waiter/package.json b/packages/util-waiter/package.json new file mode 100644 index 000000000000..494fe7a0fdf6 --- /dev/null +++ b/packages/util-waiter/package.json @@ -0,0 +1,39 @@ +{ + "name": "@aws-sdk/util-waiter", + "version": "1.0.0-rc.8", + "description": "Shared utilities for client waiters for the AWS SDK", + "dependencies": { + "tslib": "^1.8.0", + "@aws-sdk/abort-controller": "1.0.0-rc.8" + }, + "devDependencies": { + "@types/jest": "^26.0.4", + "jest": "^26.1.0", + "typescript": "~4.1.2" + }, + "scripts": { + "prepublishOnly": "yarn build:cjs && yarn build:es", + "pretest": "yarn build:cjs", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:es": "tsc -p tsconfig.es.json", + "build": "yarn build:es && yarn build:cjs", + "test": "jest" + }, + "author": { + "name": "AWS SDK for JavaScript Team", + "url": "https://aws.amazon.com/javascript/" + }, + "license": "Apache-2.0", + "main": "./dist/cjs/index.js", + "module": "./dist/es/index.js", + "types": "./dist/cjs/index.d.ts", + "engines": { + "node": ">= 10.0.0" + }, + "homepage": "https://github.com/aws/aws-sdk-js-v3/tree/master/packages/util-waiter", + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-js-v3.git", + "directory": "packages/util-waiter" + } +} diff --git a/packages/util-waiter/src/index.spec.ts b/packages/util-waiter/src/index.spec.ts new file mode 100644 index 000000000000..5bb3118e5e6e --- /dev/null +++ b/packages/util-waiter/src/index.spec.ts @@ -0,0 +1,11 @@ +import * as exported from "./index"; + +describe("Waiter util module exports", () => { + it("should export the proper functions", () => { + expect(exported.sleep).toBeDefined(); + expect(exported.waiterTimeout).toBeDefined(); + expect(exported.abortTimeout).toBeDefined(); + expect(exported.validateWaiterOptions).toBeDefined(); + expect(exported.runPolling).toBeDefined(); + }); +}); diff --git a/packages/util-waiter/src/index.ts b/packages/util-waiter/src/index.ts new file mode 100644 index 000000000000..7a8a399a17e9 --- /dev/null +++ b/packages/util-waiter/src/index.ts @@ -0,0 +1,4 @@ +export * from "./utils/validate"; +export * from "./utils/sleep"; +export * from "./poller"; +export * from "./waiter"; diff --git a/packages/util-waiter/src/poller.spec.ts b/packages/util-waiter/src/poller.spec.ts new file mode 100644 index 000000000000..aa212cafff4e --- /dev/null +++ b/packages/util-waiter/src/poller.spec.ts @@ -0,0 +1,93 @@ +import { runPolling } from "./poller"; +import { sleep } from "./utils/sleep"; +import { WaiterState } from "./waiter"; + +jest.mock("./utils/sleep"); + +describe(runPolling.name, () => { + const config = { + minDelay: 2, + maxDelay: 30, + maxWaitTime: 99999, + }; + const client = "mockClient"; + const input = "mockInput"; + const failureState = { + state: WaiterState.FAILURE, + }; + const successState = { + state: WaiterState.SUCCESS, + }; + const retryState = { + state: WaiterState.RETRY, + }; + + let mockAcceptorChecks; + + beforeEach(() => { + (sleep as jest.Mock).mockResolvedValueOnce(""); + jest.spyOn(global.Math, "random").mockReturnValue(0.5); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.spyOn(global.Math, "random").mockRestore(); + }); + + it("should returns state in case of failure", async () => { + mockAcceptorChecks = jest.fn().mockResolvedValueOnce(failureState); + await expect(runPolling(config, client, input, mockAcceptorChecks)).resolves.toStrictEqual( + failureState + ); + + expect(mockAcceptorChecks).toHaveBeenCalled(); + expect(mockAcceptorChecks).toHaveBeenCalledTimes(1); + expect(mockAcceptorChecks).toHaveBeenCalledWith(client, input); + + expect(sleep).toHaveBeenCalled(); + expect(sleep).toHaveBeenCalledTimes(1); + expect(sleep).toHaveBeenCalledWith(config.minDelay); + }); + + it("returns state in case of success", async () => { + const config = { + minDelay: 2, + maxDelay: 30, + maxWaitTime: 99999, + }; + + mockAcceptorChecks = jest.fn().mockResolvedValueOnce(successState); + await expect(runPolling(config, client, input, mockAcceptorChecks)).resolves.toStrictEqual( + successState + ); + expect(sleep).toHaveBeenCalled(); + expect(sleep).toHaveBeenCalledTimes(1); + expect(sleep).toHaveBeenCalledWith(config.minDelay); + }); + + it("sleeps as per exponentialBackoff in case of retry", async () => { + mockAcceptorChecks = jest + .fn() + .mockResolvedValueOnce(retryState) + .mockResolvedValueOnce(retryState) + .mockResolvedValueOnce(retryState) + .mockResolvedValueOnce(retryState) + .mockResolvedValueOnce(retryState) + .mockResolvedValueOnce(retryState) + .mockResolvedValueOnce(successState); + + await expect(runPolling(config, client, input, mockAcceptorChecks)).resolves.toStrictEqual( + successState + ); + + expect(sleep).toHaveBeenCalled(); + expect(sleep).toHaveBeenCalledTimes(7); + expect(sleep).toHaveBeenNthCalledWith(1, 2); // min delay + expect(sleep).toHaveBeenNthCalledWith(2, 3); // +random() * 2 + expect(sleep).toHaveBeenNthCalledWith(3, 5); // +random() * 4 + expect(sleep).toHaveBeenNthCalledWith(4, 9); // +random() * 8 + expect(sleep).toHaveBeenNthCalledWith(5, 17); // +random() * 16 + expect(sleep).toHaveBeenNthCalledWith(6, 30); // max delay + expect(sleep).toHaveBeenNthCalledWith(7, 30); // max delay + }); +}); diff --git a/packages/util-waiter/src/poller.ts b/packages/util-waiter/src/poller.ts new file mode 100644 index 000000000000..8ffd189eb185 --- /dev/null +++ b/packages/util-waiter/src/poller.ts @@ -0,0 +1,36 @@ +import { sleep } from "./utils/sleep"; +import { WaiterOptions, WaiterResult, WaiterState } from "./waiter"; + +/** + * Reference: https://github.com/awslabs/smithy/pull/656 + * The theoretical limit to the attempt is max delay cannot be > Number.MAX_VALUE, but it's unlikely because of + * `maxWaitTime` + */ +const exponentialBackoffWithJitter = (floor: number, ciel: number, attempt: number) => + Math.floor(Math.min(ciel, randomInRange(floor, floor * 2 ** (attempt - 1)))); +const randomInRange = (min: number, max: number) => min + Math.random() * (max - min); + +/** + * Function that runs indefinite polling as part of waiters. + * @param params options passed to the waiter. + * @param client AWS SDK Client + * @param input client input + * @param stateChecker function that checks the acceptor states on each poll. + */ +export const runPolling = async ( + { minDelay, maxDelay }: WaiterOptions, + client: T, + input: S, + acceptorChecks: (client: T, input: S) => Promise +): Promise => { + let currentAttempt = 1; + + while (true) { + await sleep(exponentialBackoffWithJitter(minDelay, maxDelay, currentAttempt)); + const { state } = await acceptorChecks(client, input); + if (state === WaiterState.SUCCESS || state === WaiterState.FAILURE) { + return { state }; + } + currentAttempt += 1; + } +}; diff --git a/packages/util-waiter/src/utils/sleep.spec.ts b/packages/util-waiter/src/utils/sleep.spec.ts new file mode 100644 index 000000000000..b45a24727d80 --- /dev/null +++ b/packages/util-waiter/src/utils/sleep.spec.ts @@ -0,0 +1,59 @@ +import { AbortController } from "@aws-sdk/abort-controller"; + +import { WaiterState } from "../waiter"; +import { abortTimeout, sleep, waiterTimeout } from "./sleep"; + +jest.useFakeTimers(); + +describe("Sleep Module", () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.clearAllTimers(); + }); + + describe(sleep.name, () => { + it("should call setTimeout with the proper number of milliseconds", () => { + sleep(1); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); + }); + }); + + describe(waiterTimeout.name, () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.clearAllTimers(); + }); + + it("should call sleep with the proper number of seconds", () => { + waiterTimeout(3); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 3000); + }); + + it("should call return state retry", async () => { + const result = waiterTimeout(1); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); + jest.advanceTimersByTime(1000); + await expect(result).resolves.toEqual({ state: WaiterState.RETRY }); + }); + }); + + describe(abortTimeout.name, () => { + it("should listen to the abort control", async () => { + const abortControl = new AbortController(); + const mockTimeout = 1000; + const race = Promise.race([ + new Promise((resolve) => setTimeout(resolve, mockTimeout)), + abortTimeout(abortControl.signal), + ]); + abortControl.abort(); + // jest.advanceTimersByTime(mockTimeout); + const result = await race; + + await expect(result).toEqual({ state: WaiterState.ABORTED }); + }); + }); +}); diff --git a/packages/util-waiter/src/utils/sleep.ts b/packages/util-waiter/src/utils/sleep.ts new file mode 100644 index 000000000000..ce10ceb8d421 --- /dev/null +++ b/packages/util-waiter/src/utils/sleep.ts @@ -0,0 +1,18 @@ +import { AbortSignal } from "@aws-sdk/abort-controller"; + +import { WaiterResult, WaiterState } from "../waiter"; + +export const sleep = (seconds: number) => { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); +}; + +export const waiterTimeout = async (seconds: number): Promise => { + await sleep(seconds); + return { state: WaiterState.RETRY }; +}; + +export const abortTimeout = async (abortSignal: AbortSignal): Promise => { + return new Promise((resolve) => { + abortSignal.onabort = () => resolve({ state: WaiterState.ABORTED }); + }); +}; diff --git a/packages/util-waiter/src/utils/validate.spec.ts b/packages/util-waiter/src/utils/validate.spec.ts new file mode 100644 index 000000000000..4b41dc3de4dc --- /dev/null +++ b/packages/util-waiter/src/utils/validate.spec.ts @@ -0,0 +1,62 @@ +import { WaiterOptions } from "../waiter"; +import { validateWaiterOptions } from "./validate"; + +describe(validateWaiterOptions.name, () => { + let waiterOptions: WaiterOptions; + + beforeEach(() => { + waiterOptions = { + maxWaitTime: 120, + minDelay: 20, + maxDelay: 120, + }; + }); + + it("should not throw an error when maxWaitTime is proper", (done) => { + waiterOptions.maxWaitTime = 300; + waiterOptions.minDelay = 200; + try { + validateWaiterOptions(waiterOptions); + expect(1).toBe(1); + done(); + } catch (e) { + expect(e).toBe("SHOULD NOT ERROR HERE"); + } + }); + + it("should throw when maxWaitTime is less than 0", (done) => { + waiterOptions.maxWaitTime = -2; + waiterOptions.minDelay = -1; + try { + validateWaiterOptions(waiterOptions); + } catch (e) { + expect(e).toBe("WaiterOptions.maxWaitTime must be greater than 0"); + done(); + } + }); + + it("should throw when maxWaitTime is less than minDelay", (done) => { + waiterOptions.maxWaitTime = 150; + waiterOptions.minDelay = 200; + try { + validateWaiterOptions(waiterOptions); + } catch (e) { + expect(e).toBe( + "WaiterOptions.maxWaitTime [150] must be greater than WaiterOptions.minDelay [200] for this waiter" + ); + done(); + } + }); + + it("should throw when maxWaitTime is equal tominDelay", () => { + waiterOptions.maxWaitTime = 200; + waiterOptions.minDelay = 200; + try { + validateWaiterOptions(waiterOptions); + } catch (e) { + expect(e).toBe( + "WaiterOptions.maxWaitTime [200] must be greater than WaiterOptions.minDelay [200] for this waiter" + ); + } + }); +}); diff --git a/packages/util-waiter/src/utils/validate.ts b/packages/util-waiter/src/utils/validate.ts new file mode 100644 index 000000000000..7fa8bacf0011 --- /dev/null +++ b/packages/util-waiter/src/utils/validate.ts @@ -0,0 +1,13 @@ +import { WaiterOptions } from "../waiter"; + +/** + * Validates that waiter options are passed correctly + * @param options a waiter configuration object + */ +export const validateWaiterOptions = (options: WaiterOptions): void => { + if (options.maxWaitTime < 1) { + throw `WaiterOptions.maxWaitTime must be greater than 0`; + } else if (options.maxWaitTime <= options.minDelay) { + throw `WaiterOptions.maxWaitTime [${options.maxWaitTime}] must be greater than WaiterOptions.minDelay [${options.minDelay}] for this waiter`; + } +}; diff --git a/packages/util-waiter/src/waiter.ts b/packages/util-waiter/src/waiter.ts new file mode 100644 index 000000000000..0c0a8135c6c9 --- /dev/null +++ b/packages/util-waiter/src/waiter.ts @@ -0,0 +1,41 @@ +import { AbortController } from "@aws-sdk/abort-controller"; + +export interface WaiterConfiguration { + /** + * The amount of time in seconds a user is willing to wait for a waiter to complete. This + * defaults to 300 (5 minutes). + */ + maxWaitTime: number; + + /** + * Abort controller. Used for ending the waiter early. + */ + abortController?: AbortController; +} + +export interface WaiterOptions extends WaiterConfiguration { + /** + * The minimum amount of time to delay between retries in seconds. This value defaults + * to 2 if not specified. If specified, this value MUST be greater than or equal to 1 + * and less than or equal to maxDelay. + */ + minDelay: number; + + /** + * The maximum amount of time to delay between retries in seconds. The maximum amount + * of time in seconds to delay between each retry. This value defaults to 120 if not + * specified (2 minutes). If specified, this value MUST be greater than or equal to 1. + */ + maxDelay: number; +} + +export enum WaiterState { + ABORTED = "ABORTED", + FAILURE = "FAILURE", + SUCCESS = "SUCCESS", + RETRY = "RETRY", +} + +export type WaiterResult = { + state: WaiterState; +}; diff --git a/packages/util-waiter/tsconfig.cjs.json b/packages/util-waiter/tsconfig.cjs.json new file mode 100644 index 000000000000..f31c61813366 --- /dev/null +++ b/packages/util-waiter/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist/cjs", + "baseUrl": "." + }, + "extends": "../../tsconfig.cjs.json", + "include": ["src/"] +} diff --git a/packages/util-waiter/tsconfig.es.json b/packages/util-waiter/tsconfig.es.json new file mode 100644 index 000000000000..fe84cbe7e342 --- /dev/null +++ b/packages/util-waiter/tsconfig.es.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "lib": ["es5", "es2015.collection"], + "rootDir": "./src", + "outDir": "./dist/es", + "baseUrl": "." + }, + "extends": "../../tsconfig.es.json", + "include": ["src/"] +}