@fluidframework/driver-utils 2.91.0 → 2.93.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @fluidframework/driver-utils
2
2
 
3
+ ## 2.93.0
4
+
5
+ Dependency updates only.
6
+
7
+ ## 2.92.0
8
+
9
+ Dependency updates only.
10
+
3
11
  ## 2.91.0
4
12
 
5
13
  Dependency updates only.
package/README.md CHANGED
@@ -57,7 +57,7 @@ When making such a request please include if the configuration already works (an
57
57
 
58
58
  ### Supported Runtimes
59
59
 
60
- - NodeJs ^20.10.0 except that we will drop support for it [when NodeJs 20 loses its upstream support on 2026-04-30](https://github.com/nodejs/release#release-schedule), and will support a newer LTS version of NodeJS (22) at least 1 year before 20 is end-of-life. This same policy applies to NodeJS 22 when it is end of life (2027-04-30).
60
+ - NodeJs ^22.22.2 except that we will drop support for it [when NodeJs 22 loses its upstream support on 2027-04-30](https://github.com/nodejs/release#release-schedule), and will support a newer LTS version of NodeJS at least 1 year before 22 is end-of-life.
61
61
  - Running Fluid in a Node.js environment with the `--no-experimental-fetch` flag is not supported.
62
62
  - Modern browsers supporting the es2022 standard library: in response to asks we can add explicit support for using babel to polyfill to target specific standards or runtimes (meaning we can avoid/remove use of things that don't polyfill robustly, but otherwise target modern standards).
63
63
 
package/dist/legacy.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  /*
7
7
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
8
+ * Generated by "flub generate entrypoints --resolutionConditions require --outFileLegacyBeta legacy --outDir ./dist" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
11
  export {
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/driver-utils";
8
- export declare const pkgVersion = "2.91.0";
8
+ export declare const pkgVersion = "2.93.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/driver-utils";
11
- exports.pkgVersion = "2.91.0";
11
+ exports.pkgVersion = "2.93.0";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,8BAA8B,CAAC;AACzC,QAAA,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-utils\";\nexport const pkgVersion = \"2.91.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,8BAA8B,CAAC;AACzC,QAAA,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-utils\";\nexport const pkgVersion = \"2.93.0\";\n"]}
package/dist/public.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  /*
7
7
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
8
+ * Generated by "flub generate entrypoints --resolutionConditions require --outFileLegacyBeta legacy --outDir ./dist" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
11
  export {
@@ -39,7 +39,7 @@ export interface IProgress {
39
39
  *
40
40
  * @internal
41
41
  */
42
- export declare function runWithRetry<T>(api: (cancel?: AbortSignal) => Promise<T>, fetchCallName: string, logger: ITelemetryLoggerExt, progress: IProgress): Promise<T>;
42
+ export declare function runWithRetry<T>(api: (cancel?: AbortSignal) => Promise<T>, fetchCallName: string, logger: ITelemetryLoggerExt, progress: IProgress, maxRetries?: number): Promise<T>;
43
43
  /**
44
44
  * Calculates time to wait for after an error based on the error and wait time for previous iteration.
45
45
  * In case endpoint(service or socket) is not reachable, then we maybe offline or may have got some
@@ -1 +1 @@
1
- {"version":3,"file":"runWithRetry.d.ts","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,0CAA0C,CAAC;AAKlD;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;;;;OAOG;IACH,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,SAAS,GACjB,OAAO,CAAC,CAAC,CAAC,CAuFZ;AAKD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAU5E"}
1
+ {"version":3,"file":"runWithRetry.d.ts","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAGN,KAAK,mBAAmB,EACxB,MAAM,0CAA0C,CAAC;AAKlD;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;;;;OAOG;IACH,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,SAAS,EACnB,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,CAAC,CAoHZ;AAKD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAU5E"}
@@ -16,7 +16,7 @@ const packageVersion_js_1 = require("./packageVersion.js");
16
16
  *
17
17
  * @internal
18
18
  */
19
- async function runWithRetry(api, fetchCallName, logger, progress) {
19
+ async function runWithRetry(api, fetchCallName, logger, progress, maxRetries) {
20
20
  let result;
21
21
  let success = false;
22
22
  // We double this value in first try in when we calculate time to wait for in "calculateMaxWaitTime" function.
@@ -66,6 +66,22 @@ async function runWithRetry(api, fetchCallName, logger, progress) {
66
66
  }, error);
67
67
  }
68
68
  numRetries++;
69
+ // Check if max retries limit has been reached
70
+ if (maxRetries !== undefined && numRetries > maxRetries) {
71
+ logger.sendTelemetryEvent({
72
+ eventName: `${fetchCallName}_maxRetriesExceeded`,
73
+ retry: numRetries - 1,
74
+ maxRetries,
75
+ duration: (0, client_utils_1.performanceNow)() - startTime,
76
+ fetchCallName,
77
+ }, error);
78
+ // Wrap the original error to preserve its details while marking it non-retriable
79
+ throw (0, internal_3.wrapError)(error, (message) => new network_js_1.NonRetryableError(`runWithRetry failed after max retries: ${message}`, internal_2.DriverErrorTypes.genericError, {
80
+ driverVersion: packageVersion_js_1.pkgVersion,
81
+ fetchCallName,
82
+ maxRetries,
83
+ }));
84
+ }
69
85
  lastError = error;
70
86
  // Wait for the calculated time before retrying.
71
87
  retryAfterMs = calculateMaxWaitTime(retryAfterMs, error);
@@ -1 +1 @@
1
- {"version":3,"file":"runWithRetry.js","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport { delay } from \"@fluidframework/core-utils/internal\";\nimport { DriverErrorTypes } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tisFluidError,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { NonRetryableError, canRetryOnError, getRetryDelayFromError } from \"./network.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\n/**\n * Interface describing an object passed to various network APIs.\n * It allows caller to control cancellation, as well as learn about any delays.\n * @internal\n */\nexport interface IProgress {\n\t/**\n\t * Abort signal used to cancel operation.\n\t *\n\t * @remarks Note that most of the layers do not use this signal yet. We need to change that over time.\n\t * Please consult with API documentation / implementation.\n\t * Note that number of layers may not check this signal while holding this request in a queue,\n\t * so it may take a while it takes effect. This can be improved in the future.\n\t *\n\t * The layers in question are:\n\t *\n\t * - driver (RateLimiter)\n\t *\n\t * - runWithRetry\n\t */\n\tcancel?: AbortSignal;\n\n\t/**\n\t * Called whenever api returns cancellable error and the call is going to be retried.\n\t * Any exception thrown from this call back result in cancellation of operation\n\t * and propagation of thrown exception.\n\t * @param delayInMs - delay before next retry. This value will depend on internal back-off logic,\n\t * as well as information provided by service (like 429 error asking to wait for some time before retry)\n\t * @param error - error object returned from the call.\n\t */\n\tonRetry?(delayInMs: number, error: unknown): void;\n}\n\n/**\n * Runs the provided API call with automatic retry logic.\n *\n * @internal\n */\nexport async function runWithRetry<T>(\n\tapi: (cancel?: AbortSignal) => Promise<T>,\n\tfetchCallName: string,\n\tlogger: ITelemetryLoggerExt,\n\tprogress: IProgress,\n): Promise<T> {\n\tlet result: T | undefined;\n\tlet success = false;\n\t// We double this value in first try in when we calculate time to wait for in \"calculateMaxWaitTime\" function.\n\tlet retryAfterMs = 500; // has to be positive!\n\tlet numRetries = 0;\n\tconst startTime = performanceNow();\n\tlet lastError: unknown;\n\tdo {\n\t\ttry {\n\t\t\tresult = await api(progress.cancel);\n\t\t\tsuccess = true;\n\t\t} catch (error) {\n\t\t\t// If it is not retriable, then just throw the error.\n\t\t\tif (!canRetryOnError(error)) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_cancel`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (progress.cancel?.aborted === true) {\n\t\t\t\tconst abortReason = progress.cancel.reason as string;\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_runWithRetryAborted`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow new NonRetryableError(\n\t\t\t\t\t\"runWithRetry was Aborted\",\n\t\t\t\t\tDriverErrorTypes.genericError,\n\t\t\t\t\t{\n\t\t\t\t\t\tdriverVersion: pkgVersion,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// logging the first failed retry instead of every attempt. We want to avoid filling telemetry\n\t\t\t// when we have tight loop of retrying in offline mode, but we also want to know what caused\n\t\t\t// the failure in the first place\n\t\t\tif (numRetries === 0) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_firstFailed`,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tnumRetries++;\n\t\t\tlastError = error;\n\t\t\t// Wait for the calculated time before retrying.\n\t\t\tretryAfterMs = calculateMaxWaitTime(retryAfterMs, error);\n\t\t\tif (progress.onRetry) {\n\t\t\t\tprogress.onRetry(retryAfterMs, error);\n\t\t\t}\n\t\t\tawait delay(retryAfterMs);\n\t\t}\n\t} while (!success);\n\tif (numRetries > 0) {\n\t\tlogger.sendTelemetryEvent(\n\t\t\t{\n\t\t\t\teventName: `${fetchCallName}_lastError`,\n\t\t\t\tretry: numRetries,\n\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\tfetchCallName,\n\t\t\t},\n\t\t\tlastError,\n\t\t);\n\t}\n\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\treturn result!;\n}\n\nconst MaxReconnectDelayInMsWhenEndpointIsReachable = 60000;\nconst MaxReconnectDelayInMsWhenEndpointIsNotReachable = 8000;\n\n/**\n * Calculates time to wait for after an error based on the error and wait time for previous iteration.\n * In case endpoint(service or socket) is not reachable, then we maybe offline or may have got some\n * transient error not related to endpoint, in that case we want to try at faster pace and hence the\n * max wait is lesser 8s as compared to when endpoint is reachable in which case it is 60s.\n * @param delayMs - wait time for previous iteration\n * @param error - error based on which we decide wait time.\n * @returns Wait time to wait for.\n * @internal\n */\nexport function calculateMaxWaitTime(delayMs: number, error: unknown): number {\n\tconst retryDelayFromError = getRetryDelayFromError(error);\n\tlet newDelayMs = Math.max(retryDelayFromError ?? 0, delayMs * 2);\n\tnewDelayMs = Math.min(\n\t\tnewDelayMs,\n\t\tisFluidError(error) && error.getTelemetryProperties().endpointReached === true\n\t\t\t? MaxReconnectDelayInMsWhenEndpointIsReachable\n\t\t\t: MaxReconnectDelayInMsWhenEndpointIsNotReachable,\n\t);\n\treturn newDelayMs;\n}\n"]}
1
+ {"version":3,"file":"runWithRetry.js","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport { delay } from \"@fluidframework/core-utils/internal\";\nimport { DriverErrorTypes } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tisFluidError,\n\twrapError,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { NonRetryableError, canRetryOnError, getRetryDelayFromError } from \"./network.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\n/**\n * Interface describing an object passed to various network APIs.\n * It allows caller to control cancellation, as well as learn about any delays.\n * @internal\n */\nexport interface IProgress {\n\t/**\n\t * Abort signal used to cancel operation.\n\t *\n\t * @remarks Note that most of the layers do not use this signal yet. We need to change that over time.\n\t * Please consult with API documentation / implementation.\n\t * Note that number of layers may not check this signal while holding this request in a queue,\n\t * so it may take a while it takes effect. This can be improved in the future.\n\t *\n\t * The layers in question are:\n\t *\n\t * - driver (RateLimiter)\n\t *\n\t * - runWithRetry\n\t */\n\tcancel?: AbortSignal;\n\n\t/**\n\t * Called whenever api returns cancellable error and the call is going to be retried.\n\t * Any exception thrown from this call back result in cancellation of operation\n\t * and propagation of thrown exception.\n\t * @param delayInMs - delay before next retry. This value will depend on internal back-off logic,\n\t * as well as information provided by service (like 429 error asking to wait for some time before retry)\n\t * @param error - error object returned from the call.\n\t */\n\tonRetry?(delayInMs: number, error: unknown): void;\n}\n\n/**\n * Runs the provided API call with automatic retry logic.\n *\n * @internal\n */\nexport async function runWithRetry<T>(\n\tapi: (cancel?: AbortSignal) => Promise<T>,\n\tfetchCallName: string,\n\tlogger: ITelemetryLoggerExt,\n\tprogress: IProgress,\n\tmaxRetries?: number,\n): Promise<T> {\n\tlet result: T | undefined;\n\tlet success = false;\n\t// We double this value in first try in when we calculate time to wait for in \"calculateMaxWaitTime\" function.\n\tlet retryAfterMs = 500; // has to be positive!\n\tlet numRetries = 0;\n\tconst startTime = performanceNow();\n\tlet lastError: unknown;\n\tdo {\n\t\ttry {\n\t\t\tresult = await api(progress.cancel);\n\t\t\tsuccess = true;\n\t\t} catch (error) {\n\t\t\t// If it is not retriable, then just throw the error.\n\t\t\tif (!canRetryOnError(error)) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_cancel`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (progress.cancel?.aborted === true) {\n\t\t\t\tconst abortReason = progress.cancel.reason as string;\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_runWithRetryAborted`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow new NonRetryableError(\n\t\t\t\t\t\"runWithRetry was Aborted\",\n\t\t\t\t\tDriverErrorTypes.genericError,\n\t\t\t\t\t{\n\t\t\t\t\t\tdriverVersion: pkgVersion,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// logging the first failed retry instead of every attempt. We want to avoid filling telemetry\n\t\t\t// when we have tight loop of retrying in offline mode, but we also want to know what caused\n\t\t\t// the failure in the first place\n\t\t\tif (numRetries === 0) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_firstFailed`,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tnumRetries++;\n\n\t\t\t// Check if max retries limit has been reached\n\t\t\tif (maxRetries !== undefined && numRetries > maxRetries) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_maxRetriesExceeded`,\n\t\t\t\t\t\tretry: numRetries - 1,\n\t\t\t\t\t\tmaxRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\t// Wrap the original error to preserve its details while marking it non-retriable\n\t\t\t\tthrow wrapError(\n\t\t\t\t\terror,\n\t\t\t\t\t(message) =>\n\t\t\t\t\t\tnew NonRetryableError(\n\t\t\t\t\t\t\t`runWithRetry failed after max retries: ${message}`,\n\t\t\t\t\t\t\tDriverErrorTypes.genericError,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdriverVersion: pkgVersion,\n\t\t\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\t\t\tmaxRetries,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlastError = error;\n\t\t\t// Wait for the calculated time before retrying.\n\t\t\tretryAfterMs = calculateMaxWaitTime(retryAfterMs, error);\n\t\t\tif (progress.onRetry) {\n\t\t\t\tprogress.onRetry(retryAfterMs, error);\n\t\t\t}\n\t\t\tawait delay(retryAfterMs);\n\t\t}\n\t} while (!success);\n\tif (numRetries > 0) {\n\t\tlogger.sendTelemetryEvent(\n\t\t\t{\n\t\t\t\teventName: `${fetchCallName}_lastError`,\n\t\t\t\tretry: numRetries,\n\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\tfetchCallName,\n\t\t\t},\n\t\t\tlastError,\n\t\t);\n\t}\n\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\treturn result!;\n}\n\nconst MaxReconnectDelayInMsWhenEndpointIsReachable = 60000;\nconst MaxReconnectDelayInMsWhenEndpointIsNotReachable = 8000;\n\n/**\n * Calculates time to wait for after an error based on the error and wait time for previous iteration.\n * In case endpoint(service or socket) is not reachable, then we maybe offline or may have got some\n * transient error not related to endpoint, in that case we want to try at faster pace and hence the\n * max wait is lesser 8s as compared to when endpoint is reachable in which case it is 60s.\n * @param delayMs - wait time for previous iteration\n * @param error - error based on which we decide wait time.\n * @returns Wait time to wait for.\n * @internal\n */\nexport function calculateMaxWaitTime(delayMs: number, error: unknown): number {\n\tconst retryDelayFromError = getRetryDelayFromError(error);\n\tlet newDelayMs = Math.max(retryDelayFromError ?? 0, delayMs * 2);\n\tnewDelayMs = Math.min(\n\t\tnewDelayMs,\n\t\tisFluidError(error) && error.getTelemetryProperties().endpointReached === true\n\t\t\t? MaxReconnectDelayInMsWhenEndpointIsReachable\n\t\t\t: MaxReconnectDelayInMsWhenEndpointIsNotReachable,\n\t);\n\treturn newDelayMs;\n}\n"]}
package/eslint.config.mts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { Linter } from "eslint";
7
- import { recommended } from "../../../common/build/eslint-config-fluid/flat.mts";
7
+ import { recommended } from "@fluidframework/eslint-config-fluid/flat.mts";
8
8
 
9
9
  const config: Linter.Config[] = [
10
10
  ...recommended,
package/internal.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  /*
7
7
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
8
+ * Generated by "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./lib --node10TypeCompat" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
11
  export * from "./lib/index.js";
package/legacy.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  /*
7
7
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
8
+ * Generated by "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./lib --node10TypeCompat" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
11
  export * from "./lib/legacy.js";
package/lib/legacy.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  /*
7
7
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
8
+ * Generated by "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./lib --node10TypeCompat" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
11
  export {
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/driver-utils";
8
- export declare const pkgVersion = "2.91.0";
8
+ export declare const pkgVersion = "2.93.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/driver-utils";
8
- export const pkgVersion = "2.91.0";
8
+ export const pkgVersion = "2.93.0";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,8BAA8B,CAAC;AACtD,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-utils\";\nexport const pkgVersion = \"2.91.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,8BAA8B,CAAC;AACtD,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-utils\";\nexport const pkgVersion = \"2.93.0\";\n"]}
package/lib/public.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  /*
7
7
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
8
- * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
8
+ * Generated by "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./lib --node10TypeCompat" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
11
  export {
@@ -39,7 +39,7 @@ export interface IProgress {
39
39
  *
40
40
  * @internal
41
41
  */
42
- export declare function runWithRetry<T>(api: (cancel?: AbortSignal) => Promise<T>, fetchCallName: string, logger: ITelemetryLoggerExt, progress: IProgress): Promise<T>;
42
+ export declare function runWithRetry<T>(api: (cancel?: AbortSignal) => Promise<T>, fetchCallName: string, logger: ITelemetryLoggerExt, progress: IProgress, maxRetries?: number): Promise<T>;
43
43
  /**
44
44
  * Calculates time to wait for after an error based on the error and wait time for previous iteration.
45
45
  * In case endpoint(service or socket) is not reachable, then we maybe offline or may have got some
@@ -1 +1 @@
1
- {"version":3,"file":"runWithRetry.d.ts","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,0CAA0C,CAAC;AAKlD;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;;;;OAOG;IACH,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,SAAS,GACjB,OAAO,CAAC,CAAC,CAAC,CAuFZ;AAKD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAU5E"}
1
+ {"version":3,"file":"runWithRetry.d.ts","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAGN,KAAK,mBAAmB,EACxB,MAAM,0CAA0C,CAAC;AAKlD;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;;;;OAOG;IACH,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,SAAS,EACnB,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,CAAC,CAoHZ;AAKD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAU5E"}
@@ -5,7 +5,7 @@
5
5
  import { performanceNow } from "@fluid-internal/client-utils";
6
6
  import { delay } from "@fluidframework/core-utils/internal";
7
7
  import { DriverErrorTypes } from "@fluidframework/driver-definitions/internal";
8
- import { isFluidError, } from "@fluidframework/telemetry-utils/internal";
8
+ import { isFluidError, wrapError, } from "@fluidframework/telemetry-utils/internal";
9
9
  import { NonRetryableError, canRetryOnError, getRetryDelayFromError } from "./network.js";
10
10
  import { pkgVersion } from "./packageVersion.js";
11
11
  /**
@@ -13,7 +13,7 @@ import { pkgVersion } from "./packageVersion.js";
13
13
  *
14
14
  * @internal
15
15
  */
16
- export async function runWithRetry(api, fetchCallName, logger, progress) {
16
+ export async function runWithRetry(api, fetchCallName, logger, progress, maxRetries) {
17
17
  let result;
18
18
  let success = false;
19
19
  // We double this value in first try in when we calculate time to wait for in "calculateMaxWaitTime" function.
@@ -63,6 +63,22 @@ export async function runWithRetry(api, fetchCallName, logger, progress) {
63
63
  }, error);
64
64
  }
65
65
  numRetries++;
66
+ // Check if max retries limit has been reached
67
+ if (maxRetries !== undefined && numRetries > maxRetries) {
68
+ logger.sendTelemetryEvent({
69
+ eventName: `${fetchCallName}_maxRetriesExceeded`,
70
+ retry: numRetries - 1,
71
+ maxRetries,
72
+ duration: performanceNow() - startTime,
73
+ fetchCallName,
74
+ }, error);
75
+ // Wrap the original error to preserve its details while marking it non-retriable
76
+ throw wrapError(error, (message) => new NonRetryableError(`runWithRetry failed after max retries: ${message}`, DriverErrorTypes.genericError, {
77
+ driverVersion: pkgVersion,
78
+ fetchCallName,
79
+ maxRetries,
80
+ }));
81
+ }
66
82
  lastError = error;
67
83
  // Wait for the calculated time before retrying.
68
84
  retryAfterMs = calculateMaxWaitTime(retryAfterMs, error);
@@ -1 +1 @@
1
- {"version":3,"file":"runWithRetry.js","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport { delay } from \"@fluidframework/core-utils/internal\";\nimport { DriverErrorTypes } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tisFluidError,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { NonRetryableError, canRetryOnError, getRetryDelayFromError } from \"./network.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\n/**\n * Interface describing an object passed to various network APIs.\n * It allows caller to control cancellation, as well as learn about any delays.\n * @internal\n */\nexport interface IProgress {\n\t/**\n\t * Abort signal used to cancel operation.\n\t *\n\t * @remarks Note that most of the layers do not use this signal yet. We need to change that over time.\n\t * Please consult with API documentation / implementation.\n\t * Note that number of layers may not check this signal while holding this request in a queue,\n\t * so it may take a while it takes effect. This can be improved in the future.\n\t *\n\t * The layers in question are:\n\t *\n\t * - driver (RateLimiter)\n\t *\n\t * - runWithRetry\n\t */\n\tcancel?: AbortSignal;\n\n\t/**\n\t * Called whenever api returns cancellable error and the call is going to be retried.\n\t * Any exception thrown from this call back result in cancellation of operation\n\t * and propagation of thrown exception.\n\t * @param delayInMs - delay before next retry. This value will depend on internal back-off logic,\n\t * as well as information provided by service (like 429 error asking to wait for some time before retry)\n\t * @param error - error object returned from the call.\n\t */\n\tonRetry?(delayInMs: number, error: unknown): void;\n}\n\n/**\n * Runs the provided API call with automatic retry logic.\n *\n * @internal\n */\nexport async function runWithRetry<T>(\n\tapi: (cancel?: AbortSignal) => Promise<T>,\n\tfetchCallName: string,\n\tlogger: ITelemetryLoggerExt,\n\tprogress: IProgress,\n): Promise<T> {\n\tlet result: T | undefined;\n\tlet success = false;\n\t// We double this value in first try in when we calculate time to wait for in \"calculateMaxWaitTime\" function.\n\tlet retryAfterMs = 500; // has to be positive!\n\tlet numRetries = 0;\n\tconst startTime = performanceNow();\n\tlet lastError: unknown;\n\tdo {\n\t\ttry {\n\t\t\tresult = await api(progress.cancel);\n\t\t\tsuccess = true;\n\t\t} catch (error) {\n\t\t\t// If it is not retriable, then just throw the error.\n\t\t\tif (!canRetryOnError(error)) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_cancel`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (progress.cancel?.aborted === true) {\n\t\t\t\tconst abortReason = progress.cancel.reason as string;\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_runWithRetryAborted`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow new NonRetryableError(\n\t\t\t\t\t\"runWithRetry was Aborted\",\n\t\t\t\t\tDriverErrorTypes.genericError,\n\t\t\t\t\t{\n\t\t\t\t\t\tdriverVersion: pkgVersion,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// logging the first failed retry instead of every attempt. We want to avoid filling telemetry\n\t\t\t// when we have tight loop of retrying in offline mode, but we also want to know what caused\n\t\t\t// the failure in the first place\n\t\t\tif (numRetries === 0) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_firstFailed`,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tnumRetries++;\n\t\t\tlastError = error;\n\t\t\t// Wait for the calculated time before retrying.\n\t\t\tretryAfterMs = calculateMaxWaitTime(retryAfterMs, error);\n\t\t\tif (progress.onRetry) {\n\t\t\t\tprogress.onRetry(retryAfterMs, error);\n\t\t\t}\n\t\t\tawait delay(retryAfterMs);\n\t\t}\n\t} while (!success);\n\tif (numRetries > 0) {\n\t\tlogger.sendTelemetryEvent(\n\t\t\t{\n\t\t\t\teventName: `${fetchCallName}_lastError`,\n\t\t\t\tretry: numRetries,\n\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\tfetchCallName,\n\t\t\t},\n\t\t\tlastError,\n\t\t);\n\t}\n\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\treturn result!;\n}\n\nconst MaxReconnectDelayInMsWhenEndpointIsReachable = 60000;\nconst MaxReconnectDelayInMsWhenEndpointIsNotReachable = 8000;\n\n/**\n * Calculates time to wait for after an error based on the error and wait time for previous iteration.\n * In case endpoint(service or socket) is not reachable, then we maybe offline or may have got some\n * transient error not related to endpoint, in that case we want to try at faster pace and hence the\n * max wait is lesser 8s as compared to when endpoint is reachable in which case it is 60s.\n * @param delayMs - wait time for previous iteration\n * @param error - error based on which we decide wait time.\n * @returns Wait time to wait for.\n * @internal\n */\nexport function calculateMaxWaitTime(delayMs: number, error: unknown): number {\n\tconst retryDelayFromError = getRetryDelayFromError(error);\n\tlet newDelayMs = Math.max(retryDelayFromError ?? 0, delayMs * 2);\n\tnewDelayMs = Math.min(\n\t\tnewDelayMs,\n\t\tisFluidError(error) && error.getTelemetryProperties().endpointReached === true\n\t\t\t? MaxReconnectDelayInMsWhenEndpointIsReachable\n\t\t\t: MaxReconnectDelayInMsWhenEndpointIsNotReachable,\n\t);\n\treturn newDelayMs;\n}\n"]}
1
+ {"version":3,"file":"runWithRetry.js","sourceRoot":"","sources":["../src/runWithRetry.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport { delay } from \"@fluidframework/core-utils/internal\";\nimport { DriverErrorTypes } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tisFluidError,\n\twrapError,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { NonRetryableError, canRetryOnError, getRetryDelayFromError } from \"./network.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\n/**\n * Interface describing an object passed to various network APIs.\n * It allows caller to control cancellation, as well as learn about any delays.\n * @internal\n */\nexport interface IProgress {\n\t/**\n\t * Abort signal used to cancel operation.\n\t *\n\t * @remarks Note that most of the layers do not use this signal yet. We need to change that over time.\n\t * Please consult with API documentation / implementation.\n\t * Note that number of layers may not check this signal while holding this request in a queue,\n\t * so it may take a while it takes effect. This can be improved in the future.\n\t *\n\t * The layers in question are:\n\t *\n\t * - driver (RateLimiter)\n\t *\n\t * - runWithRetry\n\t */\n\tcancel?: AbortSignal;\n\n\t/**\n\t * Called whenever api returns cancellable error and the call is going to be retried.\n\t * Any exception thrown from this call back result in cancellation of operation\n\t * and propagation of thrown exception.\n\t * @param delayInMs - delay before next retry. This value will depend on internal back-off logic,\n\t * as well as information provided by service (like 429 error asking to wait for some time before retry)\n\t * @param error - error object returned from the call.\n\t */\n\tonRetry?(delayInMs: number, error: unknown): void;\n}\n\n/**\n * Runs the provided API call with automatic retry logic.\n *\n * @internal\n */\nexport async function runWithRetry<T>(\n\tapi: (cancel?: AbortSignal) => Promise<T>,\n\tfetchCallName: string,\n\tlogger: ITelemetryLoggerExt,\n\tprogress: IProgress,\n\tmaxRetries?: number,\n): Promise<T> {\n\tlet result: T | undefined;\n\tlet success = false;\n\t// We double this value in first try in when we calculate time to wait for in \"calculateMaxWaitTime\" function.\n\tlet retryAfterMs = 500; // has to be positive!\n\tlet numRetries = 0;\n\tconst startTime = performanceNow();\n\tlet lastError: unknown;\n\tdo {\n\t\ttry {\n\t\t\tresult = await api(progress.cancel);\n\t\t\tsuccess = true;\n\t\t} catch (error) {\n\t\t\t// If it is not retriable, then just throw the error.\n\t\t\tif (!canRetryOnError(error)) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_cancel`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (progress.cancel?.aborted === true) {\n\t\t\t\tconst abortReason = progress.cancel.reason as string;\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_runWithRetryAborted`,\n\t\t\t\t\t\tretry: numRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow new NonRetryableError(\n\t\t\t\t\t\"runWithRetry was Aborted\",\n\t\t\t\t\tDriverErrorTypes.genericError,\n\t\t\t\t\t{\n\t\t\t\t\t\tdriverVersion: pkgVersion,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\treason: abortReason,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// logging the first failed retry instead of every attempt. We want to avoid filling telemetry\n\t\t\t// when we have tight loop of retrying in offline mode, but we also want to know what caused\n\t\t\t// the failure in the first place\n\t\t\tif (numRetries === 0) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_firstFailed`,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tnumRetries++;\n\n\t\t\t// Check if max retries limit has been reached\n\t\t\tif (maxRetries !== undefined && numRetries > maxRetries) {\n\t\t\t\tlogger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: `${fetchCallName}_maxRetriesExceeded`,\n\t\t\t\t\t\tretry: numRetries - 1,\n\t\t\t\t\t\tmaxRetries,\n\t\t\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\t// Wrap the original error to preserve its details while marking it non-retriable\n\t\t\t\tthrow wrapError(\n\t\t\t\t\terror,\n\t\t\t\t\t(message) =>\n\t\t\t\t\t\tnew NonRetryableError(\n\t\t\t\t\t\t\t`runWithRetry failed after max retries: ${message}`,\n\t\t\t\t\t\t\tDriverErrorTypes.genericError,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdriverVersion: pkgVersion,\n\t\t\t\t\t\t\t\tfetchCallName,\n\t\t\t\t\t\t\t\tmaxRetries,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlastError = error;\n\t\t\t// Wait for the calculated time before retrying.\n\t\t\tretryAfterMs = calculateMaxWaitTime(retryAfterMs, error);\n\t\t\tif (progress.onRetry) {\n\t\t\t\tprogress.onRetry(retryAfterMs, error);\n\t\t\t}\n\t\t\tawait delay(retryAfterMs);\n\t\t}\n\t} while (!success);\n\tif (numRetries > 0) {\n\t\tlogger.sendTelemetryEvent(\n\t\t\t{\n\t\t\t\teventName: `${fetchCallName}_lastError`,\n\t\t\t\tretry: numRetries,\n\t\t\t\tduration: performanceNow() - startTime,\n\t\t\t\tfetchCallName,\n\t\t\t},\n\t\t\tlastError,\n\t\t);\n\t}\n\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\treturn result!;\n}\n\nconst MaxReconnectDelayInMsWhenEndpointIsReachable = 60000;\nconst MaxReconnectDelayInMsWhenEndpointIsNotReachable = 8000;\n\n/**\n * Calculates time to wait for after an error based on the error and wait time for previous iteration.\n * In case endpoint(service or socket) is not reachable, then we maybe offline or may have got some\n * transient error not related to endpoint, in that case we want to try at faster pace and hence the\n * max wait is lesser 8s as compared to when endpoint is reachable in which case it is 60s.\n * @param delayMs - wait time for previous iteration\n * @param error - error based on which we decide wait time.\n * @returns Wait time to wait for.\n * @internal\n */\nexport function calculateMaxWaitTime(delayMs: number, error: unknown): number {\n\tconst retryDelayFromError = getRetryDelayFromError(error);\n\tlet newDelayMs = Math.max(retryDelayFromError ?? 0, delayMs * 2);\n\tnewDelayMs = Math.min(\n\t\tnewDelayMs,\n\t\tisFluidError(error) && error.getTelemetryProperties().endpointReached === true\n\t\t\t? MaxReconnectDelayInMsWhenEndpointIsReachable\n\t\t\t: MaxReconnectDelayInMsWhenEndpointIsNotReachable,\n\t);\n\treturn newDelayMs;\n}\n"]}
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.52.11"
8
+ "packageVersion": "7.58.1"
9
9
  }
10
10
  ]
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/driver-utils",
3
- "version": "2.91.0",
3
+ "version": "2.93.0",
4
4
  "description": "Collection of utility functions for Fluid drivers",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -69,27 +69,27 @@
69
69
  "temp-directory": "nyc/.nyc_output"
70
70
  },
71
71
  "dependencies": {
72
- "@fluid-internal/client-utils": "~2.91.0",
73
- "@fluidframework/core-interfaces": "~2.91.0",
74
- "@fluidframework/core-utils": "~2.91.0",
75
- "@fluidframework/driver-definitions": "~2.91.0",
76
- "@fluidframework/telemetry-utils": "~2.91.0",
72
+ "@fluid-internal/client-utils": "~2.93.0",
73
+ "@fluidframework/core-interfaces": "~2.93.0",
74
+ "@fluidframework/core-utils": "~2.93.0",
75
+ "@fluidframework/driver-definitions": "~2.93.0",
76
+ "@fluidframework/telemetry-utils": "~2.93.0",
77
77
  "lz4js": "^0.2.0",
78
78
  "uuid": "^11.1.0"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@arethetypeswrong/cli": "^0.18.2",
82
82
  "@biomejs/biome": "~2.4.5",
83
- "@fluid-internal/mocha-test-setup": "~2.91.0",
84
- "@fluid-tools/build-cli": "^0.63.0",
83
+ "@fluid-internal/mocha-test-setup": "~2.93.0",
84
+ "@fluid-tools/build-cli": "^0.64.0",
85
85
  "@fluidframework/build-common": "^2.0.3",
86
- "@fluidframework/build-tools": "^0.63.0",
87
- "@fluidframework/driver-utils-previous": "npm:@fluidframework/driver-utils@2.83.0",
88
- "@fluidframework/eslint-config-fluid": "~2.91.0",
89
- "@microsoft/api-extractor": "7.52.11",
86
+ "@fluidframework/build-tools": "^0.64.0",
87
+ "@fluidframework/driver-utils-previous": "npm:@fluidframework/driver-utils@2.92.0",
88
+ "@fluidframework/eslint-config-fluid": "^9.0.0",
89
+ "@microsoft/api-extractor": "7.58.1",
90
90
  "@types/lz4js": "^0.2.0",
91
91
  "@types/mocha": "^10.0.10",
92
- "@types/node": "~20.19.30",
92
+ "@types/node": "~22.19.17",
93
93
  "@types/sinon": "^17.0.3",
94
94
  "c8": "^10.1.3",
95
95
  "concurrently": "^9.2.1",
@@ -109,7 +109,7 @@
109
109
  },
110
110
  "scripts": {
111
111
  "api": "fluid-build . --task api",
112
- "api-extractor:commonjs": "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./dist",
112
+ "api-extractor:commonjs": "flub generate entrypoints --resolutionConditions require --outFileLegacyBeta legacy --outDir ./dist",
113
113
  "api-extractor:esnext": "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./lib --node10TypeCompat",
114
114
  "build": "fluid-build . --task build",
115
115
  "build:api-reports": "concurrently \"npm:build:api-reports:*\"",
@@ -150,7 +150,6 @@
150
150
  "test:mocha:esm": "mocha",
151
151
  "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
152
152
  "tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist",
153
- "typetests:gen": "flub generate typetests --dir . -v",
154
- "typetests:prepare": "flub typetests --dir . --reset --previous --normalize"
153
+ "typetests:gen": "flub generate typetests --dir . -v"
155
154
  }
156
155
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/driver-utils";
9
- export const pkgVersion = "2.91.0";
9
+ export const pkgVersion = "2.93.0";
@@ -8,6 +8,7 @@ import { delay } from "@fluidframework/core-utils/internal";
8
8
  import { DriverErrorTypes } from "@fluidframework/driver-definitions/internal";
9
9
  import {
10
10
  isFluidError,
11
+ wrapError,
11
12
  type ITelemetryLoggerExt,
12
13
  } from "@fluidframework/telemetry-utils/internal";
13
14
 
@@ -57,6 +58,7 @@ export async function runWithRetry<T>(
57
58
  fetchCallName: string,
58
59
  logger: ITelemetryLoggerExt,
59
60
  progress: IProgress,
61
+ maxRetries?: number,
60
62
  ): Promise<T> {
61
63
  let result: T | undefined;
62
64
  let success = false;
@@ -122,6 +124,35 @@ export async function runWithRetry<T>(
122
124
  }
123
125
 
124
126
  numRetries++;
127
+
128
+ // Check if max retries limit has been reached
129
+ if (maxRetries !== undefined && numRetries > maxRetries) {
130
+ logger.sendTelemetryEvent(
131
+ {
132
+ eventName: `${fetchCallName}_maxRetriesExceeded`,
133
+ retry: numRetries - 1,
134
+ maxRetries,
135
+ duration: performanceNow() - startTime,
136
+ fetchCallName,
137
+ },
138
+ error,
139
+ );
140
+ // Wrap the original error to preserve its details while marking it non-retriable
141
+ throw wrapError(
142
+ error,
143
+ (message) =>
144
+ new NonRetryableError(
145
+ `runWithRetry failed after max retries: ${message}`,
146
+ DriverErrorTypes.genericError,
147
+ {
148
+ driverVersion: pkgVersion,
149
+ fetchCallName,
150
+ maxRetries,
151
+ },
152
+ ),
153
+ );
154
+ }
155
+
125
156
  lastError = error;
126
157
  // Wait for the calculated time before retrying.
127
158
  retryAfterMs = calculateMaxWaitTime(retryAfterMs, error);