From ddef9fffe07da7ab1da420f42346a8fb7fcffad3 Mon Sep 17 00:00:00 2001 From: Douglas Midgley Date: Sun, 28 Jul 2024 23:27:43 +0200 Subject: [PATCH 1/2] #254-Moved auth to fetch + mock tests --- .vscode/launch.json | 26 ++++++++ @types/auth.d.ts | 17 +++-- @types/auth.d.ts.map | 2 +- @types/index.d.ts.map | 2 +- @types/rest.d.ts | 4 +- @types/rest.d.ts.map | 2 +- @types/soap.d.ts.map | 2 +- @types/util.d.ts | 45 ++++++++----- @types/util.d.ts.map | 2 +- README.md | 9 ++- lib/auth.js | 51 ++++++++------- lib/rest.js | 144 ++++++++++++++++++++---------------------- lib/util.js | 93 ++++++++++++++++----------- package-lock.json | 93 ++++++++++++++++++++++++++- package.json | 1 + test.js | 16 +++++ test/auth.test.js | 42 ++++++------ test/utils.js | 12 ++++ 18 files changed, 373 insertions(+), 190 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 test.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2ff9da3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Run app.js", + "type": "node", + "program": "app.js", // Assuming this is your main app file. + "stopOnEntry": false, + "args": [], + "cwd": ".", + "runtimeExecutable": null, + "env": { "NODE_ENV": "production" } + }, + { + "name": "Run mocha", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "stopOnEntry": false, + "args": ["${workspaceFolder}/test/auth.test.js"], + "cwd": ".", + "runtimeExecutable": null, + "env": { "NODE_ENV": "production" } + } + ] +} diff --git a/@types/auth.d.ts b/@types/auth.d.ts index 14f87e4..bf26e8b 100644 --- a/@types/auth.d.ts +++ b/@types/auth.d.ts @@ -12,6 +12,10 @@ export default class Auth { * @param {string} authObject.auth_url Auth URL from SFMC config * @param {string[]} [authObject.scope] Array of scopes used for requests * @param {object} options options for the SDK as a whole, for example collection of handler functions, or retry settings + * @param {object} options.requestAttempts number of attempts which should be made before + * @param {object} options.retryOnConnectionError if continued attempts should be made in case of connection issue + * + * */ constructor(authObject: { client_id: string; @@ -19,7 +23,10 @@ export default class Auth { account_id: number; auth_url: string; scope?: string[]; - }, options: object); + }, options: { + requestAttempts: object; + retryOnConnectionError: object; + }); authObject: { client_id: string; client_secret: string; @@ -27,15 +34,17 @@ export default class Auth { auth_url: string; scope?: string[]; }; - options: any; + options: { + requestAttempts: object; + retryOnConnectionError: object; + }; /** * * * @param {boolean} [forceRefresh] used to enforce a refresh of token - * @param {number} [remainingAttempts] number of retries in case of issues * @returns {Promise.} current session information */ - getAccessToken(forceRefresh?: boolean, remainingAttempts?: number): Promise; + getAccessToken(forceRefresh?: boolean): Promise; /** * Helper to get back list of scopes supported by SDK * diff --git a/@types/auth.d.ts.map b/@types/auth.d.ts.map index a21185f..41baba9 100644 --- a/@types/auth.d.ts.map +++ b/@types/auth.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../lib/auth.js"],"names":[],"mappings":"AA0FA;;GAEG;AACH;IACI;;;;;;;;;;OAUG;IACH;QAP8B,SAAS,EAA5B,MAAM;QACa,aAAa,EAAhC,MAAM;QACa,UAAU,EAA7B,MAAM;QACa,QAAQ,EAA3B,MAAM;QACgB,KAAK,GAA3B,MAAM,EAAE;gBACR,MAAM,EA8ChB;IAFG;mBAjDO,MAAM;uBACN,MAAM;oBACN,MAAM;kBACN,MAAM;gBACN,MAAM,EAAE;MA6CmD;IAClE,aAAsB;IAE1B;;;;;;OAMG;IACH,8BAJW,OAAO,sBACP,MAAM,GACJ,QAAS,MAAM,CAAC,CA8B5B;IAED;;;;OAIG;IACH,sBAFa,MAAM,EAAE,CAIpB;CACJ"} \ No newline at end of file +{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../lib/auth.js"],"names":[],"mappings":"AA0FA;;GAEG;AACH;IACI;;;;;;;;;;;;;;OAcG;IACH,wBAXG;QAA2B,SAAS,EAA5B,MAAM;QACa,aAAa,EAAhC,MAAM;QACa,UAAU,EAA7B,MAAM;QACa,QAAQ,EAA3B,MAAM;QACgB,KAAK,GAA3B,MAAM,EAAE;KAChB,WACA;QAAwB,eAAe,EAA/B,MAAM;QACU,sBAAsB,EAAtC,MAAM;KAGhB,EA6CA;IAFG;mBArDO,MAAM;uBACN,MAAM;oBACN,MAAM;kBACN,MAAM;gBACN,MAAM,EAAE;MAiDmD;IAClE;yBAhDO,MAAM;gCACN,MAAM;MA+CS;IAE1B;;;;;OAKG;IACH,8BAHW,OAAO,GACL,OAAO,CAAE,MAAM,CAAC,CA0B5B;IAED;;;;OAIG;IACH,sBAFa,MAAM,EAAE,CAIpB;CACJ"} \ No newline at end of file diff --git a/@types/index.d.ts.map b/@types/index.d.ts.map index f0ae9b9..d02b183 100644 --- a/@types/index.d.ts.map +++ b/@types/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.js"],"names":[],"mappings":"AAKA;;GAEG;AACH;IACI;;;;;;;;OAQG;IACH,wBANW,MAAM;QAEW,eAAe,GAAhC,MAAM;QACY,sBAAsB,GAAxC,OAAO;QACU,aAAa,GAA9B,MAAM;OAehB;IAHG,WAAyC;IACzC,WAAwC;IACxC,WAAwC;CAE/C;iBA/BgB,WAAW;iBACX,WAAW;iBACX,WAAW"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.js"],"names":[],"mappings":"AAKA;;GAEG;AACH;IACI;;;;;;;;OAQG;IACH,wBANW,MAAM,YAEd;QAAyB,eAAe,GAAhC,MAAM;QACY,sBAAsB,GAAxC,OAAO;QACU,aAAa,GAA9B,MAAM;KAChB,EAcA;IAHG,WAAyC;IACzC,WAAwC;IACxC,WAAwC;CAE/C;iBA/BgB,WAAW;iBACX,WAAW;iBACX,WAAW"} \ No newline at end of file diff --git a/@types/rest.d.ts b/@types/rest.d.ts index 3c810d0..d30bb43 100644 --- a/@types/rest.d.ts +++ b/@types/rest.d.ts @@ -91,10 +91,8 @@ export default class Rest { * Method that makes the api request * * @param {object} requestOptions configuration for the request including body - * @param {number} remainingAttempts number of times this request should be reattempted in case of error - * @param {object} [headers] optional headers to include in the request; note that Authorization-header is always overwritten * @returns {Promise.} Results from the Rest request in Object format */ - _apiRequest(requestOptions: object, remainingAttempts: number, headers?: object): Promise; + _apiRequest(requestOptions: object): Promise; } //# sourceMappingURL=rest.d.ts.map \ No newline at end of file diff --git a/@types/rest.d.ts.map b/@types/rest.d.ts.map index aba909b..bfe5598 100644 --- a/@types/rest.d.ts.map +++ b/@types/rest.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../lib/rest.js"],"names":[],"mappings":"AAUA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,wBAHW,MAAM,WACN,MAAM,EAUhB;IAPG,UAAsB;IACtB,aAAsB;IACtB,4BAIC;IAGL;;;;;OAKG;IACH,SAHW,MAAM,GACJ,QAAS,MAAM,CAAC,CAU5B;IACD;;;;;;OAMG;IACH,mCAEC;IACD;;;;;;OAMG;IACH,qBAEC;IACD;;;;;;;OAOG;IACH,aALW,MAAM,aACN,MAAM,kBACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAmF5B;IACD;;;;;;OAMG;IACH,wBAJW,MAAM,EAAE,oBACR,MAAM,GACJ,cAAe,CAmB3B;IACD;;;;;;;OAOG;IACH,UALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAU5B;IACD;;;;;;;OAOG;IACH,SALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAU5B;IACD;;;;;;;OAOG;IACH,WALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAU5B;IACD;;;;;OAKG;IACH,YAHW,MAAM,GACJ,QAAS,MAAM,CAAC,CAW5B;IACD;;;;;;;OAOG;IACH,4BALW,MAAM,qBACN,MAAM,YACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAiD5B;CACJ"} \ No newline at end of file +{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../lib/rest.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,wBAHW,MAAM,WACN,MAAM,EAUhB;IAPG,UAAsB;IACtB,aAAsB;IACtB,4BAIC;IAGL;;;;;OAKG;IACH,SAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAO5B;IACD;;;;;;OAMG;IACH,mCAEC;IACD;;;;;;OAMG;IACH,qBAEC;IACD;;;;;;;OAOG;IACH,aALW,MAAM,aACN,MAAM,kBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAgF5B;IACD;;;;;;OAMG;IACH,wBAJW,MAAM,EAAE,oBACR,MAAM,GACJ,OAAO,OAAQ,CAgB3B;IACD;;;;;;;OAOG;IACH,UALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;;;OAOG;IACH,SALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;;;OAOG;IACH,WALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;OAKG;IACH,YAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAO5B;IACD;;;;;OAKG;IACH,4BAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA6D5B;CACJ"} \ No newline at end of file diff --git a/@types/soap.d.ts.map b/@types/soap.d.ts.map index 077c34e..b3153d7 100644 --- a/@types/soap.d.ts.map +++ b/@types/soap.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"soap.d.ts","sourceRoot":"","sources":["../lib/soap.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,kBAHW,MAAM,WACN,MAAM,EAKhB;IAFG,UAAgB;IAChB,aAAsB;IAG1B;;;;;;;OAOG;IACH,eALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,QAAS,MAAM,CAAC,CAsD5B;IACD;;;;;;;OAOG;IACH,mBALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,QAAS,MAAM,CAAC,CAwB5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,QAAS,MAAM,CAAC,CA0B5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,QAAS,MAAM,CAAC,CA2B5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,QAAS,MAAM,CAAC,CA0B5B;IACD;;;;;;;;;OASG;IACH,eAPW,MAAM,YACN,MAAM,gBACN,QAAQ,MAAM,UACd,MAAM,YACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAiC5B;IACD;;;;;OAKG;IACH,eAHW,MAAM,GACJ,QAAS,MAAM,CAAC,CAuB5B;IACD;;;;;;OAMG;IACH,cAJW,MAAM,cACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAsB5B;IACD;;;;;;;OAOG;IACH,cALW,MAAM,UACN,MAAM,WACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAiC5B;IACD;;;;;;OAMG;IACH,gBAJW,MAAM,eACN,MAAM,EAAE,GACN,QAAS,MAAM,CAAC,CA4B5B;IAED;;;;;;OAMG;IACH,qBAJW,MAAM,qBACN,MAAM,GACJ,QAAS,MAAM,CAAC,CAsE5B;CACJ"} \ No newline at end of file +{"version":3,"file":"soap.d.ts","sourceRoot":"","sources":["../lib/soap.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,kBAHW,MAAM,WACN,MAAM,EAKhB;IAFG,UAAgB;IAChB,aAAsB;IAG1B;;;;;;;OAOG;IACH,eALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAsD5B;IACD;;;;;;;OAOG;IACH,mBALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAwB5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA0B5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA2B5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA0B5B;IACD;;;;;;;;;OASG;IACH,eAPW,MAAM,YACN,MAAM,gBACN,QAAQ,MAAM,UACd,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAiC5B;IACD;;;;;OAKG;IACH,eAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAuB5B;IACD;;;;;;OAMG;IACH,cAJW,MAAM,cACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAsB5B;IACD;;;;;;;OAOG;IACH,cALW,MAAM,UACN,MAAM,WACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAiC5B;IACD;;;;;;OAMG;IACH,gBAJW,MAAM,eACN,MAAM,EAAE,GACN,OAAO,CAAE,MAAM,CAAC,CA4B5B;IAED;;;;;;OAMG;IACH,qBAJW,MAAM,qBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAsE5B;CACJ"} \ No newline at end of file diff --git a/@types/util.d.ts b/@types/util.d.ts index 22416d0..5b414fd 100644 --- a/@types/util.d.ts +++ b/@types/util.d.ts @@ -19,6 +19,13 @@ export function isPayload(object: object): boolean; * @returns {boolean} true if a connection error */ export function isConnectionError(code: string): boolean; +/** + * Method to check if the repsonse is JSON + * + * @param {object} apiResponse Fetch response before parsing body + * @returns {boolean} true if is simple Object + */ +export function isJSONResponse(apiResponse: object): boolean; /** * @typedef {object} EnhancedRestError - Error object * @augments {Error} @@ -34,21 +41,31 @@ export function isConnectionError(code: string): boolean; */ export class RestError extends Error { /** - * - * @param {EnhancedRestError} ex Error object + * @param {object} response api respone + * @param {object} responseBody rest body */ - constructor(ex: EnhancedRestError); + constructor(response: object, responseBody: object); code: any; - endpoint: any; response: any; + json: any; + endpoint: any; } /** - * @typedef {object} EnhancedSoapError - Error object + * CustomError type for handling Network based errors + * ie. errors not returning a 400-500 code + * + * @class NetworkError * @augments {Error} - * @property {object} response - - * @property {string} code - - * @property {string} endpoint - */ +export class NetworkError extends Error { + /** + * @param {Error} ex Error object + * @param {string} url url of request, if available + */ + constructor(ex: Error, url: string); + code: any; + endpoint: string; +} /** * CustomError type for handling SOAP based errors * @@ -58,22 +75,18 @@ export class RestError extends Error { export class SOAPError extends Error { /** * - * @param {EnhancedSoapError} ex Error object - * @param {object} response api respone - * @param {object} soapBody soap body + * @param {object} response api response + * @param {object} responseBody response body (parsed) */ - constructor(ex: EnhancedSoapError, response: object, soapBody: object); + constructor(response: object, responseBody: object); code: any; response: any; json: any; + endpoint: any; } export const axiosInstance: import("axios").AxiosInstance; /** * - Error object */ export type EnhancedRestError = object; -/** - * - Error object - */ -export type EnhancedSoapError = object; //# sourceMappingURL=util.d.ts.map \ No newline at end of file diff --git a/@types/util.d.ts.map b/@types/util.d.ts.map index 4dd41d8..cf93a43 100644 --- a/@types/util.d.ts.map +++ b/@types/util.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../lib/util.js"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,iCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AACH,wCAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;;;GAMG;AACH;;;;;GAKG;AACH;IACI;;;OAGG;IACH,gBAFW,iBAAiB,EAwB3B;IAlBO,UAAiD;IAWjD,cAA2B;IAE/B,cAA2B;CAMlC;AACD;;;;;;GAMG;AACH;;;;;GAKG;AACH;IACI;;;;;OAKG;IACH,gBAJW,iBAAiB,YACjB,MAAM,YACN,MAAM,EAwChB;IAlCO,UAAkC;IA4BtC,cAAwB;IACxB,UAAoB;CAM3B;AAED,0DAA4C;;;;gCAtG/B,MAAM;;;;gCA0CN,MAAM"} \ No newline at end of file +{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../lib/util.js"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,iCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AAEH,wCAJW,MAAM,GACJ,OAAO,CAQnB;AAkHD;;;;;GAKG;AACH,4CAHW,MAAM,GACJ,OAAO,CAInB;AAxHD;;;;;;GAMG;AACH;;;;;GAKG;AACH;IACI;;;OAGG;IACH,sBAHW,MAAM,gBACN,MAAM,EAsBhB;IAhBO,UAAkC;IAStC,cAAwB;IACxB,UAAwB;IACxB,cAA4B;CAMnC;AAED;;;;;;GAMG;AACH;IACI;;;OAGG;IACH,gBAHW,KAAK,OACL,MAAM,EAUhB;IANG,UAAmB;IACnB,iBAAmB;CAM1B;AAED;;;;;GAKG;AACH;IACI;;;;OAIG;IACH,sBAHW,MAAM,gBACN,MAAM,EAoChB;IA9BO,UAAsC;IAuB1C,cAAwB;IACxB,UAAwB;IACxB,cAA4B;CAMnC;AAYD,0DAA4C;;;;gCA1H/B,MAAM"} \ No newline at end of file diff --git a/README.md b/README.md index 89cadf4..86789bb 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The SDK will automatically request a new token if none is valid. the second parameter in the constructor is to allow for specific options such as events to execute a function. See the below example for supported events. This reduces the number of requests for token therefore increasing speed between executions (when testing was 2.5 seconds down to 1.5 seconds for one rest and one soap request) ```javascript -const SDK = require('sfmc-sdk'); +import SDK from 'sfmc-sdk'; const sfmc = new SDK( { client_id: 'XXXXX', @@ -42,11 +42,10 @@ const sfmc = new SDK( onRefresh: (options) => console.log('RefreshingToken.', Options), logRequest: (req) => console.log(req), logResponse: (res) => console.log(res), - onConnectionError: (ex, remainingAttempts) => console.log(ex.code, remainingAttempts) - + onConnectionError: (ex, remainingAttempts) => console.log(ex.code, remainingAttempts), }, - requestAttempts : 1 - retryOnConnectionError: true + requestAttempts: 1, + retryOnConnectionError: true, } ); ``` diff --git a/lib/auth.js b/lib/auth.js index c7ddb5a..2406e69 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,5 +1,5 @@ 'use strict'; -import { isConnectionError, RestError, axiosInstance as axios } from './util.js'; +import { RestError, isJSONResponse, NetworkError } from './util.js'; const AVAIALABLE_SCOPES = [ 'accounts_read', 'accounts_write', @@ -102,6 +102,10 @@ export default class Auth { * @param {string} authObject.auth_url Auth URL from SFMC config * @param {string[]} [authObject.scope] Array of scopes used for requests * @param {object} options options for the SDK as a whole, for example collection of handler functions, or retry settings + * @param {object} options.requestAttempts number of attempts which should be made before + * @param {object} options.retryOnConnectionError if continued attempts should be made in case of connection issue + * + * */ constructor(authObject, options) { if (!authObject) { @@ -152,30 +156,25 @@ export default class Auth { * * * @param {boolean} [forceRefresh] used to enforce a refresh of token - * @param {number} [remainingAttempts] number of retries in case of issues * @returns {Promise.} current session information */ - async getAccessToken(forceRefresh, remainingAttempts) { - if (remainingAttempts === undefined) { - remainingAttempts = this.options.requestAttempts; - } + async getAccessToken(forceRefresh) { if (Boolean(forceRefresh) || _isExpired(this.authObject)) { try { - remainingAttempts--; + this.options.requestAttempts--; this.authObject = await _requestToken(this.authObject); } catch (error) { - if ( - this.options.retryOnConnectionError && - remainingAttempts > 0 && - isConnectionError(error.code) - ) { + if (this.options.retryOnConnectionError && this.options.requestAttempts > 0) { if (this.options?.eventHandlers?.onConnectionError) { - this.options.eventHandlers.onConnectionError(error, remainingAttempts); + this.options.eventHandlers.onConnectionError( + error, + this.options.requestAttempts + ); } - return this.getAccessToken(forceRefresh, remainingAttempts); + return this.getAccessToken(forceRefresh); } - - throw new RestError(error); + error.endpoint = this.authObject?.auth_url; + throw error; } if (this.options?.eventHandlers?.onRefresh) { @@ -216,7 +215,6 @@ function _isExpired(authObject) { * @returns {Promise.} updated Auth Object */ async function _requestToken(authObject) { - // TODO retry logic const payload = { grant_type: 'client_credentials', client_id: authObject.client_id, @@ -226,14 +224,21 @@ async function _requestToken(authObject) { if (authObject.scope && Array.isArray(authObject.scope)) { payload.scope = authObject.scope.join(' '); } - const result = await axios({ + const apiResponse = await fetch(new URL('/v2/token', authObject.auth_url), { method: 'post', - baseURL: authObject.auth_url, - url: '/v2/token', - data: payload, + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + }, }); - return Object.assign(authObject, result.data, { - expiration: process.hrtime()[0] + result.data.expires_in, + const payloadResponse = isJSONResponse(apiResponse) + ? await apiResponse.json() + : await apiResponse.text(); + if (!apiResponse.ok) { + throw new RestError(apiResponse, payloadResponse); + } + return Object.assign(authObject, payloadResponse, { + expiration: process.hrtime()[0] + payloadResponse.expires_in, }); } /** diff --git a/lib/rest.js b/lib/rest.js index 1e7e364..5a2232d 100644 --- a/lib/rest.js +++ b/lib/rest.js @@ -1,11 +1,5 @@ 'use strict'; -import { - isObject, - isPayload, - isConnectionError, - RestError, - axiosInstance as axios, -} from './util.js'; +import { isObject, isPayload, RestError, isJSONResponse, NetworkError } from './util.js'; import pLimit from 'p-limit'; /** @@ -36,13 +30,10 @@ export default class Rest { * @returns {Promise.} API response */ get(url) { - return this._apiRequest( - { - method: 'GET', - url: url, - }, - this.options.requestAttempts - ); + return this._apiRequest({ + method: 'GET', + url: url, + }); } /** * helper for {@link this.getBulk} to determine if the url is a transactional message API @@ -96,13 +87,10 @@ export default class Rest { queryParameters.set(pageSizeKey, Number(pageSize).toString()); do { queryParameters.set(pageKey, Number(page).toString()); - const responseBatch = await this._apiRequest( - { - method: 'GET', - url: baseUrl + '?' + decodeURIComponent(queryParameters.toString()), - }, - this.options.requestAttempts - ); + const responseBatch = await this._apiRequest({ + method: 'GET', + url: baseUrl + '?' + decodeURIComponent(queryParameters.toString()), + }); // determine iterator field if not provided if (iteratorField && Array.isArray(responseBatch[iteratorField])) { @@ -168,13 +156,10 @@ export default class Rest { return Promise.all( urlArray.map((url) => limit(() => - this._apiRequest( - { - method: 'GET', - url: url, - }, - this.options.requestAttempts - ) + this._apiRequest({ + method: 'GET', + url: url, + }) ) ) ); @@ -191,10 +176,11 @@ export default class Rest { const requestOptions = { method: 'POST', url: url, - data: payload, + body: JSON.stringify(payload), + headers: headers, }; _checkPayload(requestOptions); - return this._apiRequest(requestOptions, this.options.requestAttempts, headers); + return this._apiRequest(requestOptions); } /** * Method that makes the PUT api request @@ -208,10 +194,11 @@ export default class Rest { const requestOptions = { method: 'PUT', url: url, - data: payload, + body: JSON.stringify(payload), + headers: headers, }; _checkPayload(requestOptions); - return this._apiRequest(requestOptions, this.options.requestAttempts, headers); + return this._apiRequest(requestOptions); } /** * Method that makes the PATCH api request @@ -225,10 +212,11 @@ export default class Rest { const requestOptions = { method: 'PATCH', url: url, - data: payload, + body: JSON.stringify(payload), + headers: headers, }; _checkPayload(requestOptions); - return this._apiRequest(requestOptions, this.options.requestAttempts, headers); + return this._apiRequest(requestOptions); } /** * Method that makes the DELETE api request @@ -237,70 +225,76 @@ export default class Rest { * @returns {Promise.} API response */ delete(url) { - return this._apiRequest( - { - method: 'DELETE', - - url: url, - }, - this.options.requestAttempts - ); + return this._apiRequest({ + method: 'DELETE', + url: url, + }); } /** * Method that makes the api request * * @param {object} requestOptions configuration for the request including body - * @param {number} remainingAttempts number of times this request should be reattempted in case of error - * @param {object} [headers] optional headers to include in the request; note that Authorization-header is always overwritten * @returns {Promise.} Results from the Rest request in Object format */ - async _apiRequest(requestOptions, remainingAttempts, headers = {}) { + async _apiRequest(requestOptions) { if (!isObject(requestOptions)) { throw new Error('requestOptions argument is required'); } + const _url = new URL(requestOptions.url, this.auth.authObject.rest_instance_url); + let apiResponse; + let payloadResponse; try { await this.auth.getAccessToken(); - requestOptions.baseURL = this.auth.authObject.rest_instance_url; - requestOptions.headers = headers; - requestOptions.headers.Authorization = `Bearer ` + this.auth.authObject.access_token; - remainingAttempts--; + const _requestOptions = { + method: requestOptions.method, + headers: Object.assign(requestOptions.headers, { + 'Content-Type': 'application/json', + Authorization: `Bearer ` + this.auth.authObject.access_token, + }), + }; + this.options.requestAttempts--; + if (this.options?.eventHandlers?.logRequest) { - this.options.eventHandlers.logRequest(requestOptions); + this.options.eventHandlers.logRequest(_requestOptions); } - const response = await axios(requestOptions); + apiResponse = await fetch(_url, _requestOptions); + + // if json then parse json, otherise text + payloadResponse = isJSONResponse(apiResponse) + ? await apiResponse.json() + : await apiResponse.text(); + if (this.options?.eventHandlers?.logResponse) { this.options.eventHandlers.logResponse({ - data: response.data, - status: response.status, + data: payloadResponse, + status: apiResponse.status, }); } - return response.data; } catch (error) { - if (requestOptions.url) { - error.endpoint = - requestOptions.baseURL + - (requestOptions.url.startsWith('/') - ? requestOptions.url.slice(1) - : requestOptions.url); - } - if ( - this.options.retryOnConnectionError && - remainingAttempts > 0 && - isConnectionError(error.code) - ) { + if (this.options.retryOnConnectionError && this.options.requestAttempts > 0) { if (this.options?.eventHandlers?.onConnectionError) { - this.options.eventHandlers.onConnectionError(error, remainingAttempts); + this.options.eventHandlers.onConnectionError( + error, + this.options.requestAttempts + ); } - return this._apiRequest(requestOptions, remainingAttempts); - } else if (error.response && error.response.status === 401 && remainingAttempts) { - // force refresh due to url related issue - await this.auth.getAccessToken(true); - //only retry once on refresh since there should be no reason for this token to be invalid - return this._apiRequest(requestOptions, 1); + return this._apiRequest(requestOptions); } else { - throw new RestError(error); + //convert to connection error + throw new NetworkError(error, _url); } } + + if (apiResponse.status === 401 && this.options.requestAttempts) { + // force refresh due to url related issue + await this.auth.getAccessToken(true); + //only retry once on refresh since there should be no reason for this token to be invalid + this.options.requestAttempts = 1; + return this._apiRequest(requestOptions); + } else if (!apiResponse.ok) { + throw new RestError(apiResponse, payloadResponse); + } + return payloadResponse; } } @@ -310,7 +304,7 @@ export default class Rest { * @param {object} options API request opptions */ function _checkPayload(options) { - if (!isPayload(options.data)) { + if (!isPayload(options.body)) { throw new Error(`${options.method} requests require a payload in options.data`); } } diff --git a/lib/util.js b/lib/util.js index 2c735e5..8155f7d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -25,6 +25,7 @@ export function isPayload(object) { * @param {string} code returned code from exception * @returns {boolean} true if a connection error */ +// TODO DEPRECATE IN SOAP export function isConnectionError(code) { return ( code && @@ -47,40 +48,55 @@ export function isConnectionError(code) { */ export class RestError extends Error { /** - * - * @param {EnhancedRestError} ex Error object + * @param {object} response api respone + * @param {object} responseBody rest body */ - constructor(ex) { + constructor(response, responseBody) { // Expired Error - if (ex.response?.data?.message) { - super(ex.response.data.message); - this.code = ex.response.data.errorcode || ex.code; + if (responseBody?.message) { + super(responseBody.message); + this.code = responseBody.errorcode; } // Unauthenticated - else if (ex.response?.data?.error_description) { - super(ex.response.data.error_description); - this.code = ex.response.data.error || ex.code; + else if (responseBody?.error_description) { + super(responseBody?.error_description); + this.code = responseBody?.error; } else { - super(ex.message); - this.code = ex.code; + super('Unhandled Exception. See details'); } - if (ex.endpoint) { - this.endpoint = ex.endpoint; - } - this.response = ex.response; + this.response = response; + this.json = responseBody; + this.endpoint = response.url; this.name = this.constructor.name; if (Error.captureStackTrace) { Error.captureStackTrace(this, RestError); } } } + /** - * @typedef {object} EnhancedSoapError - Error object + * CustomError type for handling Network based errors + * ie. errors not returning a 400-500 code + * + * @class NetworkError * @augments {Error} - * @property {object} response - - * @property {string} code - - * @property {string} endpoint - */ +export class NetworkError extends Error { + /** + * @param {Error} ex Error object + * @param {string} url url of request, if available + */ + constructor(ex, url) { + super(ex.message); + this.code = ex.code; + this.endpoint = url; + this.name = this.constructor.name; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, NetworkError); + } + } +} + /** * CustomError type for handling SOAP based errors * @@ -90,49 +106,54 @@ export class RestError extends Error { export class SOAPError extends Error { /** * - * @param {EnhancedSoapError} ex Error object - * @param {object} response api respone - * @param {object} soapBody soap body + * @param {object} response api response + * @param {object} responseBody response body (parsed) */ - constructor(ex, response, soapBody) { + constructor(response, responseBody) { // Content Error - if (soapBody && ['Error', 'Has Errors'].includes(soapBody.OverallStatus)) { + if (responseBody && ['Error', 'Has Errors'].includes(responseBody.OverallStatus)) { super('One or more errors in the Results'); - this.code = soapBody.OverallStatus; + this.code = responseBody.OverallStatus; } // Payload Error - else if (soapBody && soapBody['soap:Fault']) { - const fault = soapBody['soap:Fault']; + else if (responseBody && responseBody['soap:Fault']) { + const fault = responseBody['soap:Fault']; super(fault.faultstring); this.code = fault.faultcode; } - // Request Error + // General Request Error else if (response?.status > 299) { super('Error with SOAP Request'); this.code = response?.status; } // unsupported handler - else if (soapBody?.OverallStatus?.startsWith('Error:')) { - super(soapBody.OverallStatus.split('Error:')[1].trim()); + else if (responseBody?.OverallStatus?.startsWith('Error:')) { + super(responseBody.OverallStatus.split('Error:')[1].trim()); this.code = 'Error'; } - // Fallback Error - else if (ex) { - super(ex.message); - this.code = ex.code; - } // Fallback Unknown Error else { super('Unknown Error'); this.code = '520'; } this.response = response; - this.json = soapBody; + this.json = responseBody; + this.endpoint = response.url; this.name = this.constructor.name; if (Error.captureStackTrace) { Error.captureStackTrace(this, SOAPError); } } } +/** + * Method to check if the repsonse is JSON + * + * @param {object} apiResponse Fetch response before parsing body + * @returns {boolean} true if is simple Object + */ +export function isJSONResponse(apiResponse) { + return !!apiResponse.headers.get('content-type')?.includes('application/json'); +} + import axios from 'axios'; export const axiosInstance = axios.create(); diff --git a/package-lock.json b/package-lock.json index 21d558c..29bb0f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sfmc-sdk", - "version": "2.0.1", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sfmc-sdk", - "version": "2.0.1", + "version": "2.1.0", "license": "BSD-3-Clause", "dependencies": { "axios": "^1.7.2", @@ -25,6 +25,7 @@ "eslint-plugin-mocha": "10.4.3", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-unicorn": "54.0.0", + "fetch-mock": "10.1.1-alpha.1", "husky": "9.0.11", "mocha": "10.5.2", "prettier": "3.3.2", @@ -1503,6 +1504,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -2157,6 +2167,27 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-mock": { + "version": "10.1.1-alpha.1", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-10.1.1-alpha.1.tgz", + "integrity": "sha512-K9jiKH+8oDhEfEdwUuxgCiUPA3dIJqEzvEMHFAOFyh/gLq89HjGGAC4Vz/Z946svDNi5tPhKKw/JYF1pCyqbaA==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "dequal": "^2.0.3", + "globrex": "^0.1.2", + "is-subset": "^0.1.1", + "regexparam": "^3.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependenciesMeta": { + "node-fetch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2410,6 +2441,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2812,6 +2849,12 @@ "node": ">=8" } }, + "node_modules/is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==", + "dev": true + }, "node_modules/is-typed-array": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", @@ -4209,6 +4252,15 @@ "regexp-tree": "bin/regexp-tree" } }, + "node_modules/regexparam": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", + "integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/regjsparser": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", @@ -6042,6 +6094,12 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -6495,6 +6553,19 @@ "reusify": "^1.0.4" } }, + "fetch-mock": { + "version": "10.1.1-alpha.1", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-10.1.1-alpha.1.tgz", + "integrity": "sha512-K9jiKH+8oDhEfEdwUuxgCiUPA3dIJqEzvEMHFAOFyh/gLq89HjGGAC4Vz/Z946svDNi5tPhKKw/JYF1pCyqbaA==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "dequal": "^2.0.3", + "globrex": "^0.1.2", + "is-subset": "^0.1.1", + "regexparam": "^3.0.0" + } + }, "file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -6670,6 +6741,12 @@ "slash": "^3.0.0" } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6937,6 +7014,12 @@ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==", + "dev": true + }, "is-typed-array": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", @@ -7945,6 +8028,12 @@ "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", "dev": true }, + "regexparam": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", + "integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==", + "dev": true + }, "regjsparser": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", diff --git a/package.json b/package.json index cf1e7ea..a257dd5 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "eslint-plugin-mocha": "10.4.3", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-unicorn": "54.0.0", + "fetch-mock": "10.1.1-alpha.1", "husky": "9.0.11", "mocha": "10.5.2", "prettier": "3.3.2", diff --git a/test.js b/test.js new file mode 100644 index 0000000..211f1d2 --- /dev/null +++ b/test.js @@ -0,0 +1,16 @@ +import SDK from 'sfmc-sdk'; +const sfmc = new SDK({ + client_id: '71fzp43cyb1aksgflc159my0', + client_secret: 'oDZE354QsMXzcFqKQlGgDiH1', + auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47mq.auth.marketingcloudapis.com/', + account_id: 7281698, +}); +try { + console.log( + 'OK', + + await sfmc.auth.getAccessToken() + ); +} catch (ex) { + console.error('FAIL', ex); +} diff --git a/test/auth.test.js b/test/auth.test.js index 3ade474..a9032b8 100644 --- a/test/auth.test.js +++ b/test/auth.test.js @@ -1,43 +1,43 @@ import { assert } from 'chai'; import SDK from '../lib/index.js'; -import { defaultSdk, mock } from './utils.js'; +import { defaultSdk, mock, makeResponse } from './utils.js'; import { success, unauthorized } from './resources/auth.js'; import { isConnectionError } from '../lib/util.js'; +import fetchMock from 'fetch-mock'; + describe('auth', function () { afterEach(function () { - mock.reset(); + fetchMock.reset(); }); it('should return an auth payload with token', async function () { //given - + fetchMock.mock(success.url, () => makeResponse(success)); //when - mock.onPost(success.url).reply(success.status, success.response); const auth = await defaultSdk().auth.getAccessToken(); // then assert.equal(auth.access_token, success.response.access_token); - assert.lengthOf(mock.history.post, 1); + assert.lengthOf(fetchMock.calls(), 1); return; }); it('should return an auth payload with previous token and one request', async function () { //given - - mock.onPost(success.url).reply(success.status, success.response); + fetchMock.mock(success.url, () => makeResponse(success)); // when const sdk = defaultSdk(); await sdk.auth.getAccessToken(); const auth = await sdk.auth.getAccessToken(); // then assert.equal(auth.access_token, success.response.access_token); - assert.lengthOf(mock.history.post, 1); + assert.lengthOf(fetchMock.calls(), 1); return; }); it('should return an unauthorized error', async function () { //given - mock.onPost(unauthorized.url).reply(unauthorized.status, unauthorized.response); + fetchMock.mock(unauthorized.url, () => makeResponse(unauthorized)); // when const auth = defaultSdk().auth.getAccessToken(); // then @@ -45,6 +45,7 @@ describe('auth', function () { await auth; assert.fail(); } catch (error) { + console.log('UNAUTH', error); assert.equal(error.response.status, 401); } @@ -163,33 +164,32 @@ describe('auth', function () { it('RETRY: should return an success, after a connection issues', async function () { //given - - //when - mock.onPost(success.url) - .timeoutOnce() - .onPost(success.url) - .reply(success.status, success.response); + fetchMock + .once(success.url, { throws: new TypeError('ECONNRESET') }, { name: 'ConnectionIssue' }) + .mock(success.url, () => makeResponse(success), { name: 'Success' }); const auth = await defaultSdk().auth.getAccessToken(); // then assert.equal(auth.access_token, success.response.access_token); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(), 2); return; }); it('FAILED RETRY: should return an error, after multiple connection issues', async function () { //given - + const errorToReturn = new TypeError('ECONNRESET'); + errorToReturn.code = 'ECONNRESET'; + errorToReturn.errno = '-4077'; + errorToReturn.syscall = 'read'; + fetchMock.mock(success.url, { throws: errorToReturn }, { name: 'ConnectionIssue' }); //when - mock.onPost(success.url).timeout(); - // when try { await defaultSdk().auth.getAccessToken(); //then assert.fail(); } catch (error) { - assert.isTrue(isConnectionError(error.code)); + assert.equal(error.code, 'ECONNRESET'); } - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(), 2); return; }); }); diff --git a/test/utils.js b/test/utils.js index 1d5eb25..99ba3d2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,8 +1,20 @@ +//deprecate import MockAdapter from 'axios-mock-adapter'; +//deprecate import { axiosInstance } from '../lib/util.js'; import SDK from '../lib/index.js'; +//deprecate export const mock = new MockAdapter(axiosInstance, { onNoMatch: 'throwException' }); +export const makeResponse = (mockData) => { + return new Response(JSON.stringify(mockData.response), { + status: mockData.status, + headers: { + 'Content-Type': 'application/json', + }, + }); +}; + export const defaultSdk = () => { return new SDK( { From 0775cadbeb7cb563768b97a2ec4216432205aa32 Mon Sep 17 00:00:00 2001 From: Douglas Midgley Date: Fri, 13 Sep 2024 22:33:39 +0200 Subject: [PATCH 2/2] #254 migrate to native fetch --- .vscode/launch.json | 26 - .vscode/settings.json | 2 +- @types/auth.d.ts | 2 - @types/auth.d.ts.map | 2 +- @types/rest.d.ts.map | 2 +- @types/soap.d.ts | 5 +- @types/soap.d.ts.map | 2 +- @types/util.d.ts | 1 - @types/util.d.ts.map | 2 +- eslint.config.js | 1 + lib/auth.js | 7 +- lib/rest.js | 34 +- lib/soap.js | 270 ++++----- lib/util.js | 3 - package-lock.json | 1254 +++++++++++++++++----------------------- package.json | 30 +- test.js | 16 - test/auth.test.js | 11 +- test/resources/soap.js | 2 + test/rest.test.js | 194 +++---- test/soap.test.js | 202 ++----- test/utils.js | 48 +- 22 files changed, 880 insertions(+), 1236 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 test.js diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 2ff9da3..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "0.1.0", - "configurations": [ - { - "name": "Run app.js", - "type": "node", - "program": "app.js", // Assuming this is your main app file. - "stopOnEntry": false, - "args": [], - "cwd": ".", - "runtimeExecutable": null, - "env": { "NODE_ENV": "production" } - }, - { - "name": "Run mocha", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", - "stopOnEntry": false, - "args": ["${workspaceFolder}/test/auth.test.js"], - "cwd": ".", - "runtimeExecutable": null, - "env": { "NODE_ENV": "production" } - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index d368ea9..59ac16a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,5 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "js/ts.implicitProjectConfig.checkJs": true, "javascript.validate.enable": true, - "eslint.useESLintClass": true + "prettier.enable": true } diff --git a/@types/auth.d.ts b/@types/auth.d.ts index bf26e8b..943ccc1 100644 --- a/@types/auth.d.ts +++ b/@types/auth.d.ts @@ -14,8 +14,6 @@ export default class Auth { * @param {object} options options for the SDK as a whole, for example collection of handler functions, or retry settings * @param {object} options.requestAttempts number of attempts which should be made before * @param {object} options.retryOnConnectionError if continued attempts should be made in case of connection issue - * - * */ constructor(authObject: { client_id: string; diff --git a/@types/auth.d.ts.map b/@types/auth.d.ts.map index 41baba9..b1b0dbb 100644 --- a/@types/auth.d.ts.map +++ b/@types/auth.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../lib/auth.js"],"names":[],"mappings":"AA0FA;;GAEG;AACH;IACI;;;;;;;;;;;;;;OAcG;IACH,wBAXG;QAA2B,SAAS,EAA5B,MAAM;QACa,aAAa,EAAhC,MAAM;QACa,UAAU,EAA7B,MAAM;QACa,QAAQ,EAA3B,MAAM;QACgB,KAAK,GAA3B,MAAM,EAAE;KAChB,WACA;QAAwB,eAAe,EAA/B,MAAM;QACU,sBAAsB,EAAtC,MAAM;KAGhB,EA6CA;IAFG;mBArDO,MAAM;uBACN,MAAM;oBACN,MAAM;kBACN,MAAM;gBACN,MAAM,EAAE;MAiDmD;IAClE;yBAhDO,MAAM;gCACN,MAAM;MA+CS;IAE1B;;;;;OAKG;IACH,8BAHW,OAAO,GACL,OAAO,CAAE,MAAM,CAAC,CA0B5B;IAED;;;;OAIG;IACH,sBAFa,MAAM,EAAE,CAIpB;CACJ"} \ No newline at end of file +{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../lib/auth.js"],"names":[],"mappings":"AA0FA;;GAEG;AACH;IACI;;;;;;;;;;;;OAYG;IACH,wBATG;QAA2B,SAAS,EAA5B,MAAM;QACa,aAAa,EAAhC,MAAM;QACa,UAAU,EAA7B,MAAM;QACa,QAAQ,EAA3B,MAAM;QACgB,KAAK,GAA3B,MAAM,EAAE;KAChB,WACA;QAAwB,eAAe,EAA/B,MAAM;QACU,sBAAsB,EAAtC,MAAM;KAChB,EA6CA;IAFG;mBAnDO,MAAM;uBACN,MAAM;oBACN,MAAM;kBACN,MAAM;gBACN,MAAM,EAAE;MA+CmD;IAClE;yBA9CO,MAAM;gCACN,MAAM;MA6CS;IAE1B;;;;;OAKG;IACH,8BAHW,OAAO,GACL,OAAO,CAAE,MAAM,CAAC,CA2B5B;IAED;;;;OAIG;IACH,sBAFa,MAAM,EAAE,CAIpB;CACJ"} \ No newline at end of file diff --git a/@types/rest.d.ts.map b/@types/rest.d.ts.map index bfe5598..d0b9d29 100644 --- a/@types/rest.d.ts.map +++ b/@types/rest.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../lib/rest.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,wBAHW,MAAM,WACN,MAAM,EAUhB;IAPG,UAAsB;IACtB,aAAsB;IACtB,4BAIC;IAGL;;;;;OAKG;IACH,SAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAO5B;IACD;;;;;;OAMG;IACH,mCAEC;IACD;;;;;;OAMG;IACH,qBAEC;IACD;;;;;;;OAOG;IACH,aALW,MAAM,aACN,MAAM,kBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAgF5B;IACD;;;;;;OAMG;IACH,wBAJW,MAAM,EAAE,oBACR,MAAM,GACJ,OAAO,OAAQ,CAgB3B;IACD;;;;;;;OAOG;IACH,UALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;;;OAOG;IACH,SALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;;;OAOG;IACH,WALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;OAKG;IACH,YAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAO5B;IACD;;;;;OAKG;IACH,4BAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA6D5B;CACJ"} \ No newline at end of file +{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../lib/rest.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,wBAHW,MAAM,WACN,MAAM,EAUhB;IAPG,UAAsB;IACtB,aAAsB;IACtB,4BAIC;IAGL;;;;;OAKG;IACH,SAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAO5B;IACD;;;;;;OAMG;IACH,mCAEC;IACD;;;;;;OAMG;IACH,qBAEC;IACD;;;;;;;OAOG;IACH,aALW,MAAM,aACN,MAAM,kBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAgF5B;IACD;;;;;;OAMG;IACH,wBAJW,MAAM,EAAE,oBACR,MAAM,GACJ,OAAO,OAAQ,CAgB3B;IACD;;;;;;;OAOG;IACH,UALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;;;OAOG;IACH,SALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;;;OAOG;IACH,WALW,MAAM,WACN,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAW5B;IACD;;;;;OAKG;IACH,YAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAO5B;IACD;;;;;OAKG;IACH,4BAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAkE5B;CACJ"} \ No newline at end of file diff --git a/@types/soap.d.ts b/@types/soap.d.ts index 3d05dd1..1d0a0a7 100644 --- a/@types/soap.d.ts +++ b/@types/soap.d.ts @@ -103,10 +103,9 @@ export default class Soap { /** * Method that makes the api request * - * @param {object} options configuration for the request including body - * @param {number} remainingAttempts number of times this request should be reattempted in case of error + * @param {object} requestOptions configuration for the request including body * @returns {Promise.} Results from the SOAP request in Object format */ - _apiRequest(options: object, remainingAttempts: number): Promise; + _apiRequest(requestOptions: object): Promise; } //# sourceMappingURL=soap.d.ts.map \ No newline at end of file diff --git a/@types/soap.d.ts.map b/@types/soap.d.ts.map index b3153d7..8e528d8 100644 --- a/@types/soap.d.ts.map +++ b/@types/soap.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"soap.d.ts","sourceRoot":"","sources":["../lib/soap.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,kBAHW,MAAM,WACN,MAAM,EAKhB;IAFG,UAAgB;IAChB,aAAsB;IAG1B;;;;;;;OAOG;IACH,eALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAsD5B;IACD;;;;;;;OAOG;IACH,mBALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAwB5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA0B5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA2B5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA0B5B;IACD;;;;;;;;;OASG;IACH,eAPW,MAAM,YACN,MAAM,gBACN,QAAQ,MAAM,UACd,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAiC5B;IACD;;;;;OAKG;IACH,eAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAuB5B;IACD;;;;;;OAMG;IACH,cAJW,MAAM,cACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAsB5B;IACD;;;;;;;OAOG;IACH,cALW,MAAM,UACN,MAAM,WACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAiC5B;IACD;;;;;;OAMG;IACH,gBAJW,MAAM,eACN,MAAM,EAAE,GACN,OAAO,CAAE,MAAM,CAAC,CA4B5B;IAED;;;;;;OAMG;IACH,qBAJW,MAAM,qBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAsE5B;CACJ"} \ No newline at end of file +{"version":3,"file":"soap.d.ts","sourceRoot":"","sources":["../lib/soap.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;;;;OAMG;IACH,kBAHW,MAAM,WACN,MAAM,EAKhB;IAFG,UAAgB;IAChB,aAAsB;IAG1B;;;;;;;OAOG;IACH,eALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAmD5B;IACD;;;;;;;OAOG;IACH,mBALW,MAAM,kBACN,MAAM,EAAE,sBACR,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAwB5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAuB5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAwB5B;IACD;;;;;;;OAOG;IACH,aALW,MAAM,cACN,MAAM,sBACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAuB5B;IACD;;;;;;;;;OASG;IACH,eAPW,MAAM,YACN,MAAM,gBACN,QAAQ,MAAM,UACd,MAAM,YACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA8B5B;IACD;;;;;OAKG;IACH,eAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAoB5B;IACD;;;;;;OAMG;IACH,cAJW,MAAM,cACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAmB5B;IACD;;;;;;;OAOG;IACH,cALW,MAAM,UACN,MAAM,WACN,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CA8B5B;IACD;;;;;;OAMG;IACH,gBAJW,MAAM,eACN,MAAM,EAAE,GACN,OAAO,CAAE,MAAM,CAAC,CAyB5B;IAED;;;;;OAKG;IACH,4BAHW,MAAM,GACJ,OAAO,CAAE,MAAM,CAAC,CAyE5B;CACJ"} \ No newline at end of file diff --git a/@types/util.d.ts b/@types/util.d.ts index 5b414fd..fabb39e 100644 --- a/@types/util.d.ts +++ b/@types/util.d.ts @@ -84,7 +84,6 @@ export class SOAPError extends Error { json: any; endpoint: any; } -export const axiosInstance: import("axios").AxiosInstance; /** * - Error object */ diff --git a/@types/util.d.ts.map b/@types/util.d.ts.map index cf93a43..7c2813f 100644 --- a/@types/util.d.ts.map +++ b/@types/util.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../lib/util.js"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,iCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AAEH,wCAJW,MAAM,GACJ,OAAO,CAQnB;AAkHD;;;;;GAKG;AACH,4CAHW,MAAM,GACJ,OAAO,CAInB;AAxHD;;;;;;GAMG;AACH;;;;;GAKG;AACH;IACI;;;OAGG;IACH,sBAHW,MAAM,gBACN,MAAM,EAsBhB;IAhBO,UAAkC;IAStC,cAAwB;IACxB,UAAwB;IACxB,cAA4B;CAMnC;AAED;;;;;;GAMG;AACH;IACI;;;OAGG;IACH,gBAHW,KAAK,OACL,MAAM,EAUhB;IANG,UAAmB;IACnB,iBAAmB;CAM1B;AAED;;;;;GAKG;AACH;IACI;;;;OAIG;IACH,sBAHW,MAAM,gBACN,MAAM,EAoChB;IA9BO,UAAsC;IAuB1C,cAAwB;IACxB,UAAwB;IACxB,cAA4B;CAMnC;AAYD,0DAA4C;;;;gCA1H/B,MAAM"} \ No newline at end of file +{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../lib/util.js"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,iCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;GAKG;AAEH,wCAJW,MAAM,GACJ,OAAO,CAQnB;AAkHD;;;;;GAKG;AACH,4CAHW,MAAM,GACJ,OAAO,CAInB;AAxHD;;;;;;GAMG;AACH;;;;;GAKG;AACH;IACI;;;OAGG;IACH,sBAHW,MAAM,gBACN,MAAM,EAsBhB;IAhBO,UAAkC;IAStC,cAAwB;IACxB,UAAwB;IACxB,cAA4B;CAMnC;AAED;;;;;;GAMG;AACH;IACI;;;OAGG;IACH,gBAHW,KAAK,OACL,MAAM,EAUhB;IANG,UAAmB;IACnB,iBAAmB;CAM1B;AAED;;;;;GAKG;AACH;IACI;;;;OAIG;IACH,sBAHW,MAAM,gBACN,MAAM,EAoChB;IA9BO,UAAsC;IAuB1C,cAAwB;IACxB,UAAwB;IACxB,cAA4B;CAMnC;;;;gCA9GY,MAAM"} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index 1e5d8a2..4732a21 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -45,6 +45,7 @@ export default [ 'jsdoc/require-param-type': 'error', 'jsdoc/tag-lines': ['warn', 'any', { startLines: 1 }], 'jsdoc/no-undefined-types': 'error', + 'no-console': 'error', }, settings: { diff --git a/lib/auth.js b/lib/auth.js index 2406e69..ba627f9 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,5 +1,5 @@ 'use strict'; -import { RestError, isJSONResponse, NetworkError } from './util.js'; +import { RestError, isJSONResponse } from './util.js'; const AVAIALABLE_SCOPES = [ 'accounts_read', 'accounts_write', @@ -104,8 +104,6 @@ export default class Auth { * @param {object} options options for the SDK as a whole, for example collection of handler functions, or retry settings * @param {object} options.requestAttempts number of attempts which should be made before * @param {object} options.retryOnConnectionError if continued attempts should be made in case of connection issue - * - * */ constructor(authObject, options) { if (!authObject) { @@ -161,9 +159,10 @@ export default class Auth { async getAccessToken(forceRefresh) { if (Boolean(forceRefresh) || _isExpired(this.authObject)) { try { - this.options.requestAttempts--; this.authObject = await _requestToken(this.authObject); } catch (error) { + // where there is an error, decrement before retrying + this.options.requestAttempts--; if (this.options.retryOnConnectionError && this.options.requestAttempts > 0) { if (this.options?.eventHandlers?.onConnectionError) { this.options.eventHandlers.onConnectionError( diff --git a/lib/rest.js b/lib/rest.js index 5a2232d..5078e0b 100644 --- a/lib/rest.js +++ b/lib/rest.js @@ -179,7 +179,7 @@ export default class Rest { body: JSON.stringify(payload), headers: headers, }; - _checkPayload(requestOptions); + _checkPayload(payload, requestOptions.method); return this._apiRequest(requestOptions); } /** @@ -197,7 +197,7 @@ export default class Rest { body: JSON.stringify(payload), headers: headers, }; - _checkPayload(requestOptions); + _checkPayload(payload, requestOptions.method); return this._apiRequest(requestOptions); } /** @@ -215,7 +215,7 @@ export default class Rest { body: JSON.stringify(payload), headers: headers, }; - _checkPayload(requestOptions); + _checkPayload(payload, requestOptions.method); return this._apiRequest(requestOptions); } /** @@ -240,22 +240,25 @@ export default class Rest { if (!isObject(requestOptions)) { throw new Error('requestOptions argument is required'); } - const _url = new URL(requestOptions.url, this.auth.authObject.rest_instance_url); + let _url; let apiResponse; let payloadResponse; + await this.auth.getAccessToken(); try { - await this.auth.getAccessToken(); + _url = new URL(requestOptions.url, this.auth.authObject.rest_instance_url); const _requestOptions = { method: requestOptions.method, - headers: Object.assign(requestOptions.headers, { + headers: Object.assign(requestOptions.headers || {}, { 'Content-Type': 'application/json', Authorization: `Bearer ` + this.auth.authObject.access_token, }), }; - this.options.requestAttempts--; + if (requestOptions?.body) { + _requestOptions.body = requestOptions.body; + } if (this.options?.eventHandlers?.logRequest) { - this.options.eventHandlers.logRequest(_requestOptions); + this.options.eventHandlers.logRequest(_url, _requestOptions); } apiResponse = await fetch(_url, _requestOptions); @@ -271,6 +274,8 @@ export default class Rest { }); } } catch (error) { + // where there is an error, decrement before retrying + this.options.requestAttempts--; if (this.options.retryOnConnectionError && this.options.requestAttempts > 0) { if (this.options?.eventHandlers?.onConnectionError) { this.options.eventHandlers.onConnectionError( @@ -278,7 +283,7 @@ export default class Rest { this.options.requestAttempts ); } - return this._apiRequest(requestOptions); + return await this._apiRequest(requestOptions); } else { //convert to connection error throw new NetworkError(error, _url); @@ -290,7 +295,7 @@ export default class Rest { await this.auth.getAccessToken(true); //only retry once on refresh since there should be no reason for this token to be invalid this.options.requestAttempts = 1; - return this._apiRequest(requestOptions); + return await this._apiRequest(requestOptions); } else if (!apiResponse.ok) { throw new RestError(apiResponse, payloadResponse); } @@ -301,10 +306,11 @@ export default class Rest { /** * method to check if the payload is plausible and throw error if not * - * @param {object} options API request opptions + * @param {object} payload the payload to be used (if available) + * @param {string} method the method being checked (for display in message) */ -function _checkPayload(options) { - if (!isPayload(options.body)) { - throw new Error(`${options.method} requests require a payload in options.data`); +function _checkPayload(payload, method) { + if (!isPayload(payload)) { + throw new Error(`${method} requests require a payload in options.data`); } } diff --git a/lib/soap.js b/lib/soap.js index d49e692..0ef0773 100644 --- a/lib/soap.js +++ b/lib/soap.js @@ -1,6 +1,6 @@ 'use strict'; import { XMLBuilder, XMLParser } from 'fast-xml-parser'; -import { isObject, isConnectionError, axiosInstance as axios, SOAPError } from './util.js'; +import { isObject, SOAPError, NetworkError } from './util.js'; /** * Class which handles SOAP endpoints @@ -70,14 +70,11 @@ export default class Soap { } } - return this._apiRequest( - { - action: 'Retrieve', - req: body, - key: 'RetrieveResponseMsg', - }, - this.options.requestAttempts - ); + return this._apiRequest({ + action: 'Retrieve', + req: body, + key: 'RetrieveResponseMsg', + }); } /** * Method used to retrieve all data via SOAP API @@ -134,14 +131,11 @@ export default class Soap { }, }; body.CreateRequest.Objects['@_xsi:type'] = type; - return this._apiRequest( - { - action: 'Create', - req: body, - key: 'CreateResponse', - }, - this.options.requestAttempts - ); + return this._apiRequest({ + action: 'Create', + req: body, + key: 'CreateResponse', + }); } /** * Method used to update data via SOAP API @@ -168,14 +162,11 @@ export default class Soap { }; body.UpdateRequest.Objects['@_xsi:type'] = type; - return this._apiRequest( - { - action: 'Update', - req: body, - key: 'UpdateResponse', - }, - this.options.requestAttempts - ); + return this._apiRequest({ + action: 'Update', + req: body, + key: 'UpdateResponse', + }); } /** * Method used to delete data via SOAP API @@ -201,14 +192,11 @@ export default class Soap { }, }; body.DeleteRequest.Objects['@_xsi:type'] = type; - return this._apiRequest( - { - action: 'Delete', - req: body, - key: 'DeleteResponse', - }, - this.options.requestAttempts - ); + return this._apiRequest({ + action: 'Delete', + req: body, + key: 'DeleteResponse', + }); } /** * Method used to schedule data via SOAP API @@ -243,14 +231,11 @@ export default class Soap { throw new TypeError('Interactions must be of Array or Object Type'); } validateOptions(options); - return this._apiRequest( - { - action: 'Schedule', - req: body, - key: 'ScheduleResponseMsg', - }, - this.options.requestAttempts - ); + return this._apiRequest({ + action: 'Schedule', + req: body, + key: 'ScheduleResponseMsg', + }); } /** * Method used to describe metadata via SOAP API @@ -262,23 +247,20 @@ export default class Soap { if (!type) { throw new Error('Describe requires a type'); } - return this._apiRequest( - { - action: 'Describe', - req: { - DefinitionRequestMsg: { - '@_xmlns': 'http://exacttarget.com/wsdl/partnerAPI', - DescribeRequests: { - ObjectDefinitionRequest: { - ObjectType: type, - }, + return this._apiRequest({ + action: 'Describe', + req: { + DefinitionRequestMsg: { + '@_xmlns': 'http://exacttarget.com/wsdl/partnerAPI', + DescribeRequests: { + ObjectDefinitionRequest: { + ObjectType: type, }, }, }, - key: 'DefinitionResponseMsg', }, - this.options.requestAttempts - ); + key: 'DefinitionResponseMsg', + }); } /** * Method used to execute data via SOAP API @@ -291,22 +273,19 @@ export default class Soap { if (!type) { throw new Error('Execute requires a type'); } - return this._apiRequest( - { - action: 'Execute', - req: { - ExecuteRequestMsg: { - '@_xmlns': 'http://exacttarget.com/wsdl/partnerAPI', - Requests: { - Name: type, - Parameters: properties, - }, + return this._apiRequest({ + action: 'Execute', + req: { + ExecuteRequestMsg: { + '@_xmlns': 'http://exacttarget.com/wsdl/partnerAPI', + Requests: { + Name: type, + Parameters: properties, }, }, - key: 'ExecuteResponseMsg', }, - this.options.requestAttempts - ); + key: 'ExecuteResponseMsg', + }); } /** * Method used to execute data via SOAP API @@ -329,24 +308,21 @@ export default class Soap { }, payload ); - return this._apiRequest( - { - action: 'Perform', - req: { - PerformRequestMsg: { - '@_xmlns': 'http://exacttarget.com/wsdl/partnerAPI', - Action: action, - Definitions: [ - { - Definition: definition, - }, - ], - }, + return this._apiRequest({ + action: 'Perform', + req: { + PerformRequestMsg: { + '@_xmlns': 'http://exacttarget.com/wsdl/partnerAPI', + Action: action, + Definitions: [ + { + Definition: definition, + }, + ], }, - key: 'PerformResponseMsg', }, - this.options.requestAttempts - ); + key: 'PerformResponseMsg', + }); } /** * Method used to configure data via SOAP API @@ -373,90 +349,89 @@ export default class Soap { configuration['@_xsi:type'] = type; } - return this._apiRequest( - { - action: 'Configure', - req: body, - key: 'ConfigureResponseMsg', - }, - this.options.requestAttempts - ); + return this._apiRequest({ + action: 'Configure', + req: body, + key: 'ConfigureResponseMsg', + }); } /** * Method that makes the api request * - * @param {object} options configuration for the request including body - * @param {number} remainingAttempts number of times this request should be reattempted in case of error + * @param {object} requestOptions configuration for the request including body * @returns {Promise.} Results from the SOAP request in Object format */ - async _apiRequest(options, remainingAttempts) { - if (!isObject(options)) { + async _apiRequest(requestOptions) { + if (!isObject(requestOptions)) { throw new TypeError('options argument is required'); } - + let _url; + let apiResponse; + let payloadResponse; await this.auth.getAccessToken(); - const requestOptions = { - method: 'POST', - baseURL: this.auth.authObject.soap_instance_url, - url: '/Service.asmx', - headers: { - SOAPAction: options.action, - 'Content-Type': 'text/xml', - }, - data: _buildEnvelope(options.req, this.auth.authObject.access_token), - }; - if (this.options?.eventHandlers?.logRequest) { - this.options.eventHandlers.logRequest(requestOptions); - } - let response; - remainingAttempts--; try { - response = await axios(requestOptions); + _url = new URL('/Service.asmx', this.auth.authObject.soap_instance_url); + const _requestOptions = { + method: 'POST', + headers: Object.assign(requestOptions.headers || {}, { + 'Content-Type': 'text/xml', + SOAPAction: requestOptions.action, + }), + body: _buildEnvelope(requestOptions.body, this.auth.authObject.access_token), + }; + if (this.options?.eventHandlers?.logRequest) { + this.options.eventHandlers.logRequest(_url, _requestOptions); + } + apiResponse = await fetch(_url, _requestOptions); + payloadResponse = await apiResponse.text(); } catch (error) { - if ( - this.options.retryOnConnectionError && - remainingAttempts > 0 && - isConnectionError(error.code) - ) { + // where there is an error, decrement before retrying + this.options.requestAttempts--; + if (this.options.retryOnConnectionError && this.options.requestAttempts > 0) { if (this.options?.eventHandlers?.onConnectionError) { - this.options.eventHandlers.onConnectionError(error, remainingAttempts); + this.options.eventHandlers.onConnectionError( + error, + this.options.requestAttempts + ); } - return this._apiRequest(options, remainingAttempts); - } else if (error.response) { - // if the response is received, then continue parsing and check for errors later - response = error.response; + return await this._apiRequest(requestOptions); } else { - // if no response, then throw - throw new SOAPError(error); + //convert to connection error + throw new NetworkError(error, _url); } } if (this.options?.eventHandlers?.logResponse) { this.options.eventHandlers.logResponse({ - data: response.data, - status: response.status, - statusText: response.statusText, - headers: response.headers, + data: payloadResponse, + status: apiResponse.status, + statusText: apiResponse.statusText, + headers: apiResponse.headers, }); } // handle rejected due to expired token try { // need to wait as it may error - return await _parseResponse(response, options.key); + return await _parseResponse(apiResponse, payloadResponse, requestOptions.key); } catch (error) { - if (error.message === 'Token Expired' && remainingAttempts) { + this.options.requestAttempts--; + if (error.message === 'Token Expired' && this.options.requestAttempts) { // force refresh due to url related issue await this.auth.getAccessToken(true); - // set to no more retries as after token refresh it should always work - return this._apiRequest(options, 1); - } else if (error instanceof SOAPError) { - //rethrow as is already handled/parsed - throw error; - } else { - //unknown error - throw new SOAPError(error, response, undefined); + //only retry once on refresh since there should be no reason for this token to be invalid + this.options.requestAttempts = 1; + return await this._apiRequest(requestOptions); } + throw error; + // else if (error instanceof SOAPError) { + // //rethrow as is already handled/parsed + // throw error; + // } else { + // throw new NetworkError(error, _url); + // //unknown error + // // throw new SOAPError(error, payloadResponse, undefined); + // } } } } @@ -464,15 +439,15 @@ export default class Soap { /** * Method to build the payload then conver to XML * - * @param {object} request Object form of the payload + * @param {object} requestBody Object form of the payload * @param {string} token access token for authentication * @returns {string} XML string payload */ -function _buildEnvelope(request, token) { +function _buildEnvelope(requestBody, token) { const jsonToXml = new XMLBuilder({ ignoreAttributes: false }); return jsonToXml.build({ Envelope: { - Body: request, + Body: requestBody, '@_xmlns': 'http://schemas.xmlsoap.org/soap/envelope/', '@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', Header: { @@ -521,13 +496,14 @@ function _parseFilter(filter) { /** * Method to parse the XML response * - * @param {object} response payload including whole SOAP response + * @param {object} apiResponse reponse object from API + * @param {object} payloadResponse payload including whole SOAP response * @param {string} key key of the expected response body * @returns {Promise.} Result of request in Object format */ -async function _parseResponse(response, key) { +async function _parseResponse(apiResponse, payloadResponse, key) { const xmlToJson = new XMLParser({ ignoreAttributes: true }); - const soapBody = xmlToJson.parse(response.data)?.['soap:Envelope']?.['soap:Body']; + const soapBody = xmlToJson.parse(payloadResponse)?.['soap:Envelope']?.['soap:Body']; if (soapBody?.[key]) { // These should always be run no matter the execution // Results should always be an array @@ -539,12 +515,12 @@ async function _parseResponse(response, key) { ['Error', 'Has Errors'].includes(soapBody[key].OverallStatus) || soapBody[key].OverallStatus?.startsWith('Error:') ) { - throw new SOAPError(undefined, response, soapBody[key]); + throw new SOAPError(apiResponse, soapBody[key]); } return soapBody[key]; } - // something else went wrong but payload parsed - throw new SOAPError(undefined, response, soapBody); + // something else went wrong (for example bad request) returning parsed xml + throw new SOAPError(apiResponse, soapBody || payloadResponse); } /** * Method checks options object for validity diff --git a/lib/util.js b/lib/util.js index 8155f7d..61c6d41 100644 --- a/lib/util.js +++ b/lib/util.js @@ -154,6 +154,3 @@ export class SOAPError extends Error { export function isJSONResponse(apiResponse) { return !!apiResponse.headers.get('content-type')?.includes('application/json'); } - -import axios from 'axios'; -export const axiosInstance = axios.create(); diff --git a/package-lock.json b/package-lock.json index 29bb0f5..00a5df6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,29 +9,27 @@ "version": "2.1.0", "license": "BSD-3-Clause", "dependencies": { - "axios": "^1.7.2", - "fast-xml-parser": "4.4.0", - "p-limit": "5.0.0" + "fast-xml-parser": "4.5.0", + "p-limit": "6.1.0" }, "devDependencies": { - "@types/node": "20.14.9", + "@types/node": "22.5.4", "assert": "2.1.0", - "axios-mock-adapter": "1.22.0", "c8": "10.1.2", "chai": "5.1.1", - "eslint": "9.6.0", + "eslint": "9.10.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-jsdoc": "48.5.0", - "eslint-plugin-mocha": "10.4.3", - "eslint-plugin-prettier": "5.1.3", - "eslint-plugin-unicorn": "54.0.0", - "fetch-mock": "10.1.1-alpha.1", - "husky": "9.0.11", - "mocha": "10.5.2", - "prettier": "3.3.2", + "eslint-plugin-jsdoc": "50.2.3", + "eslint-plugin-mocha": "10.5.0", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-unicorn": "55.0.0", + "fetch-mock": "11.1.3", + "husky": "9.1.6", + "mocha": "10.7.3", + "prettier": "3.3.3", "prettier-eslint": "16.3.0", - "sinon": "18.0.0", - "typescript": "5.5.2" + "sinon": "19.0.2", + "typescript": "5.6.2" }, "engines": { "node": ">=18", @@ -151,17 +149,14 @@ "dev": true }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", - "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz", + "integrity": "sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==", "dev": true, "dependencies": { - "@types/eslint": "^8.56.5", - "@types/estree": "^1.0.5", - "@typescript-eslint/types": "^7.2.0", "comment-parser": "1.4.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" }, "engines": { "node": ">=16" @@ -183,18 +178,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", - "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", - "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", "dev": true, "dependencies": { "@eslint/object-schema": "^2.1.4", @@ -270,9 +265,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", - "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -287,6 +282,18 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -539,47 +546,47 @@ "dev": true }, "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.2.tgz", + "integrity": "sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", "dev": true, "dependencies": { - "type-detect": "4.0.8" + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" } }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "engines": { + "node": ">=4" } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "node_modules/@types/eslint": { @@ -587,6 +594,8 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -596,6 +605,14 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/glob-to-regexp": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@types/glob-to-regexp/-/glob-to-regexp-0.4.4.tgz", + "integrity": "sha512-nDKoaKJYbnn1MZxUY0cA1bPmmgZbg0cTq7Rh13d0KWYNOiKbqoR+2d89SnRPszGh7ROzSwZ/GOjZ4jPbmmZ6Eg==", "dev": true }, "node_modules/@types/istanbul-lib-coverage": { @@ -608,15 +625,17 @@ "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/normalize-package-data": { @@ -655,19 +674,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", - "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/typescript-estree": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", @@ -807,9 +813,9 @@ } }, "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "engines": { "node": ">=6" @@ -898,11 +904,6 @@ "node": ">=12" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -918,29 +919,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios-mock-adapter": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz", - "integrity": "sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "is-buffer": "^2.0.5" - }, - "peerDependencies": { - "axios": ">= 0.17.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1347,17 +1325,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/comment-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", @@ -1419,12 +1386,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1496,14 +1463,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1514,9 +1473,9 @@ } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -1625,16 +1584,17 @@ } }, "node_modules/eslint": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", - "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/config-array": "^0.17.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.6.0", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", @@ -1643,7 +1603,7 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", + "eslint-scope": "^8.0.2", "eslint-visitor-keys": "^4.0.0", "espree": "^10.1.0", "esquery": "^1.5.0", @@ -1657,7 +1617,6 @@ "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", @@ -1673,6 +1632,14 @@ }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -1688,21 +1655,22 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", - "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", + "version": "50.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.2.3.tgz", + "integrity": "sha512-aNh/dz3wSkyo53y2KWDCrA8fDuXDMtMVflcbesd8AFPgcF8ugOv9mJxC7qKB95R96nzCB91iEwU7MMznh/7okQ==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.43.1", + "@es-joy/jsdoccomment": "~0.48.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.4", + "debug": "^4.3.6", "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "parse-imports": "^2.1.0", - "semver": "^7.6.2", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.0" + "synckit": "^0.9.1" }, "engines": { "node": ">=18" @@ -1723,36 +1691,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": { + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/synckit": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", - "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "node_modules/eslint-plugin-mocha": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.3.tgz", - "integrity": "sha512-emc4TVjq5Ht0/upR+psftuz6IBG5q279p+1dSRDeHf+NS9aaerBi3lXKo1SEzwC29hFIW21gO89CEWSvRsi8IQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", + "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", "dev": true, "dependencies": { "eslint-utils": "^3.0.0", @@ -1767,13 +1748,13 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -1797,18 +1778,18 @@ } }, "node_modules/eslint-plugin-unicorn": { - "version": "54.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-54.0.0.tgz", - "integrity": "sha512-XxYLRiYtAWiAjPv6z4JREby1TAE2byBC7wlh0V4vWDCpccOSU1KovWV//jqPXF6bq3WKxqX9rdjoRQ1EhdmNdQ==", + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz", + "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.24.5", "@eslint-community/eslint-utils": "^4.4.0", - "@eslint/eslintrc": "^3.0.2", "ci-info": "^4.0.0", "clean-regexp": "^1.0.0", "core-js-compat": "^3.37.0", "esquery": "^1.5.0", + "globals": "^15.7.0", "indent-string": "^4.0.0", "is-builtin-module": "^3.2.1", "jsesc": "^3.0.2", @@ -1829,6 +1810,18 @@ "eslint": ">=8.56.0" } }, + "node_modules/eslint-plugin-unicorn/node_modules/globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-unicorn/node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -1909,9 +1902,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2056,9 +2049,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -2138,9 +2131,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", "funding": [ { "type": "github", @@ -2168,14 +2161,14 @@ } }, "node_modules/fetch-mock": { - "version": "10.1.1-alpha.1", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-10.1.1-alpha.1.tgz", - "integrity": "sha512-K9jiKH+8oDhEfEdwUuxgCiUPA3dIJqEzvEMHFAOFyh/gLq89HjGGAC4Vz/Z946svDNi5tPhKKw/JYF1pCyqbaA==", + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-11.1.3.tgz", + "integrity": "sha512-ATh0dWgnVrUHiiXuvQm1Ry+ThWfSv1QQgqJTCtybrNxyUrFiSOaDKsNG29eyysp1SHeNP6Q+dH50+8VifN51Ig==", "dev": true, "dependencies": { - "debug": "^4.1.1", + "@types/glob-to-regexp": "^0.4.4", "dequal": "^2.0.3", - "globrex": "^0.1.2", + "glob-to-regexp": "^0.4.1", "is-subset": "^0.1.1", "regexparam": "^3.0.0" }, @@ -2253,25 +2246,6 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -2297,19 +2271,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2394,6 +2355,12 @@ "node": ">= 6" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -2441,12 +2408,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2592,12 +2553,12 @@ "dev": true }, "node_modules/husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", "dev": true, "bin": { - "husky": "bin.mjs" + "husky": "bin.js" }, "engines": { "node": ">=18" @@ -2699,29 +2660,6 @@ "node": ">=8" } }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, "node_modules/is-builtin-module": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", @@ -2976,9 +2914,9 @@ } }, "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", "dev": true, "engines": { "node": ">=12.0.0" @@ -3194,9 +3132,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { "braces": "^3.0.3", @@ -3206,25 +3144,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3256,31 +3175,31 @@ } }, "node_modules/mocha": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.5.2.tgz", - "integrity": "sha512-9btlN3JKCefPf+vKd/kcKz2SXxi12z6JswkGfaAF0saQvnsqLJk504ZmbxhSoENge08E9dsymozKgFMTl5PQsA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", "chokidar": "^3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -3363,9 +3282,9 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3374,12 +3293,6 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/mocha/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3425,15 +3338,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/mocha/node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -3447,9 +3351,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/natural-compare": { @@ -3459,25 +3363,16 @@ "dev": true }, "node_modules/nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", "dev": true, "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "node_modules/nise/node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" + "path-to-regexp": "^8.1.0" } }, "node_modules/node-releases": { @@ -3499,9 +3394,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -3586,11 +3481,11 @@ } }, "node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", + "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==", "dependencies": { - "yocto-queue": "^1.0.0" + "yocto-queue": "^1.1.1" }, "engines": { "node": ">=18" @@ -3648,9 +3543,9 @@ } }, "node_modules/parse-imports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.0.tgz", - "integrity": "sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", + "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", "dev": true, "dependencies": { "es-module-lexer": "^1.5.3", @@ -3728,10 +3623,13 @@ } }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", - "dev": true + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", + "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==", + "dev": true, + "engines": { + "node": ">=16" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -3797,9 +3695,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -4135,11 +4033,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4379,9 +4272,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -4391,9 +4284,9 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -4450,36 +4343,27 @@ } }, "node_modules/sinon": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", - "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/sinon" } }, - "node_modules/sinon/node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, "node_modules/sinon/node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, "engines": { "node": ">=0.3.1" @@ -4640,9 +4524,9 @@ } }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "dependencies": { "@pkgr/core": "^0.1.0", @@ -4746,9 +4630,9 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/type-check": { @@ -4782,9 +4666,9 @@ } }, "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4795,9 +4679,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "node_modules/update-browserslist-db": { @@ -4935,9 +4819,9 @@ } }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "node_modules/wrap-ansi": { @@ -5045,9 +4929,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "engines": { "node": ">=12.20" }, @@ -5148,17 +5032,14 @@ "dev": true }, "@es-joy/jsdoccomment": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", - "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz", + "integrity": "sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==", "dev": true, "requires": { - "@types/eslint": "^8.56.5", - "@types/estree": "^1.0.5", - "@typescript-eslint/types": "^7.2.0", "comment-parser": "1.4.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" } }, "@eslint-community/eslint-utils": { @@ -5171,15 +5052,15 @@ } }, "@eslint-community/regexpp": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", - "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true }, "@eslint/config-array": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", - "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", "dev": true, "requires": { "@eslint/object-schema": "^2.1.4", @@ -5230,9 +5111,9 @@ } }, "@eslint/js": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", - "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "dev": true }, "@eslint/object-schema": { @@ -5241,6 +5122,15 @@ "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true }, + "@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dev": true, + "requires": { + "levn": "^0.4.1" + } + }, "@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -5418,49 +5308,46 @@ "dev": true }, "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.2.tgz", + "integrity": "sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==", "dev": true, "requires": { - "@sinonjs/commons": "^3.0.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } + "@sinonjs/commons": "^3.0.1" } }, "@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0", + "@sinonjs/commons": "^3.0.1", "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" + }, + "dependencies": { + "type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true + } } }, "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "@types/eslint": { @@ -5468,6 +5355,8 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -5477,6 +5366,14 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "optional": true, + "peer": true + }, + "@types/glob-to-regexp": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@types/glob-to-regexp/-/glob-to-regexp-0.4.4.tgz", + "integrity": "sha512-nDKoaKJYbnn1MZxUY0cA1bPmmgZbg0cTq7Rh13d0KWYNOiKbqoR+2d89SnRPszGh7ROzSwZ/GOjZ4jPbmmZ6Eg==", "dev": true }, "@types/istanbul-lib-coverage": { @@ -5489,15 +5386,17 @@ "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", "dev": true, "requires": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "@types/normalize-package-data": { @@ -5524,12 +5423,6 @@ } } }, - "@typescript-eslint/types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", - "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", - "dev": true - }, "@typescript-eslint/typescript-estree": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", @@ -5622,9 +5515,9 @@ } }, "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true }, "ansi-regex": { @@ -5689,11 +5582,6 @@ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, "available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5703,26 +5591,6 @@ "possible-typed-array-names": "^1.0.0" } }, - "axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", - "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "axios-mock-adapter": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz", - "integrity": "sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "is-buffer": "^2.0.5" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5985,14 +5853,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "comment-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", @@ -6041,12 +5901,12 @@ } }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "decamelize": { @@ -6089,11 +5949,6 @@ "object-keys": "^1.1.1" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, "dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -6101,9 +5956,9 @@ "dev": true }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, "dir-glob": { @@ -6191,16 +6046,17 @@ "dev": true }, "eslint": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", - "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/config-array": "^0.17.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.6.0", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", @@ -6209,7 +6065,7 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", + "eslint-scope": "^8.0.2", "eslint-visitor-keys": "^4.0.0", "espree": "^10.1.0", "esquery": "^1.5.0", @@ -6223,7 +6079,6 @@ "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", @@ -6239,9 +6094,9 @@ "dev": true }, "eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -6327,21 +6182,22 @@ "requires": {} }, "eslint-plugin-jsdoc": { - "version": "48.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", - "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", + "version": "50.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.2.3.tgz", + "integrity": "sha512-aNh/dz3wSkyo53y2KWDCrA8fDuXDMtMVflcbesd8AFPgcF8ugOv9mJxC7qKB95R96nzCB91iEwU7MMznh/7okQ==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "~0.43.1", + "@es-joy/jsdoccomment": "~0.48.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.4", + "debug": "^4.3.6", "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "parse-imports": "^2.1.0", - "semver": "^7.6.2", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.0" + "synckit": "^0.9.1" }, "dependencies": { "escape-string-regexp": { @@ -6350,6 +6206,23 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, + "eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true + }, + "espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "requires": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + } + }, "spdx-expression-parse": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", @@ -6359,23 +6232,13 @@ "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } - }, - "synckit": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", - "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", - "dev": true, - "requires": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - } } } }, "eslint-plugin-mocha": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.3.tgz", - "integrity": "sha512-emc4TVjq5Ht0/upR+psftuz6IBG5q279p+1dSRDeHf+NS9aaerBi3lXKo1SEzwC29hFIW21gO89CEWSvRsi8IQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", + "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", "dev": true, "requires": { "eslint-utils": "^3.0.0", @@ -6384,28 +6247,28 @@ } }, "eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.9.1" } }, "eslint-plugin-unicorn": { - "version": "54.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-54.0.0.tgz", - "integrity": "sha512-XxYLRiYtAWiAjPv6z4JREby1TAE2byBC7wlh0V4vWDCpccOSU1KovWV//jqPXF6bq3WKxqX9rdjoRQ1EhdmNdQ==", + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz", + "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.24.5", "@eslint-community/eslint-utils": "^4.4.0", - "@eslint/eslintrc": "^3.0.2", "ci-info": "^4.0.0", "clean-regexp": "^1.0.0", "core-js-compat": "^3.37.0", "esquery": "^1.5.0", + "globals": "^15.7.0", "indent-string": "^4.0.0", "is-builtin-module": "^3.2.1", "jsesc": "^3.0.2", @@ -6417,6 +6280,12 @@ "strip-indent": "^3.0.0" }, "dependencies": { + "globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "dev": true + }, "jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -6470,9 +6339,9 @@ } }, "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -6537,9 +6406,9 @@ "dev": true }, "fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", "requires": { "strnum": "^1.0.5" } @@ -6554,14 +6423,14 @@ } }, "fetch-mock": { - "version": "10.1.1-alpha.1", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-10.1.1-alpha.1.tgz", - "integrity": "sha512-K9jiKH+8oDhEfEdwUuxgCiUPA3dIJqEzvEMHFAOFyh/gLq89HjGGAC4Vz/Z946svDNi5tPhKKw/JYF1pCyqbaA==", + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-11.1.3.tgz", + "integrity": "sha512-ATh0dWgnVrUHiiXuvQm1Ry+ThWfSv1QQgqJTCtybrNxyUrFiSOaDKsNG29eyysp1SHeNP6Q+dH50+8VifN51Ig==", "dev": true, "requires": { - "debug": "^4.1.1", + "@types/glob-to-regexp": "^0.4.4", "dequal": "^2.0.3", - "globrex": "^0.1.2", + "glob-to-regexp": "^0.4.1", "is-subset": "^0.1.1", "regexparam": "^3.0.0" } @@ -6616,11 +6485,6 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" - }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -6640,16 +6504,6 @@ "signal-exit": "^4.0.1" } }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6710,6 +6564,12 @@ "is-glob": "^4.0.1" } }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -6741,12 +6601,6 @@ "slash": "^3.0.0" } }, - "globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6852,9 +6706,9 @@ "dev": true }, "husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", "dev": true }, "ignore": { @@ -6926,12 +6780,6 @@ "binary-extensions": "^2.0.0" } }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, "is-builtin-module": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", @@ -7105,9 +6953,9 @@ } }, "jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", "dev": true }, "json-buffer": { @@ -7282,28 +7130,15 @@ "dev": true }, "micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -7326,31 +7161,31 @@ "dev": true }, "mocha": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.5.2.tgz", - "integrity": "sha512-9btlN3JKCefPf+vKd/kcKz2SXxi12z6JswkGfaAF0saQvnsqLJk504ZmbxhSoENge08E9dsymozKgFMTl5PQsA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", "chokidar": "^3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "dependencies": { "brace-expansion": { @@ -7401,20 +7236,14 @@ } }, "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7442,12 +7271,6 @@ "has-flag": "^4.0.0" } }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -7457,9 +7280,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "natural-compare": { @@ -7469,27 +7292,16 @@ "dev": true }, "nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", "dev": true, "requires": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } + "path-to-regexp": "^8.1.0" } }, "node-releases": { @@ -7511,9 +7323,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -7576,11 +7388,11 @@ } }, "p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", + "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==", "requires": { - "yocto-queue": "^1.0.0" + "yocto-queue": "^1.1.1" } }, "p-locate": { @@ -7619,9 +7431,9 @@ } }, "parse-imports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.0.tgz", - "integrity": "sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", + "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", "dev": true, "requires": { "es-module-lexer": "^1.5.3", @@ -7675,9 +7487,9 @@ } }, "path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", + "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==", "dev": true }, "path-type": { @@ -7723,9 +7535,9 @@ "dev": true }, "prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true }, "prettier-eslint": { @@ -7944,11 +7756,6 @@ } } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8111,15 +7918,15 @@ "dev": true }, "semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -8161,32 +7968,23 @@ "dev": true }, "sinon": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", - "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" }, "dependencies": { - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, "diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true } } @@ -8311,9 +8109,9 @@ "dev": true }, "synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "requires": { "@pkgr/core": "^0.1.0", @@ -8387,9 +8185,9 @@ "requires": {} }, "tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "type-check": { @@ -8414,15 +8212,15 @@ "dev": true }, "typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true }, "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "update-browserslist-db": { @@ -8516,9 +8314,9 @@ } }, "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "wrap-ansi": { @@ -8597,9 +8395,9 @@ } }, "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==" } } } diff --git a/package.json b/package.json index a257dd5..9aed881 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,8 @@ "author": "Doug Midgley ", "license": "BSD-3-Clause", "dependencies": { - "axios": "^1.7.2", - "fast-xml-parser": "4.4.0", - "p-limit": "5.0.0" + "fast-xml-parser": "4.5.0", + "p-limit": "6.1.0" }, "keywords": [ "fuel", @@ -43,24 +42,23 @@ "sdk" ], "devDependencies": { - "@types/node": "20.14.9", + "@types/node": "22.5.4", "assert": "2.1.0", - "axios-mock-adapter": "1.22.0", "c8": "10.1.2", "chai": "5.1.1", - "eslint": "9.6.0", + "eslint": "9.10.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-jsdoc": "48.5.0", - "eslint-plugin-mocha": "10.4.3", - "eslint-plugin-prettier": "5.1.3", - "eslint-plugin-unicorn": "54.0.0", - "fetch-mock": "10.1.1-alpha.1", - "husky": "9.0.11", - "mocha": "10.5.2", - "prettier": "3.3.2", + "eslint-plugin-jsdoc": "50.2.3", + "eslint-plugin-mocha": "10.5.0", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-unicorn": "55.0.0", + "fetch-mock": "11.1.3", + "husky": "9.1.6", + "mocha": "10.7.3", + "prettier": "3.3.3", "prettier-eslint": "16.3.0", - "sinon": "18.0.0", - "typescript": "5.5.2" + "sinon": "19.0.2", + "typescript": "5.6.2" }, "engines": { "npm": ">=9", diff --git a/test.js b/test.js deleted file mode 100644 index 211f1d2..0000000 --- a/test.js +++ /dev/null @@ -1,16 +0,0 @@ -import SDK from 'sfmc-sdk'; -const sfmc = new SDK({ - client_id: '71fzp43cyb1aksgflc159my0', - client_secret: 'oDZE354QsMXzcFqKQlGgDiH1', - auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47mq.auth.marketingcloudapis.com/', - account_id: 7281698, -}); -try { - console.log( - 'OK', - - await sfmc.auth.getAccessToken() - ); -} catch (ex) { - console.error('FAIL', ex); -} diff --git a/test/auth.test.js b/test/auth.test.js index a9032b8..f298a10 100644 --- a/test/auth.test.js +++ b/test/auth.test.js @@ -1,9 +1,7 @@ import { assert } from 'chai'; import SDK from '../lib/index.js'; -import { defaultSdk, mock, makeResponse } from './utils.js'; +import { defaultSdk, makeResponse, connectionError } from './utils.js'; import { success, unauthorized } from './resources/auth.js'; -import { isConnectionError } from '../lib/util.js'; - import fetchMock from 'fetch-mock'; describe('auth', function () { @@ -45,7 +43,6 @@ describe('auth', function () { await auth; assert.fail(); } catch (error) { - console.log('UNAUTH', error); assert.equal(error.response.status, 401); } @@ -176,11 +173,7 @@ describe('auth', function () { it('FAILED RETRY: should return an error, after multiple connection issues', async function () { //given - const errorToReturn = new TypeError('ECONNRESET'); - errorToReturn.code = 'ECONNRESET'; - errorToReturn.errno = '-4077'; - errorToReturn.syscall = 'read'; - fetchMock.mock(success.url, { throws: errorToReturn }, { name: 'ConnectionIssue' }); + fetchMock.mock(success.url, { throws: connectionError() }, { name: 'ConnectionIssue' }); //when try { await defaultSdk().auth.getAccessToken(); diff --git a/test/resources/soap.js b/test/resources/soap.js index 0f2c744..8c4278b 100644 --- a/test/resources/soap.js +++ b/test/resources/soap.js @@ -1,3 +1,5 @@ + +export const soapUrl = 'https://mct0l7nxfq2r988t1kxfy8sc47ma.soap.marketingcloudapis.com/Service.asmx' export const retrieveDataExtension = { response: 'RetrieveResponseurn:uuid:4a9be7d8-0644-4998-86c0-16f47a7ee415urn:uuid:18e38997-db05-4571-a4b3-24a7308ab6b1http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous2021-12-29T21:27:30Z2021-12-29T21:32:30ZOKf06cc504-68bf-4db8-9141-e420ed248e72DC91A414-6240-48BC-BB89-7F3910D41580', diff --git a/test/rest.test.js b/test/rest.test.js index 02c5356..90bd4ef 100644 --- a/test/rest.test.js +++ b/test/rest.test.js @@ -1,37 +1,39 @@ import { assert } from 'chai'; -import { defaultSdk, mock } from './utils.js'; +import { defaultSdk, makeResponse, connectionError } from './utils.js'; import SDK from '../lib/index.js'; import * as resources from './resources/rest.js'; import { success, expired, unauthorized } from './resources/auth.js'; -import { isConnectionError } from '../lib/util.js'; + +import fetchMock from 'fetch-mock'; describe('rest', function () { beforeEach(function () { - mock.onPost(success.url).reply(success.status, success.response); + fetchMock.post(success.url, () => makeResponse(success)); }); afterEach(function () { - mock.reset(); + fetchMock.reset(); }); it('GET Bulk: should return 6 journey items', async function () { //given const { journeysPage1, journeysPage2 } = resources; - mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response); - mock.onGet(journeysPage2.url).reply(journeysPage2.status, journeysPage2.response); + fetchMock.get(journeysPage1.url, () => makeResponse(journeysPage1)); + fetchMock.get(journeysPage2.url, () => makeResponse(journeysPage2)); + // when const payload = await defaultSdk().rest.getBulk('interaction/v1/interactions', 5); // then assert.lengthOf(payload.items, 6); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.get, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'get' }), 2); return; }); it('GET Bulk: should return 9 keyword items', async function () { //given const { keywordPage1 } = resources; - mock.onGet(keywordPage1.url).reply(keywordPage1.status, keywordPage1.response); + fetchMock.get(keywordPage1.url, () => makeResponse(keywordPage1)); // when const payload = await defaultSdk().rest.getBulk( '/legacy/v1/beta/mobile/keyword/?view=simple', @@ -39,30 +41,30 @@ describe('rest', function () { ); // then assert.lengthOf(payload.entry, 9); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.get, 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'get' }), 1); return; }); it('GET: should return 5 journey items', async function () { //given const { journeysPage1 } = resources; - mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response); + fetchMock.get(journeysPage1.url, () => makeResponse(journeysPage1)); // when const payload = await defaultSdk().rest.get( 'interaction/v1/interactions?$pageSize=5&$page=1' ); // then assert.lengthOf(payload.items, 5); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.get, 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'get' }), 1); return; }); it('GETCOLLECTION: should return 2 identical payloads', async function () { //given const { journeysPage1 } = resources; - mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response); + fetchMock.get(journeysPage1.url, () => makeResponse(journeysPage1)); // when const payloads = await defaultSdk().rest.getCollection([ 'interaction/v1/interactions?$pageSize=5&$page=1', @@ -71,15 +73,15 @@ describe('rest', function () { // then assert.lengthOf(payloads, 2); assert.deepEqual(payloads[0], payloads[1]); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.get, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'get' }), 2); return; }); it('POST: should create Event Definition', async function () { //given const { eventcreate } = resources; - mock.onPost(eventcreate.url).reply(eventcreate.status, eventcreate.response); + fetchMock.post(eventcreate.url, () => makeResponse(eventcreate)); // when const payload = await defaultSdk().rest.post('interaction/v1/EventDefinitions', { type: 'APIEvent', @@ -106,31 +108,28 @@ describe('rest', function () { }); // then assert.deepEqual(payload, eventcreate.response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('POST: should add an entry to a Data Extension', async function () { //given const { dataExtensionUpsert } = resources; - mock.onPost(dataExtensionUpsert.url).reply( - dataExtensionUpsert.status, - dataExtensionUpsert.response - ); + fetchMock.post(dataExtensionUpsert.url, () => makeResponse(dataExtensionUpsert)); // when const payload = await defaultSdk().rest.post('hub/v1/dataevents/key:key/rowset', [ { keys: { primaryKey: 1 }, values: { name: 'test' } }, ]); // then assert.deepEqual(payload, dataExtensionUpsert.response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('PUT: should update Event Definition', async function () { //given const { eventupdate } = resources; - mock.onPut(eventupdate.url).reply(eventupdate.status, eventupdate.response); + fetchMock.put(eventupdate.url, () => makeResponse(eventupdate)); // when const payload = await defaultSdk().rest.put( 'interaction/v1/eventdefinitions/6cb5ae19-8f0a-4f50-ad6e-0f9c9a32d5a5', @@ -146,15 +145,15 @@ describe('rest', function () { ); // then assert.deepEqual(payload, eventupdate.response); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.put, 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'put' }), 1); return; }); it('PATCH: should update Contact', async function () { //given const { contactPatch } = resources; - mock.onPatch(contactPatch.url).reply(contactPatch.status, contactPatch.response); + fetchMock.patch(contactPatch.url, () => makeResponse(contactPatch)); // when const payload = await defaultSdk().rest.patch('contacts/v1/contacts', { contactKey: '0039E00000DcvwjQAB', @@ -197,58 +196,57 @@ describe('rest', function () { }); // then assert.deepEqual(payload, contactPatch.response); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.patch, 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'patch' }), 1); return; }); it('DELETE: should delete Campaign', async function () { //given const { campaignDelete } = resources; - mock.onDelete(campaignDelete.url).reply(campaignDelete.status, campaignDelete.response); + fetchMock.delete(campaignDelete.url, () => makeResponse(campaignDelete)); // when const payload = await defaultSdk().rest.delete('hub/v1/campaigns/12656'); // then assert.deepEqual(payload, campaignDelete.response); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.delete, 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'delete' }), 1); return; }); it('should retry auth one time on first failure then work', async function () { //given - mock.reset(); // needed to avoid before hook being used - mock.onPost(expired.url) - .replyOnce(expired.status, expired.response) - .onPost(success.url) - .replyOnce(success.status, success.response); + fetchMock.reset(); // needed to avoid before hook being used + fetchMock + .postOnce(expired.url, () => makeResponse(expired), { name: 'expired' }) + .post(success.url, () => makeResponse(success), { name: 'success' }); const { campaignDelete } = resources; - mock.onDelete(campaignDelete.url).reply(campaignDelete.status, campaignDelete.response); + fetchMock.delete(campaignDelete.url, () => makeResponse(campaignDelete)); // when const payload = await defaultSdk().rest.delete('hub/v1/campaigns/12656'); // then assert.deepEqual(payload, campaignDelete.response); - assert.lengthOf(mock.history.post, 2); - assert.lengthOf(mock.history.delete, 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'delete' }), 1); return; }); it('should retry auth one time on first failure then fail', async function () { //given - mock.reset(); // needed to avoid before hook being used - mock.onPost(unauthorized.url).reply(unauthorized.status, unauthorized.response); + fetchMock.reset(); // needed to avoid before hook being used + fetchMock.post(unauthorized.url, () => makeResponse(unauthorized)); const { campaignDelete } = resources; - mock.onDelete(campaignDelete.url).reply(campaignDelete.status, campaignDelete.response); + fetchMock.delete(campaignDelete.url, () => makeResponse(campaignDelete)); try { // when await defaultSdk().rest.delete('hub/v1/campaigns/12656'); assert.fail(); } catch (error) { - assert.deepEqual(error.response.data, unauthorized.response); - assert.lengthOf(mock.history.post, 2); - assert.lengthOf(mock.history.delete, 0); + assert.deepEqual(error.json, unauthorized.response); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'delete' }), 0); } return; }); @@ -256,80 +254,60 @@ describe('rest', function () { it('should fail to delete campaign', async function () { //given const { campaignFailed } = resources; - mock.onDelete(campaignFailed.url).reply(campaignFailed.status, campaignFailed.response); + fetchMock.post(unauthorized.url, () => makeResponse(unauthorized), { + name: 'Unauthorized', + }); + fetchMock.delete(campaignFailed.url, () => makeResponse(campaignFailed)); // when try { await defaultSdk().rest.delete('hub/v1/campaigns/abc'); assert.fail(); // then } catch (error) { - // console.log(ex.response); assert.equal(error.response.status, campaignFailed.status); - assert.deepEqual(error.response.data, campaignFailed.response); + assert.deepEqual(error.json, campaignFailed.response); } - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.delete, 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'delete' }), 1); return; }); it('RETRY: should return 5 journey items, after a connection error', async function () { //given const { journeysPage1 } = resources; - mock.onGet(journeysPage1.url) - .timeoutOnce() - .onGet(journeysPage1.url) - .reply(journeysPage1.status, journeysPage1.response); + fetchMock + .getOnce(journeysPage1.url, { throws: connectionError() }, { name: 'ConnectionIssue' }) + .get(journeysPage1.url, () => makeResponse(journeysPage1)); // when const payload = await defaultSdk().rest.get( 'interaction/v1/interactions?$pageSize=5&$page=1' ); // then assert.lengthOf(payload.items, 5); - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.get, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'get' }), 2); return; }); - it('FAILED RETRY: should return error, after 2 connection timeout errors', async function () { + it('FAILED RETRY: should return error, after 2 connection Connection Issue errors', async function () { //given const { journeysPage1 } = resources; - mock.onGet(journeysPage1.url).timeout(); - // when - try { - await defaultSdk().rest.get('interaction/v1/interactions?$pageSize=5&$page=1'); - assert.fail(); - } catch (error) { - // then - assert.isTrue(isConnectionError(error.code)); - } - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.get, 2); - - return; - }); - - it('FAILED RETRY: should return error, after 2 ECONNRESET errors', async function () { - //given - const { journeysPage1 } = resources; - - mock.onGet(journeysPage1.url).reply(() => { - // eslint-disable-next-line unicorn/error-message - const connectionError = new Error(); - // @ts-expect-error - connectionError.code = 'ECONNRESET'; - throw connectionError; - }); + fetchMock.get( + journeysPage1.url, + { throws: connectionError() }, + { name: 'ConnectionIssue' } + ); // when try { await defaultSdk().rest.get('interaction/v1/interactions?$pageSize=5&$page=1'); assert.fail(); } catch (error) { // then - assert.isTrue(isConnectionError(error.code)); + assert.equal(error.code, 'ECONNRESET'); } - assert.lengthOf(mock.history.post, 1); - assert.lengthOf(mock.history.get, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 1); + assert.lengthOf(fetchMock.calls(undefined, { method: 'get' }), 2); return; }); @@ -337,12 +315,14 @@ describe('rest', function () { it('LogRequest & Response: should run middleware for logging ', async function () { //given const { journeysPage1 } = resources; - mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response); + fetchMock.get(journeysPage1.url, () => makeResponse(journeysPage1)); // when /** @type {object} */ - let expectedRequest; + let actualRequest; + /** @type {string} */ + let actualUrl; /** @type {object} */ - let expectedResponse; + let actualResponse; const sdk = new SDK( { client_id: 'XXXXX', @@ -352,12 +332,13 @@ describe('rest', function () { }, { eventHandlers: { - logRequest: (requestObject) => { - expectedRequest = requestObject; + logRequest: (requestUrl, requestObject) => { + actualUrl = requestUrl; + actualRequest = requestObject; }, logResponse: (responseObject) => { - expectedResponse = responseObject; + actualResponse = responseObject; }, onConnectionError: () => { return; @@ -370,19 +351,20 @@ describe('rest', function () { // when await sdk.rest.get('interaction/v1/interactions?$pageSize=5&$page=1'); // then - assert.deepEqual( - { - method: 'GET', - url: 'interaction/v1/interactions?$pageSize=5&$page=1', - baseURL: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.rest.marketingcloudapis.com/', - headers: { - Authorization: 'Bearer TESTTOKEN', - }, - }, - expectedRequest + assert.equal( + // @ts-ignore + actualUrl, + 'https://mct0l7nxfq2r988t1kxfy8sc47ma.rest.marketingcloudapis.com/interaction/v1/interactions?$pageSize=5&$page=1' ); - assert.equal(200, expectedResponse.status); - assert.equal(5, expectedResponse.data.items.length); + assert.deepEqual(actualRequest, { + method: 'GET', + headers: { + Authorization: 'Bearer TESTTOKEN', + 'Content-Type': 'application/json', + }, + }); + assert.equal(actualResponse.status, 200); + assert.equal(actualResponse.data.items.length, 5); return; }); }); diff --git a/test/soap.test.js b/test/soap.test.js index 6e37a14..bc8b278 100644 --- a/test/soap.test.js +++ b/test/soap.test.js @@ -1,47 +1,36 @@ import { assert } from 'chai'; -import { defaultSdk, mock } from './utils.js'; +import { defaultSdk, makeResponse, connectionError } from './utils.js'; import * as resources from './resources/soap.js'; import { success } from './resources/auth.js'; -import { XMLValidator } from 'fast-xml-parser'; -import { isConnectionError } from '../lib/util.js'; +import fetchMock from 'fetch-mock'; -const addHandler = (metadata) => { - mock.onPost( - '/Service.asmx', +const addHandler = (resourceName, handlerType = 'post') => { + const metadata = resources[resourceName]; + fetchMock[handlerType]( { - asymmetricMatch: XMLValidator.validate, - }, - { - /** - * matcher based on headers - * - * @param {object} headers which should be passed for matching - * @returns {boolean} if value matches - */ - asymmetricMatch(headers) { - return ( - headers['SOAPAction'] === metadata.action && - headers['Content-Type'] === 'text/xml' - ); + url: resources.soapUrl, + headers: { + SOAPAction: metadata.action, + 'Content-Type': 'text/xml', }, - } - ).reply(metadata.status, metadata.response, { - 'Content-Type': 'application/soap+xml; charset=utf-8', - }); + }, + (_, options) => makeResponse(metadata, options), + { name: resourceName } + ); }; describe('soap', function () { beforeEach(function () { - mock.onPost(success.url).reply(success.status, success.response); + fetchMock.post(success.url, () => makeResponse(success)); }); afterEach(function () { - mock.reset(); + fetchMock.reset(); }); it('retrieve: should return 1 data extension', async function () { //given - addHandler(resources.retrieveDataExtension); + addHandler('retrieveDataExtension'); // when const payload = await defaultSdk().soap.retrieve('DataExtension', ['CustomerKey'], { filter: { @@ -62,66 +51,14 @@ describe('soap', function () { // then assert.lengthOf(payload.Results, 1); assert.deepEqual(payload, resources.retrieveDataExtension.parsed); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('retrieveBulk: should return 2 data extensions', async function () { //given - mock.onPost( - '/Service.asmx', - { - asymmetricMatch: XMLValidator.validate, - }, - { - /** - * matcher based on headers - * - * @param {object} headers which should be passed for matching - * @returns {boolean} if value matches - */ - asymmetricMatch(headers) { - return ( - headers['SOAPAction'] === resources.retrieveBulkDataExtension.action && - headers['Content-Type'] === 'text/xml' - ); - }, - } - ) - .replyOnce( - resources.retrieveBulkDataExtension.status, - resources.retrieveBulkDataExtension.response, - { - 'Content-Type': 'application/soap+xml; charset=utf-8', - } - ) - .onPost( - '/Service.asmx', - { - asymmetricMatch: XMLValidator.validate, - }, - { - /** - * matcher based on headers - * - * @param {object} headers which should be passed for matching - * @returns {boolean} if value matches - */ - asymmetricMatch(headers) { - return ( - headers['SOAPAction'] === resources.retrieveDataExtension.action && - headers['Content-Type'] === 'text/xml' - ); - }, - } - ) - .replyOnce( - resources.retrieveDataExtension.status, - resources.retrieveDataExtension.response, - { - 'Content-Type': 'application/soap+xml; charset=utf-8', - } - ); + addHandler('retrieveBulkDataExtension', 'postOnce'); + addHandler('retrieveDataExtension', 'postOnce'); // when const payload = await defaultSdk().soap.retrieveBulk('DataExtension', ['CustomerKey'], { filter: { @@ -133,13 +70,13 @@ describe('soap', function () { }); // then assert.lengthOf(payload.Results, 2); - assert.lengthOf(mock.history.post, 3); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 3); return; }); it('failed: should fail to create 1 subscriber', async function () { //given - addHandler(resources.subscriberFailed); + addHandler('subscriberFailed'); // when try { await defaultSdk().soap.create( @@ -158,7 +95,7 @@ describe('soap', function () { assert.fail(); } catch (error) { assert.deepEqual(error.json, resources.subscriberFailed.parsed); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); } return; @@ -166,7 +103,7 @@ describe('soap', function () { it('create: should create 1 subscriber', async function () { //given - addHandler(resources.subscriberCreated); + addHandler('subscriberCreated'); // when const response = await defaultSdk().soap.create( @@ -183,14 +120,14 @@ describe('soap', function () { ); // then assert.deepEqual(response, resources.subscriberCreated.parsed); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('update: should update 1 subscriber', async function () { //given - addHandler(resources.subscriberUpdated); + addHandler('subscriberUpdated'); // when const response = await defaultSdk().soap.update( @@ -207,14 +144,14 @@ describe('soap', function () { ); // then assert.deepEqual(response, resources.subscriberUpdated.parsed); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('expired: should return an error of expired token', async function () { //given - addHandler(resources.expiredToken); + addHandler('expiredToken'); // when try { await defaultSdk().soap.create('Subscriber', { @@ -224,7 +161,7 @@ describe('soap', function () { } catch (error) { // then assert.equal(error.message, 'Token Expired'); - assert.lengthOf(mock.history.post, 4); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 4); return; } @@ -233,7 +170,7 @@ describe('soap', function () { it('no handler: should return an error stating the object type is not supported', async function () { //given - addHandler(resources.noObjectHandlerFound); + addHandler('noObjectHandlerFound'); // when try { await defaultSdk().soap.retrieve('DeliveryProfile', ['CustomerKey']); @@ -243,7 +180,7 @@ describe('soap', function () { error.message, 'Unable to find a handler for object type: DeliveryProfile. Object types are case-sensitive, check spelling.' ); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; } @@ -252,7 +189,7 @@ describe('soap', function () { it('bad Request: should return an error of bad request', async function () { //given - addHandler(resources.badRequest); + addHandler('badRequest'); // when try { await defaultSdk().soap.create('Subscriber', { @@ -260,8 +197,8 @@ describe('soap', function () { }); } catch (error) { // then - assert.equal(error.response.data, 'Bad Request'); - assert.lengthOf(mock.history.post, 2); + assert.equal(error.json, 'Bad Request'); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; } assert.fail(); @@ -269,31 +206,31 @@ describe('soap', function () { it('Delete: should delete a subscriber', async function () { //given - addHandler(resources.subscriberDeleted); + addHandler('subscriberDeleted'); // when const response = await defaultSdk().soap.delete('Subscriber', { SubscriberKey: '1234512345', }); // then assert.deepEqual(resources.subscriberDeleted.parsed, response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('Describe: should describe the subscriber type', async function () { //given - addHandler(resources.subscriberDescribed); + addHandler('subscriberDescribed'); // when const response = await defaultSdk().soap.describe('Subscriber'); // then assert.deepEqual(resources.subscriberDescribed.parsed, response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('Execute: should unsubscribe subscriber', async function () { //given - addHandler(resources.subscribeUnsub); + addHandler('subscribeUnsub'); // when const response = await defaultSdk().soap.execute('LogUnsubEvent', { Name: 'SubscriberKey', @@ -301,26 +238,26 @@ describe('soap', function () { }); // then assert.deepEqual(resources.subscribeUnsub.parsed, response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('Perform: should unsubscribe subscriber', async function () { //given - addHandler(resources.queryPerform); + addHandler('queryPerform'); // when const response = await defaultSdk().soap.perform('QueryDefinition', 'Start', { ObjectID: 'a077064d-bcc9-4a8f-8bef-4df950193824', }); // then assert.deepEqual(resources.queryPerform.parsed, response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('Configure: should assign a business unit to a user', async function () { //given - addHandler(resources.accountUserConfigure); + addHandler('accountUserConfigure'); // when const response = await defaultSdk().soap.configure('AccountUser', [ { @@ -342,13 +279,13 @@ describe('soap', function () { ]); // then assert.deepEqual(resources.accountUserConfigure.parsed, response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('Schedule: should schedule an Automation', async function () { //given - addHandler(resources.automationSchedule); + addHandler('automationSchedule'); // when const response = await defaultSdk().soap.schedule( 'Automation', @@ -368,42 +305,18 @@ describe('soap', function () { ); // then assert.deepEqual(resources.automationSchedule.parsed, response); - assert.lengthOf(mock.history.post, 2); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 2); return; }); it('RETRY: should return 1 data extension, after a connection error', async function () { //given - - mock.onPost('/Service.asmx') - .timeoutOnce() - .onPost( - '/Service.asmx', - { - asymmetricMatch: XMLValidator.validate, - }, - { - /** - * matcher based on headers - * - * @param {object} headers which should be passed for matching - * @returns {boolean} if value matches - */ - asymmetricMatch(headers) { - return ( - headers['SOAPAction'] === resources.retrieveDataExtension.action && - headers['Content-Type'] === 'text/xml' - ); - }, - } - ) - .reply( - resources.retrieveDataExtension.status, - resources.retrieveDataExtension.response, - { - 'Content-Type': 'application/soap+xml; charset=utf-8', - } - ); + fetchMock.postOnce( + resources.soapUrl, + { throws: connectionError() }, + { name: 'ConnectionIssue' } + ); + addHandler('retrieveDataExtension'); // when const payload = await defaultSdk().soap.retrieve('DataExtension', ['CustomerKey'], { filter: { @@ -424,14 +337,17 @@ describe('soap', function () { // then assert.lengthOf(payload.Results, 1); assert.deepEqual(payload, resources.retrieveDataExtension.parsed); - assert.lengthOf(mock.history.post, 3); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 3); return; }); it('FAILED RETRY: should return error, after multiple connection error', async function () { //given - - mock.onPost('/Service.asmx').timeout(); + fetchMock.post( + resources.soapUrl, + { throws: connectionError() }, + { name: 'ConnectionIssue' } + ); // when try { await defaultSdk().soap.retrieve('DataExtension', ['CustomerKey'], { @@ -453,9 +369,9 @@ describe('soap', function () { assert.fail(); } catch (error) { // then - assert.isTrue(isConnectionError(error.code)); + assert.equal(error.code, 'ECONNRESET'); } - assert.lengthOf(mock.history.post, 3); + assert.lengthOf(fetchMock.calls(undefined, { method: 'post' }), 3); return; }); }); diff --git a/test/utils.js b/test/utils.js index 99ba3d2..242751b 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,20 +1,42 @@ //deprecate -import MockAdapter from 'axios-mock-adapter'; -//deprecate -import { axiosInstance } from '../lib/util.js'; import SDK from '../lib/index.js'; -//deprecate -export const mock = new MockAdapter(axiosInstance, { onNoMatch: 'throwException' }); +import { XMLValidator } from 'fast-xml-parser'; +import { badRequest } from './resources/soap.js'; -export const makeResponse = (mockData) => { - return new Response(JSON.stringify(mockData.response), { - status: mockData.status, - headers: { - 'Content-Type': 'application/json', - }, - }); +export const makeResponse = (mockData, options) => { + if (options?.headers['Content-Type'] == 'text/xml') { + const soapHeaders = { + 'Content-Type': 'application/soap+xml; charset=utf-8', + }; + return XMLValidator.validate(options?.body) + ? new Response(mockData.response, { + status: mockData.status, + headers: soapHeaders, + }) + : new Response(badRequest.response, { + status: badRequest.status, + headers: soapHeaders, + statusText: badRequest.response, + }); + } else { + return new Response(JSON.stringify(mockData.response), { + status: mockData.status, + headers: { + 'Content-Type': 'application/json', + }, + }); + } +}; +export const connectionError = () => { + const errorToReturn = new TypeError('ECONNRESET'); + // @ts-ignore + errorToReturn.code = 'ECONNRESET'; + // @ts-ignore + errorToReturn.errno = '-4077'; + // @ts-ignore + errorToReturn.syscall = 'read'; + return errorToReturn; }; - export const defaultSdk = () => { return new SDK( {