From e75ba575a21eeabc2cb1c2b571137ed18df2451c Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Mon, 10 Feb 2025 18:06:27 +0000 Subject: [PATCH 01/20] chore: commit base structure for axios interceptor --- .gitignore | 3 +- .../package.json | 45 + .../src/index.ts | 1 + .../src/lib/axios-interceptor.ts | 7 + .../tsconfig.json | 26 + .../tsup.config.ts | 15 + .../vitest.config.mts | 15 + pnpm-lock.yaml | 787 +++++++++++++++++- pnpm-workspace.yaml | 2 +- 9 files changed, 898 insertions(+), 3 deletions(-) create mode 100644 packages/axios-interceptor-large-response/package.json create mode 100644 packages/axios-interceptor-large-response/src/index.ts create mode 100644 packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts create mode 100644 packages/axios-interceptor-large-response/tsconfig.json create mode 100644 packages/axios-interceptor-large-response/tsup.config.ts create mode 100644 packages/axios-interceptor-large-response/vitest.config.mts diff --git a/.gitignore b/.gitignore index 8879c2f..98293ef 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ yarn-error.log* .DS_Store *.pem test-report.xml -**/lib/ +**/large-response-middleware/lib/ +**/dist/ stats.html *.tgz \ No newline at end of file diff --git a/packages/axios-interceptor-large-response/package.json b/packages/axios-interceptor-large-response/package.json new file mode 100644 index 0000000..5e8984b --- /dev/null +++ b/packages/axios-interceptor-large-response/package.json @@ -0,0 +1,45 @@ +{ + "name": "@epilot/axios-interceptor-large-response", + "version": "0.0.1", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "module": "dist/index.mjs", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/epilot-dev/aws-lambda-utility-middlewares.git#main" + }, + "description": "Axios plugin to intercept large responses", + "keywords": ["large-response-middleware", "interceptor", "axios", "large-response", "plugin"], + "author": "Alexandre Marques", + "contributors": ["Alexandre Marques(https://github.com/alexmarqs)"], + "scripts": { + "build": "tsup", + "test": "vitest", + "lint": "biome check --write .", + "prepublishOnly": "pnpm lint && pnpm build", + "typecheck": "tsc --noEmit", + "local:publish": "yalc publish" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "module": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "files": ["dist/**"], + "devDependencies": { + "typescript": "^5.3.3", + "vitest": "3.0.5", + "axios": "^1.6.2", + "tsup": "6.7.0" + }, + "peerDependencies": { + "axios": ">=0.25.0" + }, + "engines": { + "node": ">=14" + } +} diff --git a/packages/axios-interceptor-large-response/src/index.ts b/packages/axios-interceptor-large-response/src/index.ts new file mode 100644 index 0000000..1b55a03 --- /dev/null +++ b/packages/axios-interceptor-large-response/src/index.ts @@ -0,0 +1 @@ +export { axiosInterceptor } from './lib/axios-interceptor'; diff --git a/packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts b/packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts new file mode 100644 index 0000000..c038f5c --- /dev/null +++ b/packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts @@ -0,0 +1,7 @@ +import { AxiosInstance } from "axios"; + +export const axiosInterceptor = (axiosInstance: AxiosInstance) => { + axiosInstance.interceptors.response.use((response) => { + return response; + }); +}; diff --git a/packages/axios-interceptor-large-response/tsconfig.json b/packages/axios-interceptor-large-response/tsconfig.json new file mode 100644 index 0000000..71670bb --- /dev/null +++ b/packages/axios-interceptor-large-response/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "outDir": "dist", + "target": "es2022", + "module": "esnext", + "moduleResolution": "node", + "lib": ["ESNext"], + "sourceMap": true, + "types": ["vitest/globals"], + "strict": true, + "strictFunctionTypes": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipLibCheck": true, + "declaration": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/axios-interceptor-large-response/tsup.config.ts b/packages/axios-interceptor-large-response/tsup.config.ts new file mode 100644 index 0000000..1d55e78 --- /dev/null +++ b/packages/axios-interceptor-large-response/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig([ + { + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + target: 'node14', + splitting: false, + sourcemap: false, + clean: true, + minify: true, + dts: true, + outDir: 'dist', + }, +]); diff --git a/packages/axios-interceptor-large-response/vitest.config.mts b/packages/axios-interceptor-large-response/vitest.config.mts new file mode 100644 index 0000000..8e84103 --- /dev/null +++ b/packages/axios-interceptor-large-response/vitest.config.mts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['**/*.{test,spec}.ts?(x)'], + exclude: ['**/node_modules/**'], + silent: true, + watch: false, + poolOptions: { + threads: { + singleThread: true, + }, + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50dec49..743ec58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,21 @@ importers: specifier: ^5.3.3 version: 5.3.3 + packages/axios-interceptor-large-response: + devDependencies: + axios: + specifier: ^1.6.2 + version: 1.7.9 + tsup: + specifier: 6.7.0 + version: 6.7.0(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.10.5)(typescript@5.3.3))(typescript@5.3.3) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + vitest: + specifier: 3.0.5 + version: 3.0.5(@types/node@20.10.5) + packages/large-response-middleware: dependencies: core-js: @@ -781,96 +796,192 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.17.19': + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.24.2': resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.17.19': + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.24.2': resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.17.19': + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.24.2': resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.17.19': + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.24.2': resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.17.19': + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.24.2': resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.17.19': + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.17.19': + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.17.19': + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.24.2': resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.17.19': + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.24.2': resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.17.19': + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.24.2': resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.17.19': + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.24.2': resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.17.19': + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.24.2': resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.17.19': + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.24.2': resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.17.19': + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.24.2': resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.17.19': + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.24.2': resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.17.19': + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.24.2': resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} engines: {node: '>=18'} @@ -883,6 +994,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.17.19': + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} engines: {node: '>=18'} @@ -895,36 +1012,70 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.17.19': + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.17.19': + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.24.2': resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.17.19': + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.24.2': resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.17.19': + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.24.2': resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.17.19': + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.24.2': resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -968,6 +1119,10 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@rollup/plugin-babel@6.0.4': resolution: {integrity: sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==} engines: {node: '>=14.0.0'} @@ -1290,6 +1445,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -1298,6 +1457,13 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1311,6 +1477,10 @@ packages: array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + arraybuffer.prototype.slice@1.0.2: resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} engines: {node: '>= 0.4'} @@ -1319,6 +1489,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atob@2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} engines: {node: '>= 4.5.0'} @@ -1336,6 +1509,9 @@ packages: resolution: {integrity: sha512-M6wNOrq9HliJoWgmgHeRzMHHrgK6UY20RL2tUhNqq45ETZnj1ihrqG5vSt5ywLrV9WUyI/lUQAVmCP/2PYjpQw==} engines: {node: '>= 10.0.0'} + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + babel-plugin-polyfill-corejs2@0.4.11: resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} peerDependencies: @@ -1383,6 +1559,12 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} + bundle-require@4.2.1: + resolution: {integrity: sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' + bytes-iec@3.1.1: resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} engines: {node: '>= 0.8'} @@ -1430,6 +1612,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@3.0.2: resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} @@ -1510,16 +1696,30 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.13: resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1538,6 +1738,11 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} + esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.24.2: resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} @@ -1573,6 +1778,10 @@ packages: resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} engines: {node: '>=0.4.x'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + expect-type@1.1.0: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} @@ -1588,9 +1797,26 @@ packages: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fs-readdir-recursive@1.1.0: resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==} @@ -1623,6 +1849,10 @@ packages: get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -1634,6 +1864,10 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -1651,6 +1885,10 @@ packages: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + globby@14.0.0: resolution: {integrity: sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==} engines: {node: '>=18'} @@ -1690,6 +1928,10 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + ieee754@1.1.13: resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==} @@ -1790,6 +2032,10 @@ packages: is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -1818,10 +2064,17 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jmespath@0.16.0: resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} engines: {node: '>= 0.6.0'} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1846,20 +2099,37 @@ packages: engines: {node: '>=6'} hasBin: true + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + lilconfig@3.0.0: resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} engines: {node: '>=14'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + load-json-file@4.0.0: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1881,6 +2151,9 @@ packages: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1889,6 +2162,18 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1896,12 +2181,23 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.8: resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1928,6 +2224,14 @@ packages: engines: {node: '>= 4'} hasBin: true + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} @@ -1942,10 +2246,17 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -1965,10 +2276,18 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + path-type@5.0.0: resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} engines: {node: '>=12'} @@ -2003,13 +2322,36 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + postcss@8.5.1: resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} engines: {node: ^10 || ^12 || >=14} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@1.3.2: resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + querystring@0.2.0: resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} engines: {node: '>=0.4.x'} @@ -2055,6 +2397,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -2083,6 +2429,11 @@ packages: rollup: optional: true + rollup@3.29.5: + resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + rollup@4.34.2: resolution: {integrity: sha512-sBDUoxZEaqLu9QeNalL8v3jw6WjPku4wfZGyTU7l7m1oC+rpRihXc/n/H+4148ZkGz5Xli8CHMns//fFGKvpIQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2147,6 +2498,13 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + size-limit@11.0.1: resolution: {integrity: sha512-6L80ocVspWPrhIRg8kPl41VypqTGH8/lu9e6TJiSJpkNLtOR2h/EEqdAO/wNJOv/sUVtjX+lVEWrzBpItGP+gQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2156,6 +2514,10 @@ packages: resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} engines: {node: '>=6'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -2172,6 +2534,10 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -2197,6 +2563,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string.prototype.padend@3.1.5: resolution: {integrity: sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==} engines: {node: '>= 0.4'} @@ -2215,10 +2585,23 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2227,6 +2610,13 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2253,6 +2643,16 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -2270,6 +2670,22 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tsup@6.7.0: + resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==} + engines: {node: '>=14.18'} + hasBin: true + peerDependencies: + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.1.0' + peerDependenciesMeta: + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + typed-array-buffer@1.0.0: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} @@ -2415,6 +2831,12 @@ packages: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2440,6 +2862,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2458,6 +2884,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -3335,78 +3765,153 @@ snapshots: '@esbuild/aix-ppc64@0.24.2': optional: true + '@esbuild/android-arm64@0.17.19': + optional: true + '@esbuild/android-arm64@0.24.2': optional: true + '@esbuild/android-arm@0.17.19': + optional: true + '@esbuild/android-arm@0.24.2': optional: true + '@esbuild/android-x64@0.17.19': + optional: true + '@esbuild/android-x64@0.24.2': optional: true + '@esbuild/darwin-arm64@0.17.19': + optional: true + '@esbuild/darwin-arm64@0.24.2': optional: true + '@esbuild/darwin-x64@0.17.19': + optional: true + '@esbuild/darwin-x64@0.24.2': optional: true + '@esbuild/freebsd-arm64@0.17.19': + optional: true + '@esbuild/freebsd-arm64@0.24.2': optional: true + '@esbuild/freebsd-x64@0.17.19': + optional: true + '@esbuild/freebsd-x64@0.24.2': optional: true + '@esbuild/linux-arm64@0.17.19': + optional: true + '@esbuild/linux-arm64@0.24.2': optional: true + '@esbuild/linux-arm@0.17.19': + optional: true + '@esbuild/linux-arm@0.24.2': optional: true + '@esbuild/linux-ia32@0.17.19': + optional: true + '@esbuild/linux-ia32@0.24.2': optional: true + '@esbuild/linux-loong64@0.17.19': + optional: true + '@esbuild/linux-loong64@0.24.2': optional: true + '@esbuild/linux-mips64el@0.17.19': + optional: true + '@esbuild/linux-mips64el@0.24.2': optional: true + '@esbuild/linux-ppc64@0.17.19': + optional: true + '@esbuild/linux-ppc64@0.24.2': optional: true + '@esbuild/linux-riscv64@0.17.19': + optional: true + '@esbuild/linux-riscv64@0.24.2': optional: true + '@esbuild/linux-s390x@0.17.19': + optional: true + '@esbuild/linux-s390x@0.24.2': optional: true + '@esbuild/linux-x64@0.17.19': + optional: true + '@esbuild/linux-x64@0.24.2': optional: true '@esbuild/netbsd-arm64@0.24.2': optional: true + '@esbuild/netbsd-x64@0.17.19': + optional: true + '@esbuild/netbsd-x64@0.24.2': optional: true '@esbuild/openbsd-arm64@0.24.2': optional: true + '@esbuild/openbsd-x64@0.17.19': + optional: true + '@esbuild/openbsd-x64@0.24.2': optional: true + '@esbuild/sunos-x64@0.17.19': + optional: true + '@esbuild/sunos-x64@0.24.2': optional: true + '@esbuild/win32-arm64@0.17.19': + optional: true + '@esbuild/win32-arm64@0.24.2': optional: true + '@esbuild/win32-ia32@0.17.19': + optional: true + '@esbuild/win32-ia32@0.24.2': optional: true + '@esbuild/win32-x64@0.17.19': + optional: true + '@esbuild/win32-x64@0.24.2': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -3448,6 +3953,9 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.16.0 + '@pkgjs/parseargs@0.11.0': + optional: true + '@rollup/plugin-babel@6.0.4(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@4.9.1)': dependencies: '@babel/core': 7.25.2 @@ -3706,6 +4214,8 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.1.0: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -3714,6 +4224,10 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -3730,6 +4244,8 @@ snapshots: call-bind: 1.0.5 is-array-buffer: 3.0.2 + array-union@2.1.0: {} + arraybuffer.prototype.slice@1.0.2: dependencies: array-buffer-byte-length: 1.0.0 @@ -3742,6 +4258,8 @@ snapshots: assertion-error@2.0.1: {} + asynckit@0.4.0: {} + atob@2.1.2: {} available-typed-arrays@1.0.5: {} @@ -3766,6 +4284,14 @@ snapshots: uuid: 8.0.0 xml2js: 0.5.0 + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.25.2): dependencies: '@babel/compat-data': 7.25.4 @@ -3824,6 +4350,11 @@ snapshots: builtin-modules@3.3.0: {} + bundle-require@4.2.1(esbuild@0.17.19): + dependencies: + esbuild: 0.17.19 + load-tsconfig: 0.2.5 + bytes-iec@3.1.1: {} cac@6.7.14: {} @@ -3882,6 +4413,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@3.0.2: {} commander@4.1.1: {} @@ -3946,12 +4481,22 @@ snapshots: has-property-descriptors: 1.0.1 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + diff@4.0.2: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.13: {} emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -4012,6 +4557,31 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 + esbuild@0.17.19: + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + esbuild@0.24.2: optionalDependencies: '@esbuild/aix-ppc64': 0.24.2 @@ -4052,12 +4622,24 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} events@1.1.1: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + expect-type@1.1.0: {} fast-glob@3.3.2: @@ -4076,10 +4658,23 @@ snapshots: dependencies: to-regex-range: 5.0.1 + follow-redirects@1.15.9: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fs-readdir-recursive@1.1.0: {} fs.realpath@1.0.0: {} @@ -4109,6 +4704,8 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.0 + get-stream@6.0.1: {} + get-symbol-description@1.0.0: dependencies: call-bind: 1.0.5 @@ -4120,6 +4717,15 @@ snapshots: glob-to-regexp@0.4.1: {} + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -4143,6 +4749,15 @@ snapshots: dependencies: define-properties: 1.2.1 + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.0 + merge2: 1.4.1 + slash: 3.0.0 + globby@14.0.0: dependencies: '@sindresorhus/merge-streams': 1.0.0 @@ -4180,6 +4795,8 @@ snapshots: hosted-git-info@2.8.9: {} + human-signals@2.1.0: {} + ieee754@1.1.13: {} ignore@5.3.0: {} @@ -4274,6 +4891,8 @@ snapshots: dependencies: call-bind: 1.0.5 + is-stream@2.0.1: {} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.0 @@ -4300,8 +4919,16 @@ snapshots: isexe@2.0.0: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jmespath@0.16.0: {} + joycon@3.1.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -4317,8 +4944,12 @@ snapshots: json5@2.2.3: {} + lilconfig@2.1.0: {} + lilconfig@3.0.0: {} + lines-and-columns@1.2.4: {} + load-json-file@4.0.0: dependencies: graceful-fs: 4.2.11 @@ -4326,10 +4957,16 @@ snapshots: pify: 3.0.0 strip-bom: 3.0.0 + load-tsconfig@0.2.5: {} + lodash.debounce@4.0.8: {} + lodash.sortby@4.7.0: {} + loupe@3.1.3: {} + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4351,6 +4988,8 @@ snapshots: memorystream@0.3.1: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} micromatch@4.0.5: @@ -4358,6 +4997,14 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -4366,10 +5013,22 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + ms@2.1.2: {} ms@2.1.3: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.8: {} nanospinner@1.1.0: @@ -4401,6 +5060,12 @@ snapshots: shell-quote: 1.8.1 string.prototype.padend: 3.1.5 + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + object-inspect@1.13.1: {} object-keys@1.1.1: {} @@ -4416,12 +5081,18 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + open@8.4.2: dependencies: define-lazy-prop: 2.0.0 is-docker: 2.2.1 is-wsl: 2.2.0 + package-json-from-dist@1.0.1: {} + parse-json@4.0.0: dependencies: error-ex: 1.3.2 @@ -4435,10 +5106,17 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-type@3.0.0: dependencies: pify: 3.0.0 + path-type@4.0.0: {} + path-type@5.0.0: {} pathe@2.0.2: {} @@ -4457,14 +5135,28 @@ snapshots: pify@4.0.1: {} + pirates@4.0.6: {} + + postcss-load-config@3.1.4(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.10.5)(typescript@5.3.3)): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.5.1 + ts-node: 10.9.2(@types/node@20.10.5)(typescript@5.3.3) + postcss@8.5.1: dependencies: nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 + proxy-from-env@1.1.0: {} + punycode@1.3.2: {} + punycode@2.3.1: {} + querystring@0.2.0: {} queue-microtask@1.2.3: {} @@ -4512,6 +5204,8 @@ snapshots: require-directory@2.1.1: {} + resolve-from@5.0.0: {} + resolve@1.22.8: dependencies: is-core-module: 2.13.1 @@ -4537,6 +5231,10 @@ snapshots: optionalDependencies: rollup: 4.9.1 + rollup@3.29.5: + optionalDependencies: + fsevents: 2.3.3 + rollup@4.34.2: dependencies: '@types/estree': 1.0.6 @@ -4637,6 +5335,10 @@ snapshots: siginfo@2.0.0: {} + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + size-limit@11.0.1: dependencies: bytes-iec: 3.1.1 @@ -4648,6 +5350,8 @@ snapshots: slash@2.0.0: {} + slash@3.0.0: {} + slash@5.1.0: {} source-map-js@1.2.1: {} @@ -4659,6 +5363,10 @@ snapshots: source-map@0.7.4: {} + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -4685,6 +5393,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string.prototype.padend@3.1.5: dependencies: call-bind: 1.0.5 @@ -4713,14 +5427,38 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + strip-bom@3.0.0: {} + strip-final-newline@2.0.0: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 supports-preserve-symlinks-flag@1.0.0: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -4737,6 +5475,14 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + ts-node@10.9.2(@types/node@20.10.5)(typescript@5.3.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -4757,6 +5503,29 @@ snapshots: tslib@2.6.2: {} + tsup@6.7.0(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.10.5)(typescript@5.3.3))(typescript@5.3.3): + dependencies: + bundle-require: 4.2.1(esbuild@0.17.19) + cac: 6.7.14 + chokidar: 3.5.3 + debug: 4.4.0 + esbuild: 0.17.19 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 3.1.4(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.10.5)(typescript@5.3.3)) + resolve-from: 5.0.0 + rollup: 3.29.5 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.1 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + - ts-node + typed-array-buffer@1.0.0: dependencies: call-bind: 1.0.5 @@ -4909,6 +5678,14 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + webidl-conversions@4.0.2: {} + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -4944,6 +5721,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} xml2js@0.5.0: @@ -4957,6 +5740,8 @@ snapshots: yallist@3.1.1: {} + yaml@1.10.2: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index eccc335..4340350 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,2 @@ packages: - - 'packages/**' \ No newline at end of file + - 'packages/*' \ No newline at end of file From c35fce48bfcae0d5000586497596f382f12d04ad Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Thu, 13 Feb 2025 16:46:42 +0000 Subject: [PATCH 02/20] feat: wip: enhance axios interceptor for large response handling - Add comprehensive types for interceptor configuration - Implement utility functions for large payload fetching - Update interceptor to support dynamic configuration - Add debug logging and error handling - Expose new export for types and interceptor function --- .../README.md | 0 .../package.json | 3 +- .../src/index.ts | 3 +- .../src/interceptor/axios-interceptor.test.ts | 7 +++ .../src/interceptor/axios-interceptor.ts | 63 +++++++++++++++++++ .../src/lib/axios-interceptor.ts | 7 --- .../src/types.ts | 27 ++++++++ .../src/utils/utils.test.ts | 47 ++++++++++++++ .../src/utils/utils.ts | 46 ++++++++++++++ .../tsup.config.ts | 2 - 10 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 packages/axios-interceptor-large-response/README.md create mode 100644 packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts create mode 100644 packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts delete mode 100644 packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts create mode 100644 packages/axios-interceptor-large-response/src/types.ts create mode 100644 packages/axios-interceptor-large-response/src/utils/utils.test.ts create mode 100644 packages/axios-interceptor-large-response/src/utils/utils.ts diff --git a/packages/axios-interceptor-large-response/README.md b/packages/axios-interceptor-large-response/README.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/axios-interceptor-large-response/package.json b/packages/axios-interceptor-large-response/package.json index 5e8984b..2f393bd 100644 --- a/packages/axios-interceptor-large-response/package.json +++ b/packages/axios-interceptor-large-response/package.json @@ -11,7 +11,6 @@ }, "description": "Axios plugin to intercept large responses", "keywords": ["large-response-middleware", "interceptor", "axios", "large-response", "plugin"], - "author": "Alexandre Marques", "contributors": ["Alexandre Marques(https://github.com/alexmarqs)"], "scripts": { "build": "tsup", @@ -29,7 +28,7 @@ "require": "./dist/index.js" } }, - "files": ["dist/**"], + "files": ["dist/**", "README.md"], "devDependencies": { "typescript": "^5.3.3", "vitest": "3.0.5", diff --git a/packages/axios-interceptor-large-response/src/index.ts b/packages/axios-interceptor-large-response/src/index.ts index 1b55a03..0d8729c 100644 --- a/packages/axios-interceptor-large-response/src/index.ts +++ b/packages/axios-interceptor-large-response/src/index.ts @@ -1 +1,2 @@ -export { axiosInterceptor } from './lib/axios-interceptor'; +export { axiosInterceptorLargeResponse } from './interceptor/axios-interceptor'; +export * from './types'; diff --git a/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts new file mode 100644 index 0000000..01d18b0 --- /dev/null +++ b/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from 'vitest'; + +describe('axiosInterceptorLargeResponse', () => { + it('TODO: Add tests', () => { + expect(1).toBe(1); + }); +}); diff --git a/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts b/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts new file mode 100644 index 0000000..f55af16 --- /dev/null +++ b/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts @@ -0,0 +1,63 @@ +import type { + AxiosInterceptorLargeResponse, + AxiosInterceptorLargeResponseOptions, + LargePayloadResponse, +} from '../types'; +import { NAMESPACE, fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled } from '../utils/utils'; + +/** + * This is the main function that adds the interceptors to the axios instance. + */ +const axiosInterceptorLargeResponse: AxiosInterceptorLargeResponse = (axiosInstance, globalOptions) => { + const requestInterceptorId = axiosInstance.interceptors.request.use((config) => { + const { headerFlag } = getOptions(config?.[NAMESPACE], globalOptions); + + config.headers = config.headers || {}; + config.headers.Accept = headerFlag; + + return config; + }); + + const responseInterceptorId = axiosInstance.interceptors.response.use(async (response) => { + const configRequestOptions = response?.config?.[NAMESPACE]; + const { debug, logger, headerFlag, refUrlProperty } = getOptions(configRequestOptions, globalOptions); + + if ( + response.headers['content-type'] === headerFlag && + response.data && + (response.data as LargePayloadResponse)[refUrlProperty] + ) { + if (isDebugEnabled(debug)) { + logger.debug('[LargeResponseInterceptor] Fetching large payload from ref url', { + refUrl: (response.data as LargePayloadResponse)[refUrlProperty], + }); + } + try { + response.data = await fetchLargePayloadFromS3Ref((response.data as LargePayloadResponse)[refUrlProperty]); + } catch (error) { + logger.error('[LargeResponseInterceptor] Error fetching large payload from ref url', { + reason: error instanceof Error ? error.message : 'unknown', + }); + throw error; + } + } + return response; + }); + + return { + requestInterceptorId, + responseInterceptorId, + }; +}; + +/** + * This is a workaround to allow the axios-interceptor-large-response options to be used in the axios request config. + * This is necessary because the axios-interceptor-large-response options are not part of the axios request config. + */ +declare module 'axios' { + export interface AxiosRequestConfig { + [NAMESPACE]?: AxiosInterceptorLargeResponseOptions; + } +} + +export { axiosInterceptorLargeResponse }; diff --git a/packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts b/packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts deleted file mode 100644 index c038f5c..0000000 --- a/packages/axios-interceptor-large-response/src/lib/axios-interceptor.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AxiosInstance } from "axios"; - -export const axiosInterceptor = (axiosInstance: AxiosInstance) => { - axiosInstance.interceptors.response.use((response) => { - return response; - }); -}; diff --git a/packages/axios-interceptor-large-response/src/types.ts b/packages/axios-interceptor-large-response/src/types.ts new file mode 100644 index 0000000..4998f86 --- /dev/null +++ b/packages/axios-interceptor-large-response/src/types.ts @@ -0,0 +1,27 @@ +import type { AxiosInstance, AxiosStatic } from 'axios'; + +type LargePayloadResponse = { + [refUrlProperty: string]: string; +}; + +type Logger = { + debug: (message: string, ...args: unknown[]) => void; + error: (message: string, ...args: unknown[]) => void; +}; + +type AxiosInterceptorLargeResponseOptions = { + debug?: boolean; + logger?: Logger; + headerFlag?: string; + refUrlProperty?: string; +}; + +type AxiosInterceptorLargeResponse = ( + axiosInstance: AxiosInstance | AxiosStatic, + axiosInterceptorLargeResponseOptions?: AxiosInterceptorLargeResponseOptions, +) => { + requestInterceptorId: number; + responseInterceptorId: number; +}; + +export type { AxiosInterceptorLargeResponse, AxiosInterceptorLargeResponseOptions, LargePayloadResponse, Logger }; diff --git a/packages/axios-interceptor-large-response/src/utils/utils.test.ts b/packages/axios-interceptor-large-response/src/utils/utils.test.ts new file mode 100644 index 0000000..3e03e62 --- /dev/null +++ b/packages/axios-interceptor-large-response/src/utils/utils.test.ts @@ -0,0 +1,47 @@ +import axios from 'axios'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { fetchLargePayloadFromS3Ref, isDebugEnabled } from './utils'; + +vi.mock('axios'); +const mockedAxios = vi.mocked(axios, true); + +describe('isDebugEnabled', () => { + beforeEach(() => { + process.env.AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG = ''; + }); + + it('should return true if the debug environment variable is set', () => { + process.env.AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG = 'true'; + expect(isDebugEnabled()).toBe(true); + }); + + it('should return false if the debug environment variable is not set', () => { + process.env.AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG = ''; + expect(isDebugEnabled()).toBe(false); + }); + + it('should return true if the debug environment variable is set to 1', () => { + process.env.AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG = '1'; + expect(isDebugEnabled()).toBe(true); + }); + + it('should return true if the manual debug is true', () => { + expect(isDebugEnabled(true)).toBe(true); + }); + + it('should return false if the manual debug is false', () => { + expect(isDebugEnabled(false)).toBe(false); + }); +}); + +describe('fetchLargePayloadFromS3Ref', () => { + it('should fetch the large payload from the S3 ref', async () => { + mockedAxios.get.mockResolvedValueOnce({ + data: JSON.stringify({ payload: 'large-payload' }), + }); + + const largePayload = await fetchLargePayloadFromS3Ref('https://example.com/large-payload'); + + expect(largePayload).toEqual({ payload: 'large-payload' }); + }); +}); diff --git a/packages/axios-interceptor-large-response/src/utils/utils.ts b/packages/axios-interceptor-large-response/src/utils/utils.ts new file mode 100644 index 0000000..cf6f599 --- /dev/null +++ b/packages/axios-interceptor-large-response/src/utils/utils.ts @@ -0,0 +1,46 @@ +import axios from 'axios'; +import type { AxiosInterceptorLargeResponseOptions } from '../types'; + +const DEBUG_ENV_VAR = 'AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG'; + +const LARGE_PAYLOAD_MIME_TYPE = 'application/large-response.vnd+json'; + +const fetchLargePayloadFromS3Ref = async (payloadRef: string) => { + const escapedJsonResponse = await axios.get(payloadRef); + return JSON.parse(escapedJsonResponse.data); +}; + +const isDebugEnabled = (manualDebug?: boolean) => { + if (manualDebug !== undefined) { + return manualDebug; + } + const envVarValue = process.env[DEBUG_ENV_VAR]; + return envVarValue === 'true' || envVarValue === '1'; +}; + +const DEFAULT_OPTIONS: Required = { + debug: false, + logger: console, + headerFlag: LARGE_PAYLOAD_MIME_TYPE, + refUrlProperty: '$payload_ref', +}; + +/** + * This function merges the global options with the config request options. + * If the config request options are not provided, it will use the global options. + * If the config request options are provided, it will use the config request options. + */ +const getOptions = ( + configRequestOptions?: AxiosInterceptorLargeResponseOptions, + globalOptions?: AxiosInterceptorLargeResponseOptions, +) => { + return { + ...DEFAULT_OPTIONS, + ...globalOptions, + ...configRequestOptions, + }; +}; + +const NAMESPACE = 'axios-interceptor-large-response'; + +export { fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled, LARGE_PAYLOAD_MIME_TYPE, NAMESPACE }; diff --git a/packages/axios-interceptor-large-response/tsup.config.ts b/packages/axios-interceptor-large-response/tsup.config.ts index 1d55e78..9db1930 100644 --- a/packages/axios-interceptor-large-response/tsup.config.ts +++ b/packages/axios-interceptor-large-response/tsup.config.ts @@ -5,8 +5,6 @@ export default defineConfig([ entry: ['src/index.ts'], format: ['cjs', 'esm'], target: 'node14', - splitting: false, - sourcemap: false, clean: true, minify: true, dts: true, From 9d98a510c95c68a505c8524f236bd5a75211eaa3 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Fri, 14 Feb 2025 16:33:36 +0000 Subject: [PATCH 03/20] refactor: improve axios large response interceptor configuration - Rename types and functions for clarity - Add `enabled` option to conditionally apply interceptor - Support custom large payload fetching strategy - Update namespace and default options - Enhance type safety and configuration flexibility --- .vscode/settings.json | 3 +- .../src/interceptor/axios-interceptor.ts | 35 +++++++++------- .../src/types.ts | 12 +++--- .../src/utils/utils.test.ts | 40 ++++++++++++++++++- .../src/utils/utils.ts | 17 ++++---- 5 files changed, 77 insertions(+), 30 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2f37c42..f41fbd8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,8 @@ { "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { - "source.organizeImports": "always" + "source.organizeImports": "always", + "source.fixAll": "always" }, "[json]": { "editor.defaultFormatter": "biomejs.biome" diff --git a/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts b/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts index f55af16..a17c84b 100644 --- a/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts +++ b/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts @@ -1,16 +1,16 @@ -import type { - AxiosInterceptorLargeResponse, - AxiosInterceptorLargeResponseOptions, - LargePayloadResponse, -} from '../types'; -import { NAMESPACE, fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled } from '../utils/utils'; +import type { AxiosLargeResponse, AxiosLargeResponseOptions, LargePayloadResponse } from '../types'; +import { NAMESPACE, getOptions, isDebugEnabled } from '../utils/utils'; /** * This is the main function that adds the interceptors to the axios instance. */ -const axiosInterceptorLargeResponse: AxiosInterceptorLargeResponse = (axiosInstance, globalOptions) => { +const axiosLargeResponse: AxiosLargeResponse = (axiosInstance, globalOptions) => { const requestInterceptorId = axiosInstance.interceptors.request.use((config) => { - const { headerFlag } = getOptions(config?.[NAMESPACE], globalOptions); + const { headerFlag, enabled } = getOptions(config?.[NAMESPACE], globalOptions); + + if (!enabled) { + return config; + } config.headers = config.headers || {}; config.headers.Accept = headerFlag; @@ -20,20 +20,27 @@ const axiosInterceptorLargeResponse: AxiosInterceptorLargeResponse = (axiosInsta const responseInterceptorId = axiosInstance.interceptors.response.use(async (response) => { const configRequestOptions = response?.config?.[NAMESPACE]; - const { debug, logger, headerFlag, refUrlProperty } = getOptions(configRequestOptions, globalOptions); + const { debug, logger, headerFlag, refProperty, onFetchLargePayloadFromRef, enabled } = getOptions( + configRequestOptions, + globalOptions, + ); + + if (!enabled) { + return response; + } if ( response.headers['content-type'] === headerFlag && response.data && - (response.data as LargePayloadResponse)[refUrlProperty] + (response.data as LargePayloadResponse)[refProperty] ) { if (isDebugEnabled(debug)) { logger.debug('[LargeResponseInterceptor] Fetching large payload from ref url', { - refUrl: (response.data as LargePayloadResponse)[refUrlProperty], + ref: (response.data as LargePayloadResponse)[refProperty], }); } try { - response.data = await fetchLargePayloadFromS3Ref((response.data as LargePayloadResponse)[refUrlProperty]); + response.data = await onFetchLargePayloadFromRef((response.data as LargePayloadResponse)[refProperty]); } catch (error) { logger.error('[LargeResponseInterceptor] Error fetching large payload from ref url', { reason: error instanceof Error ? error.message : 'unknown', @@ -56,8 +63,8 @@ const axiosInterceptorLargeResponse: AxiosInterceptorLargeResponse = (axiosInsta */ declare module 'axios' { export interface AxiosRequestConfig { - [NAMESPACE]?: AxiosInterceptorLargeResponseOptions; + [NAMESPACE]?: AxiosLargeResponseOptions; } } -export { axiosInterceptorLargeResponse }; +export { axiosLargeResponse }; diff --git a/packages/axios-interceptor-large-response/src/types.ts b/packages/axios-interceptor-large-response/src/types.ts index 4998f86..9ed1a46 100644 --- a/packages/axios-interceptor-large-response/src/types.ts +++ b/packages/axios-interceptor-large-response/src/types.ts @@ -9,19 +9,21 @@ type Logger = { error: (message: string, ...args: unknown[]) => void; }; -type AxiosInterceptorLargeResponseOptions = { +type AxiosLargeResponseOptions = { + enabled?: boolean; debug?: boolean; logger?: Logger; headerFlag?: string; - refUrlProperty?: string; + refProperty?: string; + onFetchLargePayloadFromRef?: (ref: string) => Promise; }; -type AxiosInterceptorLargeResponse = ( +type AxiosLargeResponse = ( axiosInstance: AxiosInstance | AxiosStatic, - axiosInterceptorLargeResponseOptions?: AxiosInterceptorLargeResponseOptions, + axiosLargeResponseOptions?: AxiosLargeResponseOptions, ) => { requestInterceptorId: number; responseInterceptorId: number; }; -export type { AxiosInterceptorLargeResponse, AxiosInterceptorLargeResponseOptions, LargePayloadResponse, Logger }; +export type { AxiosLargeResponse, AxiosLargeResponseOptions, LargePayloadResponse, Logger }; diff --git a/packages/axios-interceptor-large-response/src/utils/utils.test.ts b/packages/axios-interceptor-large-response/src/utils/utils.test.ts index 3e03e62..7fcb732 100644 --- a/packages/axios-interceptor-large-response/src/utils/utils.test.ts +++ b/packages/axios-interceptor-large-response/src/utils/utils.test.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { fetchLargePayloadFromS3Ref, isDebugEnabled } from './utils'; +import type { AxiosLargeResponseOptions } from '../types'; +import { DEFAULT_OPTIONS, fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled } from './utils'; vi.mock('axios'); const mockedAxios = vi.mocked(axios, true); @@ -45,3 +46,40 @@ describe('fetchLargePayloadFromS3Ref', () => { expect(largePayload).toEqual({ payload: 'large-payload' }); }); }); + +describe('getOptions', () => { + it('should merge the global options with the config request options', () => { + const globalOptions = { + enabled: true, + debug: false, + logger: console, + headerFlag: 'application/json', + refProperty: '$payload_ref', + onFetchLargePayloadFromRef: fetchLargePayloadFromS3Ref, + } satisfies AxiosLargeResponseOptions; + + const customOnFetchLargePayloadFromRef = async (ref: string) => { + return { payload: `large-payload-from-${ref}` }; + }; + + const configRequestOptions = { + enabled: false, + onFetchLargePayloadFromRef: customOnFetchLargePayloadFromRef, + } satisfies AxiosLargeResponseOptions; + + const options = getOptions(configRequestOptions, globalOptions); + expect(options).toEqual({ + enabled: false, + debug: false, + logger: console, + headerFlag: 'application/json', + refProperty: '$payload_ref', + onFetchLargePayloadFromRef: customOnFetchLargePayloadFromRef, + }); + }); + + it('should use the default options', () => { + const options = getOptions(); + expect(options).toEqual(DEFAULT_OPTIONS); + }); +}); diff --git a/packages/axios-interceptor-large-response/src/utils/utils.ts b/packages/axios-interceptor-large-response/src/utils/utils.ts index cf6f599..2f7bd64 100644 --- a/packages/axios-interceptor-large-response/src/utils/utils.ts +++ b/packages/axios-interceptor-large-response/src/utils/utils.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import type { AxiosInterceptorLargeResponseOptions } from '../types'; +import type { AxiosLargeResponseOptions } from '../types'; const DEBUG_ENV_VAR = 'AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG'; @@ -18,11 +18,13 @@ const isDebugEnabled = (manualDebug?: boolean) => { return envVarValue === 'true' || envVarValue === '1'; }; -const DEFAULT_OPTIONS: Required = { +export const DEFAULT_OPTIONS: Required = { + enabled: true, debug: false, logger: console, headerFlag: LARGE_PAYLOAD_MIME_TYPE, - refUrlProperty: '$payload_ref', + refProperty: '$payload_ref', + onFetchLargePayloadFromRef: fetchLargePayloadFromS3Ref, }; /** @@ -30,17 +32,14 @@ const DEFAULT_OPTIONS: Required = { * If the config request options are not provided, it will use the global options. * If the config request options are provided, it will use the config request options. */ -const getOptions = ( - configRequestOptions?: AxiosInterceptorLargeResponseOptions, - globalOptions?: AxiosInterceptorLargeResponseOptions, -) => { +const getOptions = (configRequestOptions?: AxiosLargeResponseOptions, globalOptions?: AxiosLargeResponseOptions) => { return { ...DEFAULT_OPTIONS, ...globalOptions, ...configRequestOptions, - }; + } satisfies AxiosLargeResponseOptions; }; -const NAMESPACE = 'axios-interceptor-large-response'; +const NAMESPACE = 'largeResponse'; export { fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled, LARGE_PAYLOAD_MIME_TYPE, NAMESPACE }; From 68f4bd4704a33ac5f9e71bcd2414a54759cafcca Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Fri, 14 Feb 2025 17:34:43 +0000 Subject: [PATCH 04/20] test: add initial test suite for axios large response interceptor - Create comprehensive test cases for axios large response interceptor - Add test configuration for Vitest - Implement initial test scenarios for request and response interceptors - Verify basic functionality and configuration options --- .../src/interceptor/axios-interceptor.test.ts | 7 - .../README.md | 0 .../package.json | 5 +- .../src/index.ts | 0 .../src/interceptor/axios-interceptor.test.ts | 164 ++++++++++++++++++ .../src/interceptor/axios-interceptor.ts | 0 .../src/types.ts | 0 .../src/utils/utils.test.ts | 0 .../src/utils/utils.ts | 0 .../tsconfig.json | 0 .../tsup.config.ts | 0 .../vitest.config.mts | 2 +- 12 files changed, 168 insertions(+), 10 deletions(-) delete mode 100644 packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts rename packages/{axios-interceptor-large-response => axios-large-response}/README.md (100%) rename packages/{axios-interceptor-large-response => axios-large-response}/package.json (90%) rename packages/{axios-interceptor-large-response => axios-large-response}/src/index.ts (100%) create mode 100644 packages/axios-large-response/src/interceptor/axios-interceptor.test.ts rename packages/{axios-interceptor-large-response => axios-large-response}/src/interceptor/axios-interceptor.ts (100%) rename packages/{axios-interceptor-large-response => axios-large-response}/src/types.ts (100%) rename packages/{axios-interceptor-large-response => axios-large-response}/src/utils/utils.test.ts (100%) rename packages/{axios-interceptor-large-response => axios-large-response}/src/utils/utils.ts (100%) rename packages/{axios-interceptor-large-response => axios-large-response}/tsconfig.json (100%) rename packages/{axios-interceptor-large-response => axios-large-response}/tsup.config.ts (100%) rename packages/{axios-interceptor-large-response => axios-large-response}/vitest.config.mts (93%) diff --git a/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts deleted file mode 100644 index 01d18b0..0000000 --- a/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -describe('axiosInterceptorLargeResponse', () => { - it('TODO: Add tests', () => { - expect(1).toBe(1); - }); -}); diff --git a/packages/axios-interceptor-large-response/README.md b/packages/axios-large-response/README.md similarity index 100% rename from packages/axios-interceptor-large-response/README.md rename to packages/axios-large-response/README.md diff --git a/packages/axios-interceptor-large-response/package.json b/packages/axios-large-response/package.json similarity index 90% rename from packages/axios-interceptor-large-response/package.json rename to packages/axios-large-response/package.json index 2f393bd..c22abc2 100644 --- a/packages/axios-interceptor-large-response/package.json +++ b/packages/axios-large-response/package.json @@ -1,5 +1,5 @@ { - "name": "@epilot/axios-interceptor-large-response", + "name": "@epilot/axios-large-response", "version": "0.0.1", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -7,7 +7,8 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/epilot-dev/aws-lambda-utility-middlewares.git#main" + "url": "git+https://github.com/epilot-dev/aws-lambda-utility-middlewares.git#main", + "directory": "packages/axios-large-response" }, "description": "Axios plugin to intercept large responses", "keywords": ["large-response-middleware", "interceptor", "axios", "large-response", "plugin"], diff --git a/packages/axios-interceptor-large-response/src/index.ts b/packages/axios-large-response/src/index.ts similarity index 100% rename from packages/axios-interceptor-large-response/src/index.ts rename to packages/axios-large-response/src/index.ts diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts new file mode 100644 index 0000000..227a5df --- /dev/null +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -0,0 +1,164 @@ +import axios, { type AxiosInstance } from 'axios'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { AxiosLargeResponseOptions } from '../types'; +import { axiosLargeResponse } from './axios-interceptor'; + +describe('axiosInterceptorLargeResponse', () => { + let axiosInstance: AxiosInstance; + let globalOptions: AxiosLargeResponseOptions; + + beforeEach(() => { + axiosInstance = axios.create(); + globalOptions = { + enabled: true, + headerFlag: 'application/ref+json', + refProperty: 'ref', + debug: false, + logger: { + debug: vi.fn(), + error: vi.fn(), + }, + onFetchLargePayloadFromRef: vi.fn(), + }; + }); + + // biome-ignore lint/suspicious/noFocusedTests: + it.only('should allow normal responses to pass through unchanged', async () => { + // Mock request config + const requestConfig = { + headers: {}, + method: 'GET', + url: 'https://api.example.com', + }; + + // Mock response + const normalResponse = { + data: { foo: 'bar' }, + headers: { 'content-type': 'application/json' }, + config: requestConfig, + status: 200, + statusText: 'OK', + }; + + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); + + const { requestInterceptor, responseInterceptor } = getInterceptors( + axiosInstance, + requestInterceptorId, + responseInterceptorId, + ); + + const modifiedConfig = await requestInterceptor(requestConfig); + expect(modifiedConfig.headers.Accept).toBe(globalOptions.headerFlag); + + const result = await responseInterceptor(normalResponse); + expect(result).toEqual(normalResponse); + + expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); + }); + + // it('should handle large responses appropriately', async () => { + // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); + // const largePayloadUrl = 'https://api.example.com/large-payload'; + // const largePayloadData = { huge: 'data' }; + + // const largeResponse = { + // data: { ref: largePayloadUrl } as LargePayloadResponse, + // headers: { 'content-type': 'application/ref+json' }, + // config: {}, + // }; + + // globalOptions.onFetchLargePayloadFromRef.mockResolvedValueOnce(largePayloadData); + + // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; + // const result = await interceptor(largeResponse); + + // expect(result.data).toEqual(largePayloadData); + // expect(globalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); + // }); + + // it('should respect the configured size threshold when disabled', async () => { + // const localOptions: AxiosLargeResponseOptions = { + // ...globalOptions, + // enabled: false, + // }; + + // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, localOptions); + // const largeResponse = { + // data: { ref: 'some-url' } as LargePayloadResponse, + // headers: { 'content-type': 'application/ref+json' }, + // config: {}, + // }; + + // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; + // const result = await interceptor(largeResponse); + + // expect(result).toEqual(largeResponse); + // expect(localOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); + // }); + + // it('should handle errors gracefully', async () => { + // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); + // const error = new Error('Failed to fetch'); + // const largeResponse = { + // data: { ref: 'error-url' } as LargePayloadResponse, + // headers: { 'content-type': 'application/ref+json' }, + // config: {}, + // }; + + // globalOptions.onFetchLargePayloadFromRef.mockRejectedValueOnce(error); + + // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; + + // await expect(interceptor(largeResponse)).rejects.toThrow('Failed to fetch'); + // expect(globalOptions.logger.error).toHaveBeenCalledWith( + // '[LargeResponseInterceptor] Error fetching large payload from ref url', + // { reason: 'Failed to fetch' }, + // ); + // }); + + // it('should set correct request headers', async () => { + // const { requestInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); + // const config = { + // headers: {}, + // }; + + // const interceptor = axiosInstance.interceptors.request.handlers[requestInterceptorId].fulfilled; + // const result = await interceptor(config); + + // expect(result.headers.Accept).toBe(globalOptions.headerFlag); + // }); + + // it('should respect per-request options', async () => { + // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); + // const customRefProperty = 'customRef'; + // const largePayloadUrl = 'https://api.example.com/large-payload'; + + // const largeResponse = { + // data: { [customRefProperty]: largePayloadUrl }, + // headers: { 'content-type': 'application/ref+json' }, + // config: { + // [NAMESPACE]: { + // refProperty: customRefProperty, + // }, + // }, + // }; + + // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; + // await interceptor(largeResponse); + + // expect(globalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); + // }); +}); + +const getInterceptors = (axiosInstance: AxiosInstance, requestId: number, responseId: number) => { + // biome-ignore lint/suspicious/noExplicitAny: + const requestInterceptor = (axiosInstance.interceptors.request as any).handlers[requestId].fulfilled; + // biome-ignore lint/suspicious/noExplicitAny: + const responseInterceptor = (axiosInstance.interceptors.response as any).handlers[responseId].fulfilled; + + return { + requestInterceptor, + responseInterceptor, + }; +}; diff --git a/packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.ts similarity index 100% rename from packages/axios-interceptor-large-response/src/interceptor/axios-interceptor.ts rename to packages/axios-large-response/src/interceptor/axios-interceptor.ts diff --git a/packages/axios-interceptor-large-response/src/types.ts b/packages/axios-large-response/src/types.ts similarity index 100% rename from packages/axios-interceptor-large-response/src/types.ts rename to packages/axios-large-response/src/types.ts diff --git a/packages/axios-interceptor-large-response/src/utils/utils.test.ts b/packages/axios-large-response/src/utils/utils.test.ts similarity index 100% rename from packages/axios-interceptor-large-response/src/utils/utils.test.ts rename to packages/axios-large-response/src/utils/utils.test.ts diff --git a/packages/axios-interceptor-large-response/src/utils/utils.ts b/packages/axios-large-response/src/utils/utils.ts similarity index 100% rename from packages/axios-interceptor-large-response/src/utils/utils.ts rename to packages/axios-large-response/src/utils/utils.ts diff --git a/packages/axios-interceptor-large-response/tsconfig.json b/packages/axios-large-response/tsconfig.json similarity index 100% rename from packages/axios-interceptor-large-response/tsconfig.json rename to packages/axios-large-response/tsconfig.json diff --git a/packages/axios-interceptor-large-response/tsup.config.ts b/packages/axios-large-response/tsup.config.ts similarity index 100% rename from packages/axios-interceptor-large-response/tsup.config.ts rename to packages/axios-large-response/tsup.config.ts diff --git a/packages/axios-interceptor-large-response/vitest.config.mts b/packages/axios-large-response/vitest.config.mts similarity index 93% rename from packages/axios-interceptor-large-response/vitest.config.mts rename to packages/axios-large-response/vitest.config.mts index 8e84103..6de0115 100644 --- a/packages/axios-interceptor-large-response/vitest.config.mts +++ b/packages/axios-large-response/vitest.config.mts @@ -4,7 +4,7 @@ export default defineConfig({ test: { include: ['**/*.{test,spec}.ts?(x)'], exclude: ['**/node_modules/**'], - silent: true, + silent: false, watch: false, poolOptions: { threads: { From 8a9ffa6d24712b2bbded67a5aa49a20052aab790 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Fri, 14 Feb 2025 17:36:17 +0000 Subject: [PATCH 05/20] test: configure Vitest to run silently by default again --- packages/axios-large-response/vitest.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/axios-large-response/vitest.config.mts b/packages/axios-large-response/vitest.config.mts index 6de0115..8e84103 100644 --- a/packages/axios-large-response/vitest.config.mts +++ b/packages/axios-large-response/vitest.config.mts @@ -4,7 +4,7 @@ export default defineConfig({ test: { include: ['**/*.{test,spec}.ts?(x)'], exclude: ['**/node_modules/**'], - silent: false, + silent: true, watch: false, poolOptions: { threads: { From 2119429b05ee7445830f304541a8ccbde0c26fba Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Mon, 17 Feb 2025 13:20:18 +0000 Subject: [PATCH 06/20] test: expand test coverage for axios large response interceptor - Add comprehensive test cases for global and custom configuration scenarios - Verify interceptor behavior with different response types - Improve test coverage for request and response interceptors - Enhance test suite with more detailed assertions --- .../src/interceptor/axios-interceptor.test.ts | 227 ++++++++++-------- .../src/interceptor/axios-interceptor.ts | 4 +- 2 files changed, 124 insertions(+), 107 deletions(-) diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts index 227a5df..3bf34ea 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -1,37 +1,35 @@ import axios, { type AxiosInstance } from 'axios'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AxiosLargeResponseOptions } from '../types'; +import { LARGE_PAYLOAD_MIME_TYPE } from '../utils/utils'; import { axiosLargeResponse } from './axios-interceptor'; describe('axiosInterceptorLargeResponse', () => { let axiosInstance: AxiosInstance; - let globalOptions: AxiosLargeResponseOptions; + let globalOptions: Required; beforeEach(() => { axiosInstance = axios.create(); globalOptions = { enabled: true, - headerFlag: 'application/ref+json', - refProperty: 'ref', + headerFlag: LARGE_PAYLOAD_MIME_TYPE, + refProperty: '$payloadRef', debug: false, logger: { debug: vi.fn(), error: vi.fn(), }, - onFetchLargePayloadFromRef: vi.fn(), + onFetchLargePayloadFromRef: vi.fn().mockResolvedValue({ huge: 'data' }), }; }); - // biome-ignore lint/suspicious/noFocusedTests: - it.only('should allow normal responses to pass through unchanged', async () => { - // Mock request config + it('should allow normal responses to pass through unchanged', async () => { + // given const requestConfig = { - headers: {}, method: 'GET', url: 'https://api.example.com', }; - // Mock response const normalResponse = { data: { foo: 'bar' }, headers: { 'content-type': 'application/json' }, @@ -40,6 +38,7 @@ describe('axiosInterceptorLargeResponse', () => { statusText: 'OK', }; + // when const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); const { requestInterceptor, responseInterceptor } = getInterceptors( @@ -48,107 +47,125 @@ describe('axiosInterceptorLargeResponse', () => { responseInterceptorId, ); - const modifiedConfig = await requestInterceptor(requestConfig); - expect(modifiedConfig.headers.Accept).toBe(globalOptions.headerFlag); - + const modifiedRequest = await requestInterceptor(requestConfig); const result = await responseInterceptor(normalResponse); - expect(result).toEqual(normalResponse); + // then + expect(globalOptions.logger.debug).not.toHaveBeenCalled(); + expect(modifiedRequest).toEqual(requestConfig); + expect(result).toEqual(normalResponse); expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); }); - // it('should handle large responses appropriately', async () => { - // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); - // const largePayloadUrl = 'https://api.example.com/large-payload'; - // const largePayloadData = { huge: 'data' }; - - // const largeResponse = { - // data: { ref: largePayloadUrl } as LargePayloadResponse, - // headers: { 'content-type': 'application/ref+json' }, - // config: {}, - // }; - - // globalOptions.onFetchLargePayloadFromRef.mockResolvedValueOnce(largePayloadData); - - // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; - // const result = await interceptor(largeResponse); - - // expect(result.data).toEqual(largePayloadData); - // expect(globalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); - // }); - - // it('should respect the configured size threshold when disabled', async () => { - // const localOptions: AxiosLargeResponseOptions = { - // ...globalOptions, - // enabled: false, - // }; - - // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, localOptions); - // const largeResponse = { - // data: { ref: 'some-url' } as LargePayloadResponse, - // headers: { 'content-type': 'application/ref+json' }, - // config: {}, - // }; - - // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; - // const result = await interceptor(largeResponse); - - // expect(result).toEqual(largeResponse); - // expect(localOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); - // }); - - // it('should handle errors gracefully', async () => { - // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); - // const error = new Error('Failed to fetch'); - // const largeResponse = { - // data: { ref: 'error-url' } as LargePayloadResponse, - // headers: { 'content-type': 'application/ref+json' }, - // config: {}, - // }; - - // globalOptions.onFetchLargePayloadFromRef.mockRejectedValueOnce(error); - - // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; - - // await expect(interceptor(largeResponse)).rejects.toThrow('Failed to fetch'); - // expect(globalOptions.logger.error).toHaveBeenCalledWith( - // '[LargeResponseInterceptor] Error fetching large payload from ref url', - // { reason: 'Failed to fetch' }, - // ); - // }); - - // it('should set correct request headers', async () => { - // const { requestInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); - // const config = { - // headers: {}, - // }; - - // const interceptor = axiosInstance.interceptors.request.handlers[requestInterceptorId].fulfilled; - // const result = await interceptor(config); - - // expect(result.headers.Accept).toBe(globalOptions.headerFlag); - // }); - - // it('should respect per-request options', async () => { - // const { responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); - // const customRefProperty = 'customRef'; - // const largePayloadUrl = 'https://api.example.com/large-payload'; - - // const largeResponse = { - // data: { [customRefProperty]: largePayloadUrl }, - // headers: { 'content-type': 'application/ref+json' }, - // config: { - // [NAMESPACE]: { - // refProperty: customRefProperty, - // }, - // }, - // }; - - // const interceptor = axiosInstance.interceptors.response.handlers[responseInterceptorId].fulfilled; - // await interceptor(largeResponse); - - // expect(globalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); - // }); + it('should handle large responses appropriately using global options', async () => { + // given + const largePayloadUrl = 'https://api.example.com/large-payload'; + const refProperty = globalOptions.refProperty; + const headerFlag = globalOptions.headerFlag; + + const requestConfig = { + method: 'GET', + url: 'https://api.example.com', + }; + + const largeResponse = { + data: { + [refProperty]: largePayloadUrl, + }, + headers: { + 'content-type': headerFlag, + }, + status: 200, + statusText: 'OK', + }; + + // when + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); + + const { requestInterceptor, responseInterceptor } = getInterceptors( + axiosInstance, + requestInterceptorId, + responseInterceptorId, + ); + + const modifiedRequest = await requestInterceptor(requestConfig); + const result = await responseInterceptor(largeResponse); + + // then + expect(modifiedRequest).toEqual(requestConfig); + expect(globalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); + expect(result).toEqual({ + ...largeResponse, + data: { huge: 'data' }, + }); + }); + it('should handle large responses appropriately using custom global options', async () => { + // given + const largePayloadUrl = 'https://api.example.com/large-payload'; + const refProperty = '$customRef'; + const headerFlag = 'application/custom-large-response.vnd+json'; + + const requestConfig = { + method: 'GET', + url: 'https://api.example.com', + }; + + const largeResponse = { + data: { + [refProperty]: largePayloadUrl, + }, + headers: { + 'content-type': headerFlag, + }, + }; + + const customGlobalOptions = { + ...globalOptions, + headerFlag, + refProperty, + debug: true, + onFetchLargePayloadFromRef: vi.fn().mockResolvedValue({ + superBigData: { + foo: 'bar', + }, + }), + }; + + // when + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, customGlobalOptions); + + const { requestInterceptor, responseInterceptor } = getInterceptors( + axiosInstance, + requestInterceptorId, + responseInterceptorId, + ); + + const modifiedRequest = await requestInterceptor(requestConfig); + const result = await responseInterceptor(largeResponse); + + // then + expect(modifiedRequest).toEqual(requestConfig); + expect(customGlobalOptions.logger.debug).toHaveBeenCalledWith( + '[axios-large-response] Fetching large payload from ref url', + { + ref: largePayloadUrl, + }, + ); + expect(customGlobalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); + expect(result).toEqual({ + ...largeResponse, + data: { + superBigData: { + foo: 'bar', + }, + }, + }); + }); + it('should handle large responses appropriately using per-request options', async () => { + // given + // when + // then + }); }); const getInterceptors = (axiosInstance: AxiosInstance, requestId: number, responseId: number) => { diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.ts index a17c84b..eae5d69 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.ts @@ -35,14 +35,14 @@ const axiosLargeResponse: AxiosLargeResponse = (axiosInstance, globalOptions) => (response.data as LargePayloadResponse)[refProperty] ) { if (isDebugEnabled(debug)) { - logger.debug('[LargeResponseInterceptor] Fetching large payload from ref url', { + logger.debug('[axios-large-response] Fetching large payload from ref url', { ref: (response.data as LargePayloadResponse)[refProperty], }); } try { response.data = await onFetchLargePayloadFromRef((response.data as LargePayloadResponse)[refProperty]); } catch (error) { - logger.error('[LargeResponseInterceptor] Error fetching large payload from ref url', { + logger.error('[axios-large-response] Error fetching large payload from ref url', { reason: error instanceof Error ? error.message : 'unknown', }); throw error; From dcdb73c6080cac6ad87e09525c6a66160e5ad2f0 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Mon, 17 Feb 2025 13:23:12 +0000 Subject: [PATCH 07/20] test: update axios interceptor test assertions for header modification --- .../src/interceptor/axios-interceptor.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts index 3bf34ea..492618e 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -52,7 +52,7 @@ describe('axiosInterceptorLargeResponse', () => { // then expect(globalOptions.logger.debug).not.toHaveBeenCalled(); - expect(modifiedRequest).toEqual(requestConfig); + expect(modifiedRequest.headers.Accept).toEqual(globalOptions.headerFlag); expect(result).toEqual(normalResponse); expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); }); @@ -89,10 +89,11 @@ describe('axiosInterceptorLargeResponse', () => { ); const modifiedRequest = await requestInterceptor(requestConfig); + const result = await responseInterceptor(largeResponse); // then - expect(modifiedRequest).toEqual(requestConfig); + expect(modifiedRequest.headers.Accept).toEqual(headerFlag); expect(globalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); expect(result).toEqual({ ...largeResponse, @@ -144,7 +145,7 @@ describe('axiosInterceptorLargeResponse', () => { const result = await responseInterceptor(largeResponse); // then - expect(modifiedRequest).toEqual(requestConfig); + expect(modifiedRequest.headers.Accept).toEqual(headerFlag); expect(customGlobalOptions.logger.debug).toHaveBeenCalledWith( '[axios-large-response] Fetching large payload from ref url', { From edaa0a004cde15e36886e692a19c6b80d3b7d849 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Mon, 17 Feb 2025 14:58:13 +0000 Subject: [PATCH 08/20] test: enhance axios interceptor test coverage with advanced scenarios - Add test cases for per-request and global option configurations - Verify interceptor behavior with custom header flags and ref properties - Improve test suite with more nuanced option merging and handling scenarios - Expand assertions for large response interceptor functionality --- .../src/interceptor/axios-interceptor.test.ts | 178 +++++++++++++++++- 1 file changed, 175 insertions(+), 3 deletions(-) diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts index 492618e..196cc5b 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -1,13 +1,20 @@ import axios, { type AxiosInstance } from 'axios'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AxiosLargeResponseOptions } from '../types'; -import { LARGE_PAYLOAD_MIME_TYPE } from '../utils/utils'; +import { LARGE_PAYLOAD_MIME_TYPE, NAMESPACE } from '../utils/utils'; import { axiosLargeResponse } from './axios-interceptor'; +/** + * Test suite for the axiosInterceptorLargeResponse interceptor. + */ describe('axiosInterceptorLargeResponse', () => { let axiosInstance: AxiosInstance; let globalOptions: Required; + /** + * Setup the axios instance and global options. + * Before each test. + */ beforeEach(() => { axiosInstance = axios.create(); globalOptions = { @@ -23,11 +30,15 @@ describe('axiosInterceptorLargeResponse', () => { }; }); + /** + * Normal responses should pass through unchanged. + */ it('should allow normal responses to pass through unchanged', async () => { // given const requestConfig = { method: 'GET', url: 'https://api.example.com', + headers: {}, }; const normalResponse = { @@ -57,6 +68,10 @@ describe('axiosInterceptorLargeResponse', () => { expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); }); + /** + * Large responses should be fetched from the ref url and returned with the large payload. + * Using global options. + */ it('should handle large responses appropriately using global options', async () => { // given const largePayloadUrl = 'https://api.example.com/large-payload'; @@ -66,6 +81,7 @@ describe('axiosInterceptorLargeResponse', () => { const requestConfig = { method: 'GET', url: 'https://api.example.com', + headers: {}, }; const largeResponse = { @@ -77,10 +93,11 @@ describe('axiosInterceptorLargeResponse', () => { }, status: 200, statusText: 'OK', + config: requestConfig, }; // when - const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance); const { requestInterceptor, responseInterceptor } = getInterceptors( axiosInstance, @@ -100,7 +117,12 @@ describe('axiosInterceptorLargeResponse', () => { data: { huge: 'data' }, }); }); - it('should handle large responses appropriately using custom global options', async () => { + + /** + * Large responses should be fetched from the ref url and returned with the large payload. + * Using more custom global options. + */ + it('should handle large responses appropriately using more custom global options', async () => { // given const largePayloadUrl = 'https://api.example.com/large-payload'; const refProperty = '$customRef'; @@ -109,6 +131,7 @@ describe('axiosInterceptorLargeResponse', () => { const requestConfig = { method: 'GET', url: 'https://api.example.com', + headers: {}, }; const largeResponse = { @@ -118,6 +141,7 @@ describe('axiosInterceptorLargeResponse', () => { headers: { 'content-type': headerFlag, }, + config: requestConfig, }; const customGlobalOptions = { @@ -162,10 +186,158 @@ describe('axiosInterceptorLargeResponse', () => { }, }); }); + + /** + * Large responses should be fetched from the ref url and returned with the large payload. + * Using per-request options. + */ it('should handle large responses appropriately using per-request options', async () => { // given + const largePayloadUrl = 'https://api.example.com/large-payload'; + const refProperty = '$customRef'; + const headerFlag = 'application/custom-large-response.vnd+json'; + + const customGlobalOptions = { + ...globalOptions, + enabled: false, + }; + + const requestConfigEnabled = { + method: 'GET', + url: 'https://api.example.com', + [NAMESPACE]: { + enabled: true, + headerFlag, + refProperty, + debug: true, + onFetchLargePayloadFromRef: vi.fn().mockResolvedValue({ + superBigData: { + foo: 'bar', + }, + }), + }, + }; + + const requestConfigDisabled = { + method: 'GET', + url: 'https://api.example.com', + headers: {}, + }; + + const largeResponseDisabled = { + data: { + foo: 'bar', + }, + headers: { + 'content-type': 'application/json', + }, + config: requestConfigDisabled, + }; + + const largeResponseEnabled = { + headers: { + 'content-type': headerFlag, + }, + data: { + [refProperty]: largePayloadUrl, + }, + config: requestConfigEnabled, + }; + + // when + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, customGlobalOptions); + + const { requestInterceptor, responseInterceptor } = getInterceptors( + axiosInstance, + requestInterceptorId, + responseInterceptorId, + ); + + const modifiedRequestEnabled = await requestInterceptor(requestConfigEnabled); + const modifiedRequestDisabled = await requestInterceptor(requestConfigDisabled); + const resultDisabled = await responseInterceptor(largeResponseDisabled); + const resultEnabled = await responseInterceptor(largeResponseEnabled); + + // then + expect(modifiedRequestDisabled.headers.Accept).not.toEqual(headerFlag); + expect(modifiedRequestDisabled.headers.Accept).not.toEqual(globalOptions.headerFlag); + expect(resultDisabled).toEqual(largeResponseDisabled); + expect(modifiedRequestEnabled.headers.Accept).toEqual(headerFlag); + expect(resultEnabled).toEqual({ + ...largeResponseEnabled, + data: { + superBigData: { + foo: 'bar', + }, + }, + }); + expect(requestConfigEnabled[NAMESPACE].onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); + }); + + /** + * Large responses should be fetched from the ref url and returned with the large payload. + * Using merge of per-request and global options. + */ + it('should handle large responses appropriately using correct merge of per-request and global options', async () => { + // given + const largePayloadUrl = 'https://api.example.com/large-payload'; + + const customGlobalOptions = { + ...globalOptions, + refProperty: '$globalRef', + debug: true, + }; + + const requestConfig = { + method: 'GET', + url: 'https://api.example.com', + [NAMESPACE]: { + headerFlag: 'application/request-large-response.vnd+json', + debug: false, + onFetchLargePayloadFromRef: vi.fn().mockResolvedValue({ + superBigData: { + foo: 'bar', + }, + }), + }, + }; + + const largeResponse = { + data: { + [customGlobalOptions.refProperty]: largePayloadUrl, + }, + headers: { + 'content-type': requestConfig[NAMESPACE].headerFlag, + }, + config: requestConfig, + }; + // when + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, customGlobalOptions); + + const { requestInterceptor, responseInterceptor } = getInterceptors( + axiosInstance, + requestInterceptorId, + responseInterceptorId, + ); + + const modifiedRequest = await requestInterceptor(requestConfig); + const result = await responseInterceptor(largeResponse); + // then + expect(modifiedRequest.headers.Accept).toEqual(requestConfig[NAMESPACE].headerFlag); + expect(customGlobalOptions.logger.debug).not.toHaveBeenCalled(); + expect(globalOptions.logger.debug).not.toHaveBeenCalled(); + expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); + expect(requestConfig[NAMESPACE].onFetchLargePayloadFromRef).toHaveBeenCalledWith(largePayloadUrl); + expect(result).toEqual({ + ...largeResponse, + data: { + superBigData: { + foo: 'bar', + }, + }, + }); }); }); From 49e241ba34cff3f7326f53b40a5c384411f3d6c3 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Mon, 17 Feb 2025 16:09:04 +0000 Subject: [PATCH 09/20] docs: add comprehensive README for axios large response interceptor --- packages/axios-large-response/README.md | 85 +++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index e69de29..ffa166d 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -0,0 +1,85 @@ +# Axios Large Response + +Axios interceptor to handle large responses. By default it assumes that `@epilot/large-response-middleware` is used to handle the large responses in your backend as described in the [large-response-middleware README](https://github.com/epilot-dev/aws-lambda-utility-middlewares/blob/main/packages/large-response-middleware/README.md). However, it also supports custom callback function to fetch the large payload from the reference URL, custom reference property name and custom header. Please check below for more details. + +## Installation + +```bash +pnpm add @epilot/axios-large-response +npm install @epilot/axios-large-response +yarn add @epilot/axios-large-response +``` + +## Usage + +```ts +import { axiosLargeResponse } from 'axios-large-response'; +import axios from 'axios'; + +// Apply globally to an axios instance +const axiosInstance = axios.create(); + +// Example, disable interceptor globally so we enable it per-request +axiosLargeResponse(axiosInstance, { + enabled: false, + // ... other global options +}); + +// The interceptor will automatically handle large payload responses +const response = await axiosInstance.get('https://api.example.com/data'); +// If the response contains a reference, it will be automatically fetched +console.log(response.data); // The complete payload + +// Configure per-request options +const response = await axiosInstance.get('https://api.example.com/data', { + 'axios-large-response': { + enabled: true, + headerFlag: 'application/custom-large-response.vnd+json', + refProperty: '$customRef', + debug: true, + onFetchLargePayloadFromRef: async (refUrl) => { + // Custom handling for this specific request + const response = await axios.get(refUrl); + return response.data; + } + } +}); +``` + +## Options + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| enabled | Boolean | true | Enable/disable the interceptor | +| headerFlag | String | 'application/large-response.vnd+json' | Content type header indicating a large payload reference response | +| refProperty | String | '$payloadRef' | Property name containing the reference URL in the response | +| debug | Boolean | false | Enable debug logging | +| logger | Object | console | Logger object with debug() and error() methods | +| onFetchLargePayloadFromRef | Function | Fetches the reference URL and returns the full payload | Callback function to fetch the full payload from the reference URL | + +## How it works + +1. Adds the appropriate Accept header to requests to indicate large payload support; +2. Detects responses with the configured header content type; +3. If the response contains a reference in the specified refProperty, automatically fetches the full payload; +4. Returns the complete data in the response. + +Example server response for a large payload: + +```json +{ + "$payloadRef": "https://api.example.com/large-payloads/123" +} +``` + +After interceptor processing, the response becomes: + +```json +{ + "huge": "data", + "nested": { + "complex": "structure" + } +} +``` + From a6d5a7baf106fb81dee5a86e06bc72e52ff00aac Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Mon, 17 Feb 2025 16:24:18 +0000 Subject: [PATCH 10/20] feat: add per-request configuration support for axios large response interceptor --- packages/axios-large-response/README.md | 2 ++ .../src/interceptor/axios-interceptor.test.ts | 2 +- packages/axios-large-response/src/utils/utils.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index ffa166d..b9b591e 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -2,6 +2,8 @@ Axios interceptor to handle large responses. By default it assumes that `@epilot/large-response-middleware` is used to handle the large responses in your backend as described in the [large-response-middleware README](https://github.com/epilot-dev/aws-lambda-utility-middlewares/blob/main/packages/large-response-middleware/README.md). However, it also supports custom callback function to fetch the large payload from the reference URL, custom reference property name and custom header. Please check below for more details. +This interceptor supports also per-request options, so you can enable/disable the interceptor for a specific request (the namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, this intercepter is enabled by default, however we can disable it globally and enable it per-request if needed, for example. + ## Installation ```bash diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts index 196cc5b..110c601 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -97,7 +97,7 @@ describe('axiosInterceptorLargeResponse', () => { }; // when - const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance); + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse(axiosInstance, globalOptions); const { requestInterceptor, responseInterceptor } = getInterceptors( axiosInstance, diff --git a/packages/axios-large-response/src/utils/utils.ts b/packages/axios-large-response/src/utils/utils.ts index 2f7bd64..14bbef1 100644 --- a/packages/axios-large-response/src/utils/utils.ts +++ b/packages/axios-large-response/src/utils/utils.ts @@ -40,6 +40,6 @@ const getOptions = (configRequestOptions?: AxiosLargeResponseOptions, globalOpti } satisfies AxiosLargeResponseOptions; }; -const NAMESPACE = 'largeResponse'; +const NAMESPACE = 'axios-large-response'; export { fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled, LARGE_PAYLOAD_MIME_TYPE, NAMESPACE }; From f67c070d7cf5dfff1c72b9659502327d1f733ff6 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 18 Feb 2025 10:24:06 +0000 Subject: [PATCH 11/20] chore: update package name and version for axios large response interceptor --- packages/axios-large-response/README.md | 2 +- packages/axios-large-response/package.json | 2 +- packages/axios-large-response/src/index.ts | 2 +- .../src/interceptor/axios-interceptor.test.ts | 4 ++-- pnpm-lock.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index b9b591e..b6931df 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -2,7 +2,7 @@ Axios interceptor to handle large responses. By default it assumes that `@epilot/large-response-middleware` is used to handle the large responses in your backend as described in the [large-response-middleware README](https://github.com/epilot-dev/aws-lambda-utility-middlewares/blob/main/packages/large-response-middleware/README.md). However, it also supports custom callback function to fetch the large payload from the reference URL, custom reference property name and custom header. Please check below for more details. -This interceptor supports also per-request options, so you can enable/disable the interceptor for a specific request (the namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, this intercepter is enabled by default, however we can disable it globally and enable it per-request if needed, for example. +This interceptor supports also per-request options, so you can enable/disable the interceptor for a specific request (the namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, it is enabled by default, however we can do som combinations based on the use cases, for example, we can disable it globally and enable it per-request if needed. ## Installation diff --git a/packages/axios-large-response/package.json b/packages/axios-large-response/package.json index c22abc2..beff701 100644 --- a/packages/axios-large-response/package.json +++ b/packages/axios-large-response/package.json @@ -1,6 +1,6 @@ { "name": "@epilot/axios-large-response", - "version": "0.0.1", + "version": "0.0.1-rc.0", "main": "dist/index.js", "types": "dist/index.d.ts", "module": "dist/index.mjs", diff --git a/packages/axios-large-response/src/index.ts b/packages/axios-large-response/src/index.ts index 0d8729c..b29125a 100644 --- a/packages/axios-large-response/src/index.ts +++ b/packages/axios-large-response/src/index.ts @@ -1,2 +1,2 @@ -export { axiosInterceptorLargeResponse } from './interceptor/axios-interceptor'; +export { axiosLargeResponse } from './interceptor/axios-interceptor'; export * from './types'; diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts index 110c601..a48d834 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -5,9 +5,9 @@ import { LARGE_PAYLOAD_MIME_TYPE, NAMESPACE } from '../utils/utils'; import { axiosLargeResponse } from './axios-interceptor'; /** - * Test suite for the axiosInterceptorLargeResponse interceptor. + * Test suite for the axiosLargeResponse interceptor. */ -describe('axiosInterceptorLargeResponse', () => { +describe('axiosLargeResponse', () => { let axiosInstance: AxiosInstance; let globalOptions: Required; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 743ec58..abc22f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ importers: specifier: ^5.3.3 version: 5.3.3 - packages/axios-interceptor-large-response: + packages/axios-large-response: devDependencies: axios: specifier: ^1.6.2 From f85d705710df7c732551a59b8449686f4e19a2f1 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 18 Feb 2025 13:27:51 +0000 Subject: [PATCH 12/20] feat: add error payload handling for large response interceptor + bump new release version --- packages/axios-large-response/README.md | 1 + packages/axios-large-response/package.json | 2 +- .../src/interceptor/axios-interceptor.test.ts | 96 +++++++++++++++++++ .../src/interceptor/axios-interceptor.ts | 9 +- packages/axios-large-response/src/types.ts | 1 + .../axios-large-response/src/utils/utils.ts | 1 + 6 files changed, 108 insertions(+), 2 deletions(-) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index b6931df..facbeed 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -58,6 +58,7 @@ const response = await axiosInstance.get('https://api.example.com/data', { | debug | Boolean | false | Enable debug logging | | logger | Object | console | Logger object with debug() and error() methods | | onFetchLargePayloadFromRef | Function | Fetches the reference URL and returns the full payload | Callback function to fetch the full payload from the reference URL | +| errorPayload | Unknown/Any | undefined | Error payload to return if the reference URL is not found or something goes wrong - this will be returned in the response data instead of throwing an error | ## How it works diff --git a/packages/axios-large-response/package.json b/packages/axios-large-response/package.json index beff701..c22abc2 100644 --- a/packages/axios-large-response/package.json +++ b/packages/axios-large-response/package.json @@ -1,6 +1,6 @@ { "name": "@epilot/axios-large-response", - "version": "0.0.1-rc.0", + "version": "0.0.1", "main": "dist/index.js", "types": "dist/index.d.ts", "module": "dist/index.mjs", diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts index a48d834..8f48818 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -27,6 +27,7 @@ describe('axiosLargeResponse', () => { error: vi.fn(), }, onFetchLargePayloadFromRef: vi.fn().mockResolvedValue({ huge: 'data' }), + errorPayload: undefined, }; }); @@ -118,6 +119,101 @@ describe('axiosLargeResponse', () => { }); }); + /** + * Large responses errors should be handled appropriately using global options with error payload. + */ + it('should handle large responses errors appropriately using global options with error payload', async () => { + // given + const largePayloadUrl = 'https://api.example.com/large-payload'; + const refProperty = globalOptions.refProperty; + const headerFlag = globalOptions.headerFlag; + + const requestConfig = { + method: 'GET', + url: 'https://api.example.com', + headers: {}, + }; + + const largeResponse = { + data: { + [refProperty]: largePayloadUrl, + }, + headers: { 'content-type': headerFlag }, + status: 200, + statusText: 'OK', + config: requestConfig, + }; + + const globalOptionsWithErrorPayload = { + ...globalOptions, + errorPayload: { data: [], hits: 0 }, + onFetchLargePayloadFromRef: vi.fn().mockRejectedValue(new Error('Forced error to test error payload')), + }; + + // when + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse( + axiosInstance, + globalOptionsWithErrorPayload, + ); + + const { requestInterceptor, responseInterceptor } = getInterceptors( + axiosInstance, + requestInterceptorId, + responseInterceptorId, + ); + + const modifiedRequest = await requestInterceptor(requestConfig); + const result = await responseInterceptor(largeResponse); + + // then + expect(modifiedRequest.headers.Accept).toEqual(headerFlag); + expect(result).toEqual({ ...largeResponse, data: { data: [], hits: 0 } }); + expect(globalOptionsWithErrorPayload.logger.error).toHaveBeenCalledWith( + '[axios-large-response] Error fetching large payload from ref url', + { + reason: 'Forced error to test error payload', + }, + ); + }); + + /** + * Should throw error if no error payload is provided. + */ + it('Should throw error if no error payload is provided', async () => { + // given + const globalOptionsWithoutErrorPayload = { + ...globalOptions, + errorPayload: undefined, + onFetchLargePayloadFromRef: vi.fn().mockRejectedValue(new Error('Forced error to test error propagation')), + }; + + const largeResponse = { + data: { + [globalOptionsWithoutErrorPayload.refProperty]: 'https://api.example.com/large-payload', + }, + headers: { 'content-type': globalOptionsWithoutErrorPayload.headerFlag }, + status: 200, + statusText: 'OK', + }; + // when + const { requestInterceptorId, responseInterceptorId } = axiosLargeResponse( + axiosInstance, + globalOptionsWithoutErrorPayload, + ); + + const { responseInterceptor } = getInterceptors(axiosInstance, requestInterceptorId, responseInterceptorId); + + // then + await expect(responseInterceptor(largeResponse)).rejects.toThrow('Forced error to test error propagation'); + + expect(globalOptionsWithoutErrorPayload.logger.error).toHaveBeenCalledWith( + '[axios-large-response] Error fetching large payload from ref url', + { + reason: 'Forced error to test error propagation', + }, + ); + }); + /** * Large responses should be fetched from the ref url and returned with the large payload. * Using more custom global options. diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.ts index eae5d69..17f7f6e 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.ts @@ -20,7 +20,7 @@ const axiosLargeResponse: AxiosLargeResponse = (axiosInstance, globalOptions) => const responseInterceptorId = axiosInstance.interceptors.response.use(async (response) => { const configRequestOptions = response?.config?.[NAMESPACE]; - const { debug, logger, headerFlag, refProperty, onFetchLargePayloadFromRef, enabled } = getOptions( + const { debug, logger, headerFlag, refProperty, onFetchLargePayloadFromRef, enabled, errorPayload } = getOptions( configRequestOptions, globalOptions, ); @@ -45,6 +45,13 @@ const axiosLargeResponse: AxiosLargeResponse = (axiosInstance, globalOptions) => logger.error('[axios-large-response] Error fetching large payload from ref url', { reason: error instanceof Error ? error.message : 'unknown', }); + + if (errorPayload) { + response.data = errorPayload; + + return response; + } + throw error; } } diff --git a/packages/axios-large-response/src/types.ts b/packages/axios-large-response/src/types.ts index 9ed1a46..74c1f0f 100644 --- a/packages/axios-large-response/src/types.ts +++ b/packages/axios-large-response/src/types.ts @@ -16,6 +16,7 @@ type AxiosLargeResponseOptions = { headerFlag?: string; refProperty?: string; onFetchLargePayloadFromRef?: (ref: string) => Promise; + errorPayload?: unknown; }; type AxiosLargeResponse = ( diff --git a/packages/axios-large-response/src/utils/utils.ts b/packages/axios-large-response/src/utils/utils.ts index 14bbef1..aa3e47d 100644 --- a/packages/axios-large-response/src/utils/utils.ts +++ b/packages/axios-large-response/src/utils/utils.ts @@ -25,6 +25,7 @@ export const DEFAULT_OPTIONS: Required = { headerFlag: LARGE_PAYLOAD_MIME_TYPE, refProperty: '$payload_ref', onFetchLargePayloadFromRef: fetchLargePayloadFromS3Ref, + errorPayload: undefined, }; /** From 19bb4764ccdec6e0a0c674e90e53077dcee789ec Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 18 Feb 2025 13:39:44 +0000 Subject: [PATCH 13/20] chore: update package keywords and import statement in README --- packages/axios-large-response/README.md | 2 +- packages/axios-large-response/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index facbeed..88b9e85 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -15,7 +15,7 @@ yarn add @epilot/axios-large-response ## Usage ```ts -import { axiosLargeResponse } from 'axios-large-response'; +import { axiosLargeResponse } from '@epilot/axios-large-response'; import axios from 'axios'; // Apply globally to an axios instance diff --git a/packages/axios-large-response/package.json b/packages/axios-large-response/package.json index c22abc2..a79ba18 100644 --- a/packages/axios-large-response/package.json +++ b/packages/axios-large-response/package.json @@ -11,7 +11,7 @@ "directory": "packages/axios-large-response" }, "description": "Axios plugin to intercept large responses", - "keywords": ["large-response-middleware", "interceptor", "axios", "large-response", "plugin"], + "keywords": ["axios-large-response", "large-response-middleware", "interceptor", "axios", "large-response", "plugin"], "contributors": ["Alexandre Marques(https://github.com/alexmarqs)"], "scripts": { "build": "tsup", From 4b391a61fc1f30ca3721e08527d0df714679def6 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 18 Feb 2025 16:47:55 +0000 Subject: [PATCH 14/20] docs: improve README with clearer description and add debug logging option --- packages/axios-large-response/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index 88b9e85..53f75f1 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -1,8 +1,8 @@ # Axios Large Response -Axios interceptor to handle large responses. By default it assumes that `@epilot/large-response-middleware` is used to handle the large responses in your backend as described in the [large-response-middleware README](https://github.com/epilot-dev/aws-lambda-utility-middlewares/blob/main/packages/large-response-middleware/README.md). However, it also supports custom callback function to fetch the large payload from the reference URL, custom reference property name and custom header. Please check below for more details. +An Axios interceptor designed to handle large responses. By default, it assumes that your backend uses `@epilot/large-response-middleware`, as described in the [large-response-middleware README](https://github.com/epilot-dev/aws-lambda-utility-middlewares/blob/main/packages/large-response-middleware/README.md). However, it also supports custom callback function to fetch large payloads from a reference data, customizable reference property names, headers, and other options. See below for details. -This interceptor supports also per-request options, so you can enable/disable the interceptor for a specific request (the namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, it is enabled by default, however we can do som combinations based on the use cases, for example, we can disable it globally and enable it per-request if needed. +It supports per-request options, so you can enable/disable the interceptor for a specific request (the axios config namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, it is enabled by default, however we can do some combinations based on the use cases, for example, we can disable it globally and enable it per-request if needed. ## Installation @@ -60,6 +60,8 @@ const response = await axiosInstance.get('https://api.example.com/data', { | onFetchLargePayloadFromRef | Function | Fetches the reference URL and returns the full payload | Callback function to fetch the full payload from the reference URL | | errorPayload | Unknown/Any | undefined | Error payload to return if the reference URL is not found or something goes wrong - this will be returned in the response data instead of throwing an error | +For debug purposes, you can also set the `AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG` environment variable to `true` or `1` to enable debug logging. + ## How it works 1. Adds the appropriate Accept header to requests to indicate large payload support; From c15eca62054a3ff7926b0c268b2c9c13427e21d5 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Thu, 27 Feb 2025 15:15:24 +0000 Subject: [PATCH 15/20] docs: add axios large response interceptor reference to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dfc958b..31ac441 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This repository is designed to host a collection of useful AWS Lambda Utilities ## Middlewares -- [Large Response Middleware](./packages/large-response-middleware/): AWS Lambda has a known limitation regarding the payload size of responses, which is currently set at 6MB. This middleware allows a service to log and save large responses to an S3 bucket, enabling developers to investigate the causes of such large responses. Furthermore, this middleware accepts a special header that allows the rewriting of the response with a $ref pointing to the large payload stored in S3, enabling clients to recover gracefully. +- [Large Response Middleware](./packages/large-response-middleware/): AWS Lambda has a known limitation regarding the payload size of responses, which is currently set at 6MB. This middleware allows a service to log and save large responses to an S3 bucket, enabling developers to investigate the causes of such large responses. Furthermore, this middleware accepts a special header that allows the rewriting of the response with a $ref pointing to the large payload stored in S3, enabling clients to recover gracefully. If you are using `axios` in the client, feel free to try our interceptor [axios-large-response](./packages/axios-large-response/) allowing you to intercept large responses and easily fetch them from S3 with minimal effort. - [Lambda Server-Timing Middleware (ext)](https://github.com/NishuGoel/lambda-server-timing/tree/main/src): Enables Lambdas to return responses with Server-Timing Header allowing to to pass request-specific timings from the backend to the browser. Allows a server to communicate performance metrics about the request-response cycle to the user agent. It also standardizes a JavaScript interface to enable applications to collect, process, and act on these metrics to optimize application delivery. From 29c2168443e5f230032939b59577766f26a90ce9 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Thu, 27 Feb 2025 15:19:03 +0000 Subject: [PATCH 16/20] chore: improve README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31ac441..74eeba6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ This repository is designed to host a collection of useful AWS Lambda Utilities ## Middlewares -- [Large Response Middleware](./packages/large-response-middleware/): AWS Lambda has a known limitation regarding the payload size of responses, which is currently set at 6MB. This middleware allows a service to log and save large responses to an S3 bucket, enabling developers to investigate the causes of such large responses. Furthermore, this middleware accepts a special header that allows the rewriting of the response with a $ref pointing to the large payload stored in S3, enabling clients to recover gracefully. If you are using `axios` in the client, feel free to try our interceptor [axios-large-response](./packages/axios-large-response/) allowing you to intercept large responses and easily fetch them from S3 with minimal effort. +- [Large Response Middleware](./packages/large-response-middleware/): AWS Lambda has a known limitation regarding the payload size of responses, which is currently set at 6MB. This middleware allows a service to log and save large responses to an S3 bucket, enabling developers to investigate the causes of such large responses. Furthermore, this middleware accepts a special header that allows the rewriting of the response with a $ref pointing to the large payload stored in S3, enabling clients to recover gracefully. + + - If you are using `axios` in the client, feel free to try our interceptor [Axios Large Response Plugin](./packages/axios-large-response/) allowing you to intercept large responses and easily fetch them from S3 with minimal effort. - [Lambda Server-Timing Middleware (ext)](https://github.com/NishuGoel/lambda-server-timing/tree/main/src): Enables Lambdas to return responses with Server-Timing Header allowing to to pass request-specific timings from the backend to the browser. Allows a server to communicate performance metrics about the request-response cycle to the user agent. It also standardizes a JavaScript interface to enable applications to collect, process, and act on these metrics to optimize application delivery. From 1feecec46073f6c7c2e224290bb9c055564b8628 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Thu, 27 Feb 2025 15:20:53 +0000 Subject: [PATCH 17/20] docs: update Axios Large Response link text in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74eeba6..81e33e0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This repository is designed to host a collection of useful AWS Lambda Utilities - [Large Response Middleware](./packages/large-response-middleware/): AWS Lambda has a known limitation regarding the payload size of responses, which is currently set at 6MB. This middleware allows a service to log and save large responses to an S3 bucket, enabling developers to investigate the causes of such large responses. Furthermore, this middleware accepts a special header that allows the rewriting of the response with a $ref pointing to the large payload stored in S3, enabling clients to recover gracefully. - - If you are using `axios` in the client, feel free to try our interceptor [Axios Large Response Plugin](./packages/axios-large-response/) allowing you to intercept large responses and easily fetch them from S3 with minimal effort. + - If you are using `axios` in the client, feel free to try our interceptor [Axios Large Response](./packages/axios-large-response/) allowing you to intercept large responses and easily fetch them from S3 with minimal effort. - [Lambda Server-Timing Middleware (ext)](https://github.com/NishuGoel/lambda-server-timing/tree/main/src): Enables Lambdas to return responses with Server-Timing Header allowing to to pass request-specific timings from the backend to the browser. Allows a server to communicate performance metrics about the request-response cycle to the user agent. It also standardizes a JavaScript interface to enable applications to collect, process, and act on these metrics to optimize application delivery. From 038336606213bd564b1a24bee8db15a0d392ff25 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Wed, 5 Mar 2025 13:31:33 +0000 Subject: [PATCH 18/20] feat: update axios large response interceptor to be disabled by default - Add `disableWarnings` option to control usage warnings - Change default `enabled` option to `false` - Implement usage warnings for unset interceptor configuration - Update types and documentation to reflect new default behavior - Enhance flexibility of interceptor configuration --- packages/axios-large-response/README.md | 31 +++++++---- .../src/interceptor/axios-interceptor.test.ts | 52 +++++++++++++++++++ .../src/interceptor/axios-interceptor.ts | 11 ++-- packages/axios-large-response/src/types.ts | 17 +++++- .../src/utils/utils.test.ts | 25 +++++++-- .../axios-large-response/src/utils/utils.ts | 29 +++++++++-- 6 files changed, 142 insertions(+), 23 deletions(-) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index 53f75f1..b94eba1 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -4,6 +4,8 @@ An Axios interceptor designed to handle large responses. By default, it assumes It supports per-request options, so you can enable/disable the interceptor for a specific request (the axios config namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, it is enabled by default, however we can do some combinations based on the use cases, for example, we can disable it globally and enable it per-request if needed. +The interceptor is **disabled by default**, so you need to explicitly enable it. + ## Installation ```bash @@ -18,21 +20,15 @@ yarn add @epilot/axios-large-response import { axiosLargeResponse } from '@epilot/axios-large-response'; import axios from 'axios'; -// Apply globally to an axios instance +// Axios instance const axiosInstance = axios.create(); -// Example, disable interceptor globally so we enable it per-request +// Example 1: disable interceptor globally so we enable it per-request axiosLargeResponse(axiosInstance, { - enabled: false, + // enabled: false, -> disabled by default // ... other global options }); - -// The interceptor will automatically handle large payload responses -const response = await axiosInstance.get('https://api.example.com/data'); -// If the response contains a reference, it will be automatically fetched -console.log(response.data); // The complete payload - -// Configure per-request options +... const response = await axiosInstance.get('https://api.example.com/data', { 'axios-large-response': { enabled: true, @@ -46,19 +42,32 @@ const response = await axiosInstance.get('https://api.example.com/data', { } } }); + +// Example 2: enable interceptor globally so we disable it per-request +axiosLargeResponse(axiosInstance, { + enabled: true, + // ... other global options +}); +... +const response = await axiosInstance.get('https://api.example.com/data', { + 'axios-large-response': { + enabled: false + } +}); ``` ## Options | Name | Type | Default | Description | |------|------|---------|-------------| -| enabled | Boolean | true | Enable/disable the interceptor | +| enabled | Boolean | false | Enable/disable the interceptor | | headerFlag | String | 'application/large-response.vnd+json' | Content type header indicating a large payload reference response | | refProperty | String | '$payloadRef' | Property name containing the reference URL in the response | | debug | Boolean | false | Enable debug logging | | logger | Object | console | Logger object with debug() and error() methods | | onFetchLargePayloadFromRef | Function | Fetches the reference URL and returns the full payload | Callback function to fetch the full payload from the reference URL | | errorPayload | Unknown/Any | undefined | Error payload to return if the reference URL is not found or something goes wrong - this will be returned in the response data instead of throwing an error | +| disableWarnings | Boolean | false | Disable warnings, only available globally in the options | For debug purposes, you can also set the `AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG` environment variable to `true` or `1` to enable debug logging. diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts index 8f48818..23dda47 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.test.ts @@ -19,12 +19,14 @@ describe('axiosLargeResponse', () => { axiosInstance = axios.create(); globalOptions = { enabled: true, + disableWarnings: false, headerFlag: LARGE_PAYLOAD_MIME_TYPE, refProperty: '$payloadRef', debug: false, logger: { debug: vi.fn(), error: vi.fn(), + warn: vi.fn(), }, onFetchLargePayloadFromRef: vi.fn().mockResolvedValue({ huge: 'data' }), errorPayload: undefined, @@ -69,6 +71,56 @@ describe('axiosLargeResponse', () => { expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); }); + /** + * Warnings should be logged if the disableWarnings option is false (default) and enabled is not explicitly set + */ + it('should log warnings if the disableWarnings option is false and enabled is not explicitly set', async () => { + // given + // when + axiosLargeResponse(axiosInstance, { + ...globalOptions, + enabled: undefined, + }); + + // then + expect(globalOptions.logger.warn).toHaveBeenCalledWith( + `[axios-large-response] By default the interceptor is globally disabled (enabled = false). Please make sure you explicitly set the enabled option. + To mute warnings, set globally the disableWarnings option to true.`, + ); + }); + + /** + * Warnings should not be logged if the disableWarnings option is true and enabled is not explicitly set + */ + it('should not log warnings if the disableWarnings option is true and enabled is not explicitly set', async () => { + // given + const globalOptionsWithDisableWarnings = { + ...globalOptions, + disableWarnings: true, + enabled: undefined, + }; + + // when + axiosLargeResponse(axiosInstance, globalOptionsWithDisableWarnings); + + // then + expect(globalOptions.logger.warn).not.toHaveBeenCalled(); + }); + + /** + * Warnings should not be logged if enabled is explicitly set + */ + it('should not log warnings if enabled is explicitly set', async () => { + // given + const globalOptionsWithDisableWarnings = { ...globalOptions, disableWarnings: false, enabled: false }; + + // when + axiosLargeResponse(axiosInstance, globalOptionsWithDisableWarnings); + + // then + expect(globalOptions.logger.warn).not.toHaveBeenCalled(); + }); + /** * Large responses should be fetched from the ref url and returned with the large payload. * Using global options. diff --git a/packages/axios-large-response/src/interceptor/axios-interceptor.ts b/packages/axios-large-response/src/interceptor/axios-interceptor.ts index 17f7f6e..37721eb 100644 --- a/packages/axios-large-response/src/interceptor/axios-interceptor.ts +++ b/packages/axios-large-response/src/interceptor/axios-interceptor.ts @@ -1,10 +1,14 @@ -import type { AxiosLargeResponse, AxiosLargeResponseOptions, LargePayloadResponse } from '../types'; -import { NAMESPACE, getOptions, isDebugEnabled } from '../utils/utils'; +import type { AxiosLargeResponse, AxiosLargeResponseRequestOptions, LargePayloadResponse } from '../types'; +import { NAMESPACE, getOptions, isDebugEnabled, usageWarnings } from '../utils/utils'; /** * This is the main function that adds the interceptors to the axios instance. */ const axiosLargeResponse: AxiosLargeResponse = (axiosInstance, globalOptions) => { + // check for warnings + usageWarnings(globalOptions); + + // request interceptor const requestInterceptorId = axiosInstance.interceptors.request.use((config) => { const { headerFlag, enabled } = getOptions(config?.[NAMESPACE], globalOptions); @@ -18,6 +22,7 @@ const axiosLargeResponse: AxiosLargeResponse = (axiosInstance, globalOptions) => return config; }); + // response interceptor const responseInterceptorId = axiosInstance.interceptors.response.use(async (response) => { const configRequestOptions = response?.config?.[NAMESPACE]; const { debug, logger, headerFlag, refProperty, onFetchLargePayloadFromRef, enabled, errorPayload } = getOptions( @@ -70,7 +75,7 @@ const axiosLargeResponse: AxiosLargeResponse = (axiosInstance, globalOptions) => */ declare module 'axios' { export interface AxiosRequestConfig { - [NAMESPACE]?: AxiosLargeResponseOptions; + [NAMESPACE]?: AxiosLargeResponseRequestOptions; } } diff --git a/packages/axios-large-response/src/types.ts b/packages/axios-large-response/src/types.ts index 74c1f0f..a32c9a3 100644 --- a/packages/axios-large-response/src/types.ts +++ b/packages/axios-large-response/src/types.ts @@ -7,9 +7,16 @@ type LargePayloadResponse = { type Logger = { debug: (message: string, ...args: unknown[]) => void; error: (message: string, ...args: unknown[]) => void; + warn: (message: string, ...args: unknown[]) => void; }; -type AxiosLargeResponseOptions = { +type AxiosLargeResponseOptions = BaseAxiosLargeResponseOptions & { + disableWarnings?: boolean; +}; + +type AxiosLargeResponseRequestOptions = BaseAxiosLargeResponseOptions; + +type BaseAxiosLargeResponseOptions = { enabled?: boolean; debug?: boolean; logger?: Logger; @@ -27,4 +34,10 @@ type AxiosLargeResponse = ( responseInterceptorId: number; }; -export type { AxiosLargeResponse, AxiosLargeResponseOptions, LargePayloadResponse, Logger }; +export type { + AxiosLargeResponse, + AxiosLargeResponseOptions, + AxiosLargeResponseRequestOptions, + LargePayloadResponse, + Logger, +}; diff --git a/packages/axios-large-response/src/utils/utils.test.ts b/packages/axios-large-response/src/utils/utils.test.ts index 7fcb732..e81b2fa 100644 --- a/packages/axios-large-response/src/utils/utils.test.ts +++ b/packages/axios-large-response/src/utils/utils.test.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { AxiosLargeResponseOptions } from '../types'; -import { DEFAULT_OPTIONS, fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled } from './utils'; +import type { AxiosLargeResponseOptions, AxiosLargeResponseRequestOptions } from '../types'; +import { DEFAULT_OPTIONS, fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled, usageWarnings } from './utils'; vi.mock('axios'); const mockedAxios = vi.mocked(axios, true); @@ -65,12 +65,13 @@ describe('getOptions', () => { const configRequestOptions = { enabled: false, onFetchLargePayloadFromRef: customOnFetchLargePayloadFromRef, - } satisfies AxiosLargeResponseOptions; + } satisfies AxiosLargeResponseRequestOptions; const options = getOptions(configRequestOptions, globalOptions); expect(options).toEqual({ enabled: false, debug: false, + disableWarnings: false, logger: console, headerFlag: 'application/json', refProperty: '$payload_ref', @@ -83,3 +84,21 @@ describe('getOptions', () => { expect(options).toEqual(DEFAULT_OPTIONS); }); }); + +describe('usageWarnings', () => { + const consoleSpy = vi.spyOn(console, 'warn'); + + beforeEach(() => { + consoleSpy.mockClear(); + }); + + it('should not log warnings if the disableWarnings option is true', () => { + usageWarnings({ disableWarnings: true }); + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it('should log warnings if the disableWarnings option is false', () => { + usageWarnings({ disableWarnings: false }); + expect(consoleSpy).toHaveBeenCalled(); + }); +}); diff --git a/packages/axios-large-response/src/utils/utils.ts b/packages/axios-large-response/src/utils/utils.ts index aa3e47d..5ae33b9 100644 --- a/packages/axios-large-response/src/utils/utils.ts +++ b/packages/axios-large-response/src/utils/utils.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import type { AxiosLargeResponseOptions } from '../types'; +import type { AxiosLargeResponseOptions, AxiosLargeResponseRequestOptions } from '../types'; const DEBUG_ENV_VAR = 'AXIOS_INTERCEPTOR_LARGE_RESPONSE_DEBUG'; @@ -19,13 +19,14 @@ const isDebugEnabled = (manualDebug?: boolean) => { }; export const DEFAULT_OPTIONS: Required = { - enabled: true, + enabled: false, // disabled by default debug: false, logger: console, headerFlag: LARGE_PAYLOAD_MIME_TYPE, refProperty: '$payload_ref', onFetchLargePayloadFromRef: fetchLargePayloadFromS3Ref, errorPayload: undefined, + disableWarnings: false, }; /** @@ -33,7 +34,10 @@ export const DEFAULT_OPTIONS: Required = { * If the config request options are not provided, it will use the global options. * If the config request options are provided, it will use the config request options. */ -const getOptions = (configRequestOptions?: AxiosLargeResponseOptions, globalOptions?: AxiosLargeResponseOptions) => { +const getOptions = ( + configRequestOptions?: AxiosLargeResponseRequestOptions, + globalOptions?: AxiosLargeResponseOptions, +) => { return { ...DEFAULT_OPTIONS, ...globalOptions, @@ -43,4 +47,21 @@ const getOptions = (configRequestOptions?: AxiosLargeResponseOptions, globalOpti const NAMESPACE = 'axios-large-response'; -export { fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled, LARGE_PAYLOAD_MIME_TYPE, NAMESPACE }; +export { LARGE_PAYLOAD_MIME_TYPE, NAMESPACE, fetchLargePayloadFromS3Ref, getOptions, isDebugEnabled }; + +export const usageWarnings = (options: AxiosLargeResponseOptions | undefined) => { + if (options?.disableWarnings) { + return; + } + + if (typeof options?.enabled !== 'boolean') { + const logger = options?.logger || console; + + logger.warn( + `[axios-large-response] By default the interceptor is globally disabled (enabled = false). Please make sure you explicitly set the enabled option. + To mute warnings, set globally the disableWarnings option to true.`, + ); + } + + // insert here other warnings when needed +}; From 4801ccdac9536cc009c23493d6fd9827194c8c35 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Thu, 6 Mar 2025 16:55:38 +0000 Subject: [PATCH 19/20] Update packages/axios-large-response/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Pinho --- packages/axios-large-response/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index b94eba1..30277ab 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -2,7 +2,7 @@ An Axios interceptor designed to handle large responses. By default, it assumes that your backend uses `@epilot/large-response-middleware`, as described in the [large-response-middleware README](https://github.com/epilot-dev/aws-lambda-utility-middlewares/blob/main/packages/large-response-middleware/README.md). However, it also supports custom callback function to fetch large payloads from a reference data, customizable reference property names, headers, and other options. See below for details. -It supports per-request options, so you can enable/disable the interceptor for a specific request (the axios config namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, it is enabled by default, however we can do some combinations based on the use cases, for example, we can disable it globally and enable it per-request if needed. +It supports per-request options, so you can enable/disable the interceptor for a specific request (the axios config namespace is `axios-large-response` - please check the [Usage](#usage) section for more details). For now, it is disabled by default, however we can do some combinations based on the use cases, for example, we can disable it globally and enable it per-request if needed. The interceptor is **disabled by default**, so you need to explicitly enable it. From 201f5e25e340c0dc68da8bc12e87117bb2579f62 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Thu, 6 Mar 2025 17:07:45 +0000 Subject: [PATCH 20/20] chore: bump package version to 0.0.2 --- packages/axios-large-response/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/axios-large-response/package.json b/packages/axios-large-response/package.json index a79ba18..f4cb9de 100644 --- a/packages/axios-large-response/package.json +++ b/packages/axios-large-response/package.json @@ -1,6 +1,6 @@ { "name": "@epilot/axios-large-response", - "version": "0.0.1", + "version": "0.0.2", "main": "dist/index.js", "types": "dist/index.d.ts", "module": "dist/index.mjs",