@azure/core-lro
Advanced tools
Comparing version 2.0.1-alpha.20210713.1 to 2.1.0-alpha.20210714.3
# Release History | ||
## 2.0.1 (Unreleased) | ||
## 2.1.0 (Unreleased) | ||
### Features Added | ||
- Provides a long-running operation engine. | ||
### Breaking Changes | ||
@@ -8,0 +10,0 @@ |
@@ -5,2 +5,3 @@ // Copyright (c) Microsoft Corporation. | ||
export * from "./poller"; | ||
export * from "./lroEngine"; | ||
//# sourceMappingURL=index.js.map |
@@ -5,2 +5,4 @@ 'use strict'; | ||
var logger$1 = require('@azure/logger'); | ||
// Copyright (c) Microsoft Corporation. | ||
@@ -400,2 +402,337 @@ // Licensed under the MIT license. | ||
// Copyright (c) Microsoft Corporation. | ||
/** | ||
* The `@azure/logger` configuration for this package. | ||
* @internal | ||
*/ | ||
const logger = logger$1.createClientLogger("core-lro"); | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
/** | ||
* Detects where the continuation token is and returns it. Notice that azure-asyncoperation | ||
* must be checked first before the other location headers because there are scenarios | ||
* where both azure-asyncoperation and location could be present in the same response but | ||
* azure-asyncoperation should be the one to use for polling. | ||
*/ | ||
function getPollingUrl(rawResponse, defaultPath) { | ||
var _a, _b, _c; | ||
return ((_c = (_b = (_a = getAzureAsyncOperation(rawResponse)) !== null && _a !== void 0 ? _a : getLocation(rawResponse)) !== null && _b !== void 0 ? _b : getOperationLocation(rawResponse)) !== null && _c !== void 0 ? _c : defaultPath); | ||
} | ||
function getLocation(rawResponse) { | ||
return rawResponse.headers["location"]; | ||
} | ||
function getOperationLocation(rawResponse) { | ||
return rawResponse.headers["operation-location"]; | ||
} | ||
function getAzureAsyncOperation(rawResponse) { | ||
return rawResponse.headers["azure-asyncoperation"]; | ||
} | ||
function inferLroMode(requestPath, requestMethod, rawResponse) { | ||
if (getAzureAsyncOperation(rawResponse) !== undefined) { | ||
return { | ||
mode: "AzureAsync", | ||
resourceLocation: requestMethod === "PUT" | ||
? requestPath | ||
: requestMethod === "POST" | ||
? getLocation(rawResponse) | ||
: undefined | ||
}; | ||
} | ||
else if (getLocation(rawResponse) !== undefined || | ||
getOperationLocation(rawResponse) !== undefined) { | ||
return { | ||
mode: "Location" | ||
}; | ||
} | ||
else if (["PUT", "PATCH"].includes(requestMethod)) { | ||
return { | ||
mode: "Body" | ||
}; | ||
} | ||
return {}; | ||
} | ||
class SimpleRestError extends Error { | ||
constructor(message, statusCode) { | ||
super(message); | ||
this.name = "RestError"; | ||
this.statusCode = statusCode; | ||
Object.setPrototypeOf(this, SimpleRestError.prototype); | ||
} | ||
} | ||
function isUnexpectedInitialResponse(rawResponse) { | ||
const code = rawResponse.statusCode; | ||
if (![203, 204, 202, 201, 200, 500].includes(code)) { | ||
throw new SimpleRestError(`Received unexpected HTTP status code ${code} in the initial response. This may indicate a server issue.`, code); | ||
} | ||
return false; | ||
} | ||
function isUnexpectedPollingResponse(rawResponse) { | ||
const code = rawResponse.statusCode; | ||
if (![202, 201, 200, 500].includes(code)) { | ||
throw new SimpleRestError(`Received unexpected HTTP status code ${code} while polling. This may indicate a server issue.`, code); | ||
} | ||
return false; | ||
} | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
const successStates = ["succeeded"]; | ||
const failureStates = ["failed", "canceled", "cancelled"]; | ||
// Copyright (c) Microsoft Corporation. | ||
function getResponseStatus(rawResponse) { | ||
var _a, _b; | ||
const { status } = (_a = rawResponse.body) !== null && _a !== void 0 ? _a : {}; | ||
return (_b = status === null || status === void 0 ? void 0 : status.toLowerCase()) !== null && _b !== void 0 ? _b : "succeeded"; | ||
} | ||
function isAzureAsyncPollingDone(rawResponse) { | ||
const state = getResponseStatus(rawResponse); | ||
if (isUnexpectedPollingResponse(rawResponse) || failureStates.includes(state)) { | ||
throw new Error(`The long running operation has failed. The provisioning state: ${state}.`); | ||
} | ||
return successStates.includes(state); | ||
} | ||
/** | ||
* Sends a request to the URI of the provisioned resource if needed. | ||
*/ | ||
async function sendFinalRequest(lro, resourceLocation, lroResourceLocationConfig) { | ||
switch (lroResourceLocationConfig) { | ||
case "original-uri": | ||
return lro.sendPollRequest(lro.requestPath); | ||
case "azure-async-operation": | ||
return undefined; | ||
case "location": | ||
default: | ||
return lro.sendPollRequest(resourceLocation !== null && resourceLocation !== void 0 ? resourceLocation : lro.requestPath); | ||
} | ||
} | ||
function processAzureAsyncOperationResult(lro, resourceLocation, lroResourceLocationConfig) { | ||
return (response) => { | ||
if (isAzureAsyncPollingDone(response.rawResponse)) { | ||
if (resourceLocation === undefined) { | ||
return Object.assign(Object.assign({}, response), { done: true }); | ||
} | ||
else { | ||
return Object.assign(Object.assign({}, response), { done: false, next: async () => { | ||
const finalResponse = await sendFinalRequest(lro, resourceLocation, lroResourceLocationConfig); | ||
return Object.assign(Object.assign({}, (finalResponse !== null && finalResponse !== void 0 ? finalResponse : response)), { done: true }); | ||
} }); | ||
} | ||
} | ||
return Object.assign(Object.assign({}, response), { done: false }); | ||
}; | ||
} | ||
// Copyright (c) Microsoft Corporation. | ||
function getProvisioningState(rawResponse) { | ||
var _a, _b, _c; | ||
const { properties, provisioningState } = (_a = rawResponse.body) !== null && _a !== void 0 ? _a : {}; | ||
const state = (_b = properties === null || properties === void 0 ? void 0 : properties.provisioningState) !== null && _b !== void 0 ? _b : provisioningState; | ||
return (_c = state === null || state === void 0 ? void 0 : state.toLowerCase()) !== null && _c !== void 0 ? _c : "succeeded"; | ||
} | ||
function isBodyPollingDone(rawResponse) { | ||
const state = getProvisioningState(rawResponse); | ||
if (isUnexpectedPollingResponse(rawResponse) || failureStates.includes(state)) { | ||
throw new Error(`The long running operation has failed. The provisioning state: ${state}.`); | ||
} | ||
return successStates.includes(state); | ||
} | ||
/** | ||
* Creates a polling strategy based on BodyPolling which uses the provisioning state | ||
* from the result to determine the current operation state | ||
*/ | ||
function processBodyPollingOperationResult(response) { | ||
return Object.assign(Object.assign({}, response), { done: isBodyPollingDone(response.rawResponse) }); | ||
} | ||
// Copyright (c) Microsoft Corporation. | ||
function isLocationPollingDone(rawResponse) { | ||
return !isUnexpectedPollingResponse(rawResponse) && rawResponse.statusCode !== 202; | ||
} | ||
function processLocationPollingOperationResult(response) { | ||
return Object.assign(Object.assign({}, response), { done: isLocationPollingDone(response.rawResponse) }); | ||
} | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
function processPassthroughOperationResult(response) { | ||
return Object.assign(Object.assign({}, response), { done: true }); | ||
} | ||
// Copyright (c) Microsoft Corporation. | ||
/** | ||
* creates a stepping function that maps an LRO state to another. | ||
*/ | ||
function createGetLroStatusFromResponse(lroPrimitives, config, lroResourceLocationConfig) { | ||
switch (config.mode) { | ||
case "AzureAsync": { | ||
return processAzureAsyncOperationResult(lroPrimitives, config.resourceLocation, lroResourceLocationConfig); | ||
} | ||
case "Location": { | ||
return processLocationPollingOperationResult; | ||
} | ||
case "Body": { | ||
return processBodyPollingOperationResult; | ||
} | ||
default: { | ||
return processPassthroughOperationResult; | ||
} | ||
} | ||
} | ||
/** | ||
* Creates a polling operation. | ||
*/ | ||
function createPoll(lroPrimitives) { | ||
return async (path, pollerConfig, getLroStatusFromResponse) => { | ||
const response = await lroPrimitives.sendPollRequest(path); | ||
const retryAfter = response.rawResponse.headers["retry-after"]; | ||
if (retryAfter !== undefined) { | ||
const retryAfterInMs = parseInt(retryAfter); | ||
pollerConfig.intervalInMs = isNaN(retryAfterInMs) | ||
? calculatePollingIntervalFromDate(new Date(retryAfter), pollerConfig.intervalInMs) | ||
: retryAfterInMs; | ||
} | ||
return getLroStatusFromResponse(response); | ||
}; | ||
} | ||
function calculatePollingIntervalFromDate(retryAfterDate, defaultIntervalInMs) { | ||
const timeNow = Math.floor(new Date().getTime()); | ||
const retryAfterTime = retryAfterDate.getTime(); | ||
if (timeNow < retryAfterTime) { | ||
return retryAfterTime - timeNow; | ||
} | ||
return defaultIntervalInMs; | ||
} | ||
/** | ||
* Creates a callback to be used to initialize the polling operation state. | ||
* @param state - of the polling operation | ||
* @param operationSpec - of the LRO | ||
* @param callback - callback to be called when the operation is done | ||
* @returns callback that initializes the state of the polling operation | ||
*/ | ||
function createInitializeState(state, requestPath, requestMethod) { | ||
return (response) => { | ||
if (isUnexpectedInitialResponse(response.rawResponse)) | ||
; | ||
state.initialRawResponse = response.rawResponse; | ||
state.isStarted = true; | ||
state.pollingURL = getPollingUrl(state.initialRawResponse, requestPath); | ||
state.config = inferLroMode(requestPath, requestMethod, state.initialRawResponse); | ||
/** short circuit polling if body polling is done in the initial request */ | ||
if (state.config.mode === undefined || | ||
(state.config.mode === "Body" && isBodyPollingDone(state.initialRawResponse))) { | ||
state.result = response.flatResponse; | ||
state.isCompleted = true; | ||
} | ||
logger.verbose(`LRO: initial state: ${JSON.stringify(state)}`); | ||
return Boolean(state.isCompleted); | ||
}; | ||
} | ||
// Copyright (c) Microsoft Corporation. | ||
class GenericPollOperation { | ||
constructor(state, lro, lroResourceLocationConfig) { | ||
this.state = state; | ||
this.lro = lro; | ||
this.lroResourceLocationConfig = lroResourceLocationConfig; | ||
} | ||
setPollerConfig(pollerConfig) { | ||
this.pollerConfig = pollerConfig; | ||
} | ||
/** | ||
* General update function for LROPoller, the general process is as follows | ||
* 1. Check initial operation result to determine the strategy to use | ||
* - Strategies: Location, Azure-AsyncOperation, Original Uri | ||
* 2. Check if the operation result has a terminal state | ||
* - Terminal state will be determined by each strategy | ||
* 2.1 If it is terminal state Check if a final GET request is required, if so | ||
* send final GET request and return result from operation. If no final GET | ||
* is required, just return the result from operation. | ||
* - Determining what to call for final request is responsibility of each strategy | ||
* 2.2 If it is not terminal state, call the polling operation and go to step 1 | ||
* - Determining what to call for polling is responsibility of each strategy | ||
* - Strategies will always use the latest URI for polling if provided otherwise | ||
* the last known one | ||
*/ | ||
async update(options) { | ||
var _a, _b; | ||
const state = this.state; | ||
if (!state.isStarted) { | ||
const initializeState = createInitializeState(state, this.lro.requestPath, this.lro.requestMethod); | ||
const response = await this.lro.sendInitialRequest(); | ||
initializeState(response); | ||
} | ||
if (!state.isCompleted) { | ||
if (!this.poll || !this.getLroStatusFromResponse) { | ||
if (!state.config) { | ||
throw new Error("Bad state: LRO mode is undefined. Please check if the serialized state is well-formed."); | ||
} | ||
this.getLroStatusFromResponse = createGetLroStatusFromResponse(this.lro, state.config, this.lroResourceLocationConfig); | ||
this.poll = createPoll(this.lro); | ||
} | ||
if (!state.pollingURL) { | ||
throw new Error("Bad state: polling URL is undefined. Please check if the serialized state is well-formed."); | ||
} | ||
const currentState = await this.poll(state.pollingURL, this.pollerConfig, this.getLroStatusFromResponse); | ||
logger.verbose(`LRO: polling response: ${JSON.stringify(currentState.rawResponse)}`); | ||
if (currentState.done) { | ||
state.result = currentState.flatResponse; | ||
state.isCompleted = true; | ||
} | ||
else { | ||
this.poll = (_a = currentState.next) !== null && _a !== void 0 ? _a : this.poll; | ||
state.pollingURL = getPollingUrl(currentState.rawResponse, state.pollingURL); | ||
} | ||
} | ||
logger.verbose(`LRO: current state: ${JSON.stringify(state)}`); | ||
(_b = options === null || options === void 0 ? void 0 : options.fireProgress) === null || _b === void 0 ? void 0 : _b.call(options, state); | ||
return this; | ||
} | ||
async cancel() { | ||
this.state.isCancelled = true; | ||
return this; | ||
} | ||
/** | ||
* Serializes the Poller operation. | ||
*/ | ||
toString() { | ||
return JSON.stringify({ | ||
state: this.state | ||
}); | ||
} | ||
} | ||
// Copyright (c) Microsoft Corporation. | ||
function deserializeState(serializedState) { | ||
try { | ||
return JSON.parse(serializedState).state; | ||
} | ||
catch (e) { | ||
throw new Error(`LroEngine: Unable to deserialize state: ${serializedState}`); | ||
} | ||
} | ||
/** | ||
* The LRO Engine, a class that performs polling. | ||
*/ | ||
class LroEngine extends Poller { | ||
constructor(lro, options) { | ||
const { intervalInMs = 2000, resumeFrom } = options || {}; | ||
const state = resumeFrom | ||
? deserializeState(resumeFrom) | ||
: {}; | ||
const operation = new GenericPollOperation(state, lro, options === null || options === void 0 ? void 0 : options.lroResourceLocationConfig); | ||
super(operation); | ||
this.config = { intervalInMs: intervalInMs }; | ||
operation.setPollerConfig(this.config); | ||
} | ||
/** | ||
* The method used by the poller to wait before attempting to update its operation. | ||
*/ | ||
delay() { | ||
return new Promise((resolve) => setTimeout(() => resolve(), this.config.intervalInMs)); | ||
} | ||
} | ||
exports.LroEngine = LroEngine; | ||
exports.Poller = Poller; | ||
@@ -402,0 +739,0 @@ exports.PollerCancelledError = PollerCancelledError; |
@@ -5,4 +5,4 @@ { | ||
"sdk-type": "client", | ||
"version": "2.0.1-alpha.20210713.1", | ||
"description": "LRO Polling strategy for the Azure SDK in TypeScript", | ||
"version": "2.1.0-alpha.20210714.3", | ||
"description": "Isomorphic client library for supporting long-running operations in node.js and browser.", | ||
"tags": [ | ||
@@ -95,4 +95,3 @@ "isomorphic", | ||
"@azure/abort-controller": "^1.0.0", | ||
"@azure/core-tracing": "1.0.0-preview.12", | ||
"events": "^3.0.0", | ||
"@azure/logger": "^1.0.0", | ||
"tslib": "^2.2.0" | ||
@@ -102,9 +101,6 @@ }, | ||
"@azure/core-http": "^2.0.0", | ||
"@azure/core-rest-pipeline": "^1.1.0", | ||
"@azure/eslint-plugin-azure-sdk": "^3.0.0-alpha", | ||
"@azure/dev-tool": "^1.0.0-alpha", | ||
"@microsoft/api-extractor": "7.7.11", | ||
"@rollup/plugin-commonjs": "11.0.2", | ||
"@rollup/plugin-multi-entry": "^3.0.0", | ||
"@rollup/plugin-node-resolve": "^8.0.0", | ||
"@rollup/plugin-replace": "^2.2.0", | ||
"@types/chai": "^4.1.6", | ||
@@ -134,6 +130,2 @@ "@types/mocha": "^7.0.2", | ||
"rollup": "^1.16.3", | ||
"rollup-plugin-shim": "^1.0.0", | ||
"rollup-plugin-sourcemaps": "^0.4.2", | ||
"rollup-plugin-terser": "^5.1.1", | ||
"rollup-plugin-visualizer": "^4.0.4", | ||
"ts-node": "^9.0.0", | ||
@@ -140,0 +132,0 @@ "typescript": "~4.2.0", |
@@ -12,2 +12,69 @@ import { AbortSignalLike } from '@azure/abort-controller'; | ||
/** | ||
* Description of a long running operation. | ||
*/ | ||
export declare interface LongRunningOperation<T> { | ||
/** | ||
* The request path. | ||
*/ | ||
requestPath: string; | ||
/** | ||
* The HTTP request method. | ||
*/ | ||
requestMethod: string; | ||
/** | ||
* A function that can be used to send initial request to the service. | ||
*/ | ||
sendInitialRequest: () => Promise<LroResponse<T>>; | ||
/** | ||
* A function that can be used to poll for the current status of a long running operation. | ||
*/ | ||
sendPollRequest: (path: string) => Promise<LroResponse<T>>; | ||
} | ||
/** | ||
* The LRO Engine, a class that performs polling. | ||
*/ | ||
export declare class LroEngine<TResult, TState extends PollOperationState<TResult>> extends Poller<TState, TResult> { | ||
private config; | ||
constructor(lro: LongRunningOperation<TResult>, options?: LroEngineOptions); | ||
/** | ||
* The method used by the poller to wait before attempting to update its operation. | ||
*/ | ||
delay(): Promise<void>; | ||
} | ||
/** | ||
* Options for the LRO poller. | ||
*/ | ||
export declare interface LroEngineOptions { | ||
/** | ||
* Defines how much time the poller is going to wait before making a new request to the service. | ||
*/ | ||
intervalInMs?: number; | ||
/** | ||
* A serialized poller which can be used to resume an existing paused Long-Running-Operation. | ||
*/ | ||
resumeFrom?: string; | ||
/** | ||
* The potential location of the result of the LRO if specified by the LRO extension in the swagger. | ||
*/ | ||
lroResourceLocationConfig?: LroResourceLocationConfig; | ||
} | ||
/** | ||
* The potential location of the result of the LRO if specified by the LRO extension in the swagger. | ||
*/ | ||
export declare type LroResourceLocationConfig = "azure-async-operation" | "location" | "original-uri"; | ||
/** | ||
* The type of the response of a LRO. | ||
*/ | ||
export declare interface LroResponse<T> { | ||
/** The flattened response */ | ||
flatResponse: T; | ||
/** The raw response */ | ||
rawResponse: RawResponse; | ||
} | ||
/** | ||
* A class that represents the definition of a program that polls through consecutive requests | ||
@@ -449,2 +516,16 @@ * until it reaches a state of completion. | ||
/** | ||
* Simple type of the raw response. | ||
*/ | ||
export declare interface RawResponse { | ||
/** The HTTP status code */ | ||
statusCode: number; | ||
/** A HttpHeaders collection in the response represented as a simple JSON object where all header names have been normalized to be lower-case. */ | ||
headers: { | ||
[headerName: string]: string; | ||
}; | ||
/** The parsed response body */ | ||
body?: unknown; | ||
} | ||
export { } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
207423
3
33
35
1995
+ Added@azure/logger@^1.0.0
+ Added@azure/logger@1.1.4(transitive)
- Removed@azure/core-tracing@1.0.0-preview.12
- Removedevents@^3.0.0
- Removed@azure/core-tracing@1.0.0-preview.12(transitive)
- Removed@opentelemetry/api@1.9.0(transitive)
- Removedevents@3.3.0(transitive)