@smithy/util-endpoints
Advanced tools
| export class BinaryDecisionDiagram { | ||
| nodes; | ||
| root; | ||
| conditions; | ||
| results; | ||
| constructor(bdd, root, conditions, results) { | ||
| this.nodes = bdd; | ||
| this.root = root; | ||
| this.conditions = conditions; | ||
| this.results = results; | ||
| } | ||
| static from(bdd, root, conditions, results) { | ||
| return new BinaryDecisionDiagram(bdd, root, conditions, results); | ||
| } | ||
| } |
| import { EndpointError } from "./types"; | ||
| import { evaluateCondition } from "./utils/evaluateCondition"; | ||
| import { getEndpointHeaders } from "./utils/getEndpointHeaders"; | ||
| import { getEndpointProperties } from "./utils/getEndpointProperties"; | ||
| import { getEndpointUrl } from "./utils/getEndpointUrl"; | ||
| const RESULT = 100_000_000; | ||
| export const decideEndpoint = (bdd, options) => { | ||
| const { nodes, root, results, conditions } = bdd; | ||
| let ref = root; | ||
| const referenceRecord = {}; | ||
| const closure = { | ||
| referenceRecord, | ||
| endpointParams: options.endpointParams, | ||
| logger: options.logger, | ||
| }; | ||
| while (ref !== 1 && ref !== -1 && ref < RESULT) { | ||
| const node_i = 3 * (Math.abs(ref) - 1); | ||
| const [condition_i, highRef, lowRef] = [nodes[node_i], nodes[node_i + 1], nodes[node_i + 2]]; | ||
| const [fn, argv, assign] = conditions[condition_i]; | ||
| const evaluation = evaluateCondition({ fn, assign, argv }, closure); | ||
| if (evaluation.toAssign) { | ||
| const { name, value } = evaluation.toAssign; | ||
| referenceRecord[name] = value; | ||
| } | ||
| ref = ref >= 0 === evaluation.result ? highRef : lowRef; | ||
| } | ||
| if (ref >= RESULT) { | ||
| const result = results[ref - RESULT]; | ||
| if (result[0] === -1) { | ||
| const [, errorMessage] = result; | ||
| throw new EndpointError(errorMessage); | ||
| } | ||
| const [url, properties, headers] = result; | ||
| return { | ||
| url: getEndpointUrl(url, closure), | ||
| properties: getEndpointProperties(properties, closure), | ||
| headers: getEndpointHeaders(headers, closure), | ||
| }; | ||
| } | ||
| throw new EndpointError(`No matching endpoint.`); | ||
| }; |
| export function coalesce(...args) { | ||
| for (const arg of args) { | ||
| if (arg != null) { | ||
| return arg; | ||
| } | ||
| } | ||
| return undefined; | ||
| } |
| export function ite(condition, trueValue, falseValue) { | ||
| return condition ? trueValue : falseValue; | ||
| } |
| export function split(value, delimiter, limit) { | ||
| if (limit === 1) { | ||
| return [value]; | ||
| } | ||
| if (value === "") { | ||
| return [""]; | ||
| } | ||
| const parts = value.split(delimiter); | ||
| if (limit === 0) { | ||
| return parts; | ||
| } | ||
| return parts.slice(0, limit - 1).concat(parts.slice(1).join(delimiter)); | ||
| } |
| import type { EndpointObjectHeaders, ParameterObject } from "@smithy/types"; | ||
| import type { FunctionArgv } from "../types/shared"; | ||
| /** | ||
| * @internal | ||
| */ | ||
| type BddCondition = [string, FunctionArgv] | [string, FunctionArgv, string]; | ||
| /** | ||
| * @internal | ||
| */ | ||
| type BddResult = [-1] | [-1, string] | [string, Record<string, ParameterObject>, EndpointObjectHeaders]; | ||
| /** | ||
| * @internal | ||
| */ | ||
| export declare class BinaryDecisionDiagram { | ||
| nodes: Int32Array; | ||
| root: number; | ||
| conditions: BddCondition[]; | ||
| results: BddResult[]; | ||
| private constructor(); | ||
| static from(bdd: Int32Array, root: number, conditions: BddCondition[] | any[], results: BddResult[] | any[]): BinaryDecisionDiagram; | ||
| } | ||
| export {}; |
| import type { EndpointV2 } from "@smithy/types"; | ||
| import type { BinaryDecisionDiagram } from "./bdd/BinaryDecisionDiagram"; | ||
| import type { EndpointResolverOptions } from "./types"; | ||
| /** | ||
| * Resolves an endpoint URL by processing the endpoints bdd and options. | ||
| */ | ||
| export declare const decideEndpoint: (bdd: BinaryDecisionDiagram, options: EndpointResolverOptions) => EndpointV2; |
| /** | ||
| * Evaluates arguments in order and returns the first non-empty result, | ||
| * otherwise returns the result of the last argument. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function coalesce<T>(...args: (T | undefined)[]): T | undefined; |
| /** | ||
| * An if-then-else function that returns one of two values based on a boolean condition. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function ite<T>(condition: boolean, trueValue: T | undefined, falseValue: T | undefined): T | undefined; |
| /** | ||
| * The split function divides a string into an array of substrings based on a non-empty delimiter. | ||
| * The behavior is controlled by the limit parameter: | ||
| * | ||
| * limit = 0: Split all occurrences (unlimited). | ||
| * limit = 1: No split performed (returns original string as single element array). | ||
| * limit > 1: Split into at most 'limit' parts (performs limit-1 splits). | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function split(value: string, delimiter: string, limit: number): string[]; |
+154
-61
@@ -5,2 +5,18 @@ 'use strict'; | ||
| class BinaryDecisionDiagram { | ||
| nodes; | ||
| root; | ||
| conditions; | ||
| results; | ||
| constructor(bdd, root, conditions, results) { | ||
| this.nodes = bdd; | ||
| this.root = root; | ||
| this.conditions = conditions; | ||
| this.results = results; | ||
| } | ||
| static from(bdd, root, conditions, results) { | ||
| return new BinaryDecisionDiagram(bdd, root, conditions, results); | ||
| } | ||
| } | ||
| class EndpointCache { | ||
@@ -57,21 +73,9 @@ capacity; | ||
| const IP_V4_REGEX = new RegExp(`^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}$`); | ||
| const isIpAddress = (value) => IP_V4_REGEX.test(value) || (value.startsWith("[") && value.endsWith("]")); | ||
| const VALID_HOST_LABEL_REGEX = new RegExp(`^(?!.*-$)(?!-)[a-zA-Z0-9-]{1,63}$`); | ||
| const isValidHostLabel = (value, allowSubDomains = false) => { | ||
| if (!allowSubDomains) { | ||
| return VALID_HOST_LABEL_REGEX.test(value); | ||
| class EndpointError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "EndpointError"; | ||
| } | ||
| const labels = value.split("."); | ||
| for (const label of labels) { | ||
| if (!isValidHostLabel(label)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| } | ||
| const customEndpointFunctions = {}; | ||
| const debugId = "endpoints"; | ||
@@ -92,11 +96,15 @@ | ||
| class EndpointError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "EndpointError"; | ||
| const customEndpointFunctions = {}; | ||
| const booleanEquals = (value1, value2) => value1 === value2; | ||
| function coalesce(...args) { | ||
| for (const arg of args) { | ||
| if (arg != null) { | ||
| return arg; | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
| const booleanEquals = (value1, value2) => value1 === value2; | ||
| const getAttrPathList = (path) => { | ||
@@ -139,4 +147,25 @@ const parts = path.split("."); | ||
| const VALID_HOST_LABEL_REGEX = new RegExp(`^(?!.*-$)(?!-)[a-zA-Z0-9-]{1,63}$`); | ||
| const isValidHostLabel = (value, allowSubDomains = false) => { | ||
| if (!allowSubDomains) { | ||
| return VALID_HOST_LABEL_REGEX.test(value); | ||
| } | ||
| const labels = value.split("."); | ||
| for (const label of labels) { | ||
| if (!isValidHostLabel(label)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| function ite(condition, trueValue, falseValue) { | ||
| return condition ? trueValue : falseValue; | ||
| } | ||
| const not = (value) => !value; | ||
| const IP_V4_REGEX = new RegExp(`^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}$`); | ||
| const isIpAddress = (value) => IP_V4_REGEX.test(value) || (value.startsWith("[") && value.endsWith("]")); | ||
| const DEFAULT_PORTS = { | ||
@@ -192,6 +221,20 @@ [types.EndpointURLScheme.HTTP]: 80, | ||
| function split(value, delimiter, limit) { | ||
| if (limit === 1) { | ||
| return [value]; | ||
| } | ||
| if (value === "") { | ||
| return [""]; | ||
| } | ||
| const parts = value.split(delimiter); | ||
| if (limit === 0) { | ||
| return parts; | ||
| } | ||
| return parts.slice(0, limit - 1).concat(parts.slice(1).join(delimiter)); | ||
| } | ||
| const stringEquals = (value1, value2) => value1 === value2; | ||
| const substring = (input, start, stop, reverse) => { | ||
| if (start >= stop || input.length < stop || /[^\u0000-\u007f]/.test(input)) { | ||
| if (input == null || start >= stop || input.length < stop || /[^\u0000-\u007f]/.test(input)) { | ||
| return null; | ||
@@ -209,7 +252,10 @@ } | ||
| booleanEquals, | ||
| coalesce, | ||
| getAttr, | ||
| isSet, | ||
| isValidHostLabel, | ||
| ite, | ||
| not, | ||
| parseURL, | ||
| split, | ||
| stringEquals, | ||
@@ -222,6 +268,3 @@ substring, | ||
| const evaluatedTemplateArr = []; | ||
| const templateContext = { | ||
| ...options.endpointParams, | ||
| ...options.referenceRecord, | ||
| }; | ||
| const { referenceRecord, endpointParams } = options; | ||
| let currentIndex = 0; | ||
@@ -247,6 +290,6 @@ while (currentIndex < template.length) { | ||
| const [refName, attrName] = parameterName.split("#"); | ||
| evaluatedTemplateArr.push(getAttr(templateContext[refName], attrName)); | ||
| evaluatedTemplateArr.push(getAttr((referenceRecord[refName] ?? endpointParams[refName]), attrName)); | ||
| } | ||
| else { | ||
| evaluatedTemplateArr.push(templateContext[parameterName]); | ||
| evaluatedTemplateArr.push((referenceRecord[parameterName] ?? endpointParams[parameterName])); | ||
| } | ||
@@ -259,7 +302,3 @@ currentIndex = closingBraceIndex + 1; | ||
| const getReferenceValue = ({ ref }, options) => { | ||
| const referenceRecord = { | ||
| ...options.endpointParams, | ||
| ...options.referenceRecord, | ||
| }; | ||
| return referenceRecord[ref]; | ||
| return options.referenceRecord[ref] ?? options.endpointParams[ref]; | ||
| }; | ||
@@ -280,8 +319,23 @@ | ||
| const callFunction = ({ fn, argv }, options) => { | ||
| const evaluatedArgs = argv.map((arg) => ["boolean", "number"].includes(typeof arg) ? arg : group$2.evaluateExpression(arg, "arg", options)); | ||
| const fnSegments = fn.split("."); | ||
| if (fnSegments[0] in customEndpointFunctions && fnSegments[1] != null) { | ||
| return customEndpointFunctions[fnSegments[0]][fnSegments[1]](...evaluatedArgs); | ||
| const evaluatedArgs = Array(argv.length); | ||
| for (let i = 0; i < evaluatedArgs.length; ++i) { | ||
| const arg = argv[i]; | ||
| if (typeof arg === "boolean" || typeof arg === "number") { | ||
| evaluatedArgs[i] = arg; | ||
| } | ||
| else { | ||
| evaluatedArgs[i] = group$2.evaluateExpression(arg, "arg", options); | ||
| } | ||
| } | ||
| return endpointFunctions[fn](...evaluatedArgs); | ||
| if (fn.includes(".")) { | ||
| const fnSegments = fn.split("."); | ||
| if (fnSegments[0] in customEndpointFunctions && fnSegments[1] != null) { | ||
| return customEndpointFunctions[fnSegments[0]][fnSegments[1]](...evaluatedArgs); | ||
| } | ||
| } | ||
| if (typeof endpointFunctions[fn] !== "function") { | ||
| throw new Error(`function ${fn} not loaded in endpointFunctions.`); | ||
| } | ||
| const callable = endpointFunctions[fn]; | ||
| return callable(...evaluatedArgs); | ||
| }; | ||
@@ -305,23 +359,2 @@ const group$2 = { | ||
| const evaluateConditions = (conditions = [], options) => { | ||
| const conditionsReferenceRecord = {}; | ||
| for (const condition of conditions) { | ||
| const { result, toAssign } = evaluateCondition(condition, { | ||
| ...options, | ||
| referenceRecord: { | ||
| ...options.referenceRecord, | ||
| ...conditionsReferenceRecord, | ||
| }, | ||
| }); | ||
| if (!result) { | ||
| return { result }; | ||
| } | ||
| if (toAssign) { | ||
| conditionsReferenceRecord[toAssign.name] = toAssign.value; | ||
| options.logger?.debug?.(`${debugId} assign: ${toAssign.name} := ${toDebugString(toAssign.value)}`); | ||
| } | ||
| } | ||
| return { result: true, referenceRecord: conditionsReferenceRecord }; | ||
| }; | ||
| const getEndpointHeaders = (headers, options) => Object.entries(headers).reduce((acc, [headerKey, headerVal]) => ({ | ||
@@ -379,2 +412,60 @@ ...acc, | ||
| const RESULT = 100_000_000; | ||
| const decideEndpoint = (bdd, options) => { | ||
| const { nodes, root, results, conditions } = bdd; | ||
| let ref = root; | ||
| const referenceRecord = {}; | ||
| const closure = { | ||
| referenceRecord, | ||
| endpointParams: options.endpointParams, | ||
| logger: options.logger, | ||
| }; | ||
| while (ref !== 1 && ref !== -1 && ref < RESULT) { | ||
| const node_i = 3 * (Math.abs(ref) - 1); | ||
| const [condition_i, highRef, lowRef] = [nodes[node_i], nodes[node_i + 1], nodes[node_i + 2]]; | ||
| const [fn, argv, assign] = conditions[condition_i]; | ||
| const evaluation = evaluateCondition({ fn, assign, argv }, closure); | ||
| if (evaluation.toAssign) { | ||
| const { name, value } = evaluation.toAssign; | ||
| referenceRecord[name] = value; | ||
| } | ||
| ref = ref >= 0 === evaluation.result ? highRef : lowRef; | ||
| } | ||
| if (ref >= RESULT) { | ||
| const result = results[ref - RESULT]; | ||
| if (result[0] === -1) { | ||
| const [, errorMessage] = result; | ||
| throw new EndpointError(errorMessage); | ||
| } | ||
| const [url, properties, headers] = result; | ||
| return { | ||
| url: getEndpointUrl(url, closure), | ||
| properties: getEndpointProperties(properties, closure), | ||
| headers: getEndpointHeaders(headers, closure), | ||
| }; | ||
| } | ||
| throw new EndpointError(`No matching endpoint.`); | ||
| }; | ||
| const evaluateConditions = (conditions = [], options) => { | ||
| const conditionsReferenceRecord = {}; | ||
| for (const condition of conditions) { | ||
| const { result, toAssign } = evaluateCondition(condition, { | ||
| ...options, | ||
| referenceRecord: { | ||
| ...options.referenceRecord, | ||
| ...conditionsReferenceRecord, | ||
| }, | ||
| }); | ||
| if (!result) { | ||
| return { result }; | ||
| } | ||
| if (toAssign) { | ||
| conditionsReferenceRecord[toAssign.name] = toAssign.value; | ||
| options.logger?.debug?.(`${debugId} assign: ${toAssign.name} := ${toDebugString(toAssign.value)}`); | ||
| } | ||
| } | ||
| return { result: true, referenceRecord: conditionsReferenceRecord }; | ||
| }; | ||
| const evaluateEndpointRule = (endpointRule, options) => { | ||
@@ -479,7 +570,9 @@ const { conditions, endpoint } = endpointRule; | ||
| exports.BinaryDecisionDiagram = BinaryDecisionDiagram; | ||
| exports.EndpointCache = EndpointCache; | ||
| exports.EndpointError = EndpointError; | ||
| exports.customEndpointFunctions = customEndpointFunctions; | ||
| exports.decideEndpoint = decideEndpoint; | ||
| exports.isIpAddress = isIpAddress; | ||
| exports.isValidHostLabel = isValidHostLabel; | ||
| exports.resolveEndpoint = resolveEndpoint; |
+2
-0
@@ -0,2 +1,4 @@ | ||
| export { BinaryDecisionDiagram } from "./bdd/BinaryDecisionDiagram"; | ||
| export * from "./cache/EndpointCache"; | ||
| export { decideEndpoint } from "./decideEndpoint"; | ||
| export * from "./lib/isIpAddress"; | ||
@@ -3,0 +5,0 @@ export * from "./lib/isValidHostLabel"; |
| export * from "./booleanEquals"; | ||
| export * from "./coalesce"; | ||
| export * from "./getAttr"; | ||
| export * from "./isSet"; | ||
| export * from "./isValidHostLabel"; | ||
| export * from "./ite"; | ||
| export * from "./not"; | ||
| export * from "./parseURL"; | ||
| export * from "./split"; | ||
| export * from "./stringEquals"; | ||
| export * from "./substring"; | ||
| export * from "./uriEncode"; |
| export const substring = (input, start, stop, reverse) => { | ||
| if (start >= stop || input.length < stop || /[^\u0000-\u007f]/.test(input)) { | ||
| if (input == null || start >= stop || input.length < stop || /[^\u0000-\u007f]/.test(input)) { | ||
| return null; | ||
@@ -4,0 +4,0 @@ } |
@@ -1,9 +0,12 @@ | ||
| import { booleanEquals, getAttr, isSet, isValidHostLabel, not, parseURL, stringEquals, substring, uriEncode, } from "../lib"; | ||
| import { booleanEquals, coalesce, getAttr, isSet, isValidHostLabel, ite, not, parseURL, split, stringEquals, substring, uriEncode, } from "../lib"; | ||
| export const endpointFunctions = { | ||
| booleanEquals, | ||
| coalesce, | ||
| getAttr, | ||
| isSet, | ||
| isValidHostLabel, | ||
| ite, | ||
| not, | ||
| parseURL, | ||
| split, | ||
| stringEquals, | ||
@@ -10,0 +13,0 @@ substring, |
@@ -19,8 +19,23 @@ import { EndpointError } from "../types"; | ||
| export const callFunction = ({ fn, argv }, options) => { | ||
| const evaluatedArgs = argv.map((arg) => ["boolean", "number"].includes(typeof arg) ? arg : group.evaluateExpression(arg, "arg", options)); | ||
| const fnSegments = fn.split("."); | ||
| if (fnSegments[0] in customEndpointFunctions && fnSegments[1] != null) { | ||
| return customEndpointFunctions[fnSegments[0]][fnSegments[1]](...evaluatedArgs); | ||
| const evaluatedArgs = Array(argv.length); | ||
| for (let i = 0; i < evaluatedArgs.length; ++i) { | ||
| const arg = argv[i]; | ||
| if (typeof arg === "boolean" || typeof arg === "number") { | ||
| evaluatedArgs[i] = arg; | ||
| } | ||
| else { | ||
| evaluatedArgs[i] = group.evaluateExpression(arg, "arg", options); | ||
| } | ||
| } | ||
| return endpointFunctions[fn](...evaluatedArgs); | ||
| if (fn.includes(".")) { | ||
| const fnSegments = fn.split("."); | ||
| if (fnSegments[0] in customEndpointFunctions && fnSegments[1] != null) { | ||
| return customEndpointFunctions[fnSegments[0]][fnSegments[1]](...evaluatedArgs); | ||
| } | ||
| } | ||
| if (typeof endpointFunctions[fn] !== "function") { | ||
| throw new Error(`function ${fn} not loaded in endpointFunctions.`); | ||
| } | ||
| const callable = endpointFunctions[fn]; | ||
| return callable(...evaluatedArgs); | ||
| }; | ||
@@ -27,0 +42,0 @@ export const group = { |
| import { getAttr } from "../lib"; | ||
| export const evaluateTemplate = (template, options) => { | ||
| const evaluatedTemplateArr = []; | ||
| const templateContext = { | ||
| ...options.endpointParams, | ||
| ...options.referenceRecord, | ||
| }; | ||
| const { referenceRecord, endpointParams } = options; | ||
| let currentIndex = 0; | ||
@@ -28,6 +25,6 @@ while (currentIndex < template.length) { | ||
| const [refName, attrName] = parameterName.split("#"); | ||
| evaluatedTemplateArr.push(getAttr(templateContext[refName], attrName)); | ||
| evaluatedTemplateArr.push(getAttr((referenceRecord[refName] ?? endpointParams[refName]), attrName)); | ||
| } | ||
| else { | ||
| evaluatedTemplateArr.push(templateContext[parameterName]); | ||
| evaluatedTemplateArr.push((referenceRecord[parameterName] ?? endpointParams[parameterName])); | ||
| } | ||
@@ -34,0 +31,0 @@ currentIndex = closingBraceIndex + 1; |
| export const getReferenceValue = ({ ref }, options) => { | ||
| const referenceRecord = { | ||
| ...options.endpointParams, | ||
| ...options.referenceRecord, | ||
| }; | ||
| return referenceRecord[ref]; | ||
| return options.referenceRecord[ref] ?? options.endpointParams[ref]; | ||
| }; |
@@ -0,2 +1,4 @@ | ||
| export { BinaryDecisionDiagram } from "./bdd/BinaryDecisionDiagram"; | ||
| export * from "./cache/EndpointCache"; | ||
| export { decideEndpoint } from "./decideEndpoint"; | ||
| export * from "./lib/isIpAddress"; | ||
@@ -3,0 +5,0 @@ export * from "./lib/isValidHostLabel"; |
| export * from "./booleanEquals"; | ||
| export * from "./coalesce"; | ||
| export * from "./getAttr"; | ||
| export * from "./isSet"; | ||
| export * from "./isValidHostLabel"; | ||
| export * from "./ite"; | ||
| export * from "./not"; | ||
| export * from "./parseURL"; | ||
| export * from "./split"; | ||
| export * from "./stringEquals"; | ||
| export * from "./substring"; | ||
| export * from "./uriEncode"; |
@@ -0,8 +1,12 @@ | ||
| import { coalesce, ite, split } from "../lib"; | ||
| export declare const endpointFunctions: { | ||
| booleanEquals: (value1: boolean, value2: boolean) => boolean; | ||
| coalesce: typeof coalesce; | ||
| getAttr: (value: import("../lib").GetAttrValue, path: string) => import("../lib").GetAttrValue; | ||
| isSet: (value: unknown) => value is {}; | ||
| isValidHostLabel: (value: string, allowSubDomains?: boolean) => boolean; | ||
| ite: typeof ite; | ||
| not: (value: boolean) => boolean; | ||
| parseURL: (value: string | URL | import("@smithy/types").Endpoint) => import("@smithy/types").EndpointURL | null; | ||
| split: typeof split; | ||
| stringEquals: (value1: string, value2: string) => boolean; | ||
@@ -9,0 +13,0 @@ substring: (input: string, start: number, stop: number, reverse: boolean) => string | null; |
| import type { EvaluateOptions, ReferenceObject } from "../types"; | ||
| export declare const getReferenceValue: ({ ref }: ReferenceObject, options: EvaluateOptions) => import("../types").FunctionReturn; | ||
| export declare const getReferenceValue: ({ ref }: ReferenceObject, options: EvaluateOptions) => string | number | boolean | import("@smithy/types").EndpointPartition | import("@smithy/types").EndpointARN | { | ||
| [key: string]: import("../types").FunctionReturn; | ||
| }; |
+1
-1
| { | ||
| "name": "@smithy/util-endpoints", | ||
| "version": "3.3.4", | ||
| "version": "3.4.0", | ||
| "description": "Utilities to help with endpoint resolution.", | ||
@@ -5,0 +5,0 @@ "main": "./dist-cjs/index.js", |
71303
13.81%102
10.87%1466
20.36%