├── .nvmrc ├── .eslintignore ├── .github ├── CODEOWNERS └── workflows │ ├── build.yml │ └── release.yml ├── .eslintrc.json ├── .gitignore ├── src ├── main.ts ├── utils.ts ├── __tests__ │ ├── utils.test.ts │ └── index.test.ts ├── Cache.ts └── index.ts ├── .npmignore ├── .editorconfig ├── demo ├── package.json ├── index.html ├── index.esm.js ├── index.js └── package-lock.json ├── rollup.config.js ├── Makefile ├── tsconfig.json ├── LICENSE ├── .scripts └── publish.sh ├── README.md └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.13.0 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @raulanatol 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cowcoders/eslint-config/ts" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .dts/ 4 | stats.html 5 | devs.sh 6 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | export * from './index'; 2 | export * from './Cache'; 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | config/ 4 | .scripts/ 5 | .idea/ 6 | tsconfig.json 7 | .editorconfig 8 | .dts 9 | .eslintignore 10 | .eslintrc.json 11 | .nvmrc 12 | Makefile 13 | demo 14 | .github 15 | stats.html 16 | vite.config.js 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Init project 10 | run: make init 11 | - name: Build and tests 12 | run: make 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v[0-9]+.[0-9]+.* 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Github Actions Releaser 12 | id: actions_releaser 13 | uses: raulanatol/github-actions-releaser@v2.1.0 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "start-browser": "webserver.local" 9 | }, 10 | "type": "module", 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "axios": "0.24.0", 16 | "axios-etag-cache": "1.2.1" 17 | }, 18 | "devDependencies": { 19 | "vite": "2.9.16" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rollup'; 2 | import typescript from 'rollup-plugin-typescript2'; 3 | import autoExternal from 'rollup-plugin-auto-external'; 4 | 5 | export default defineConfig({ 6 | input: 'src/main.ts', 7 | output: [ 8 | { 9 | format: 'cjs', 10 | file: 'dist/index.js', 11 | sourcemap: true 12 | }, 13 | { 14 | format: 'esm', 15 | file: 'dist/index.esm.js', 16 | sourcemap: true 17 | } 18 | ], 19 | plugins: [typescript(), autoExternal()] 20 | }); 21 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | axios etag cache 6 | 7 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := build 2 | 3 | init: 4 | @echo "🏃‍♀️ Starting project..." 5 | @npm install 6 | @cd demo && npm install 7 | 8 | clean: 9 | @echo "🛁 Cleaning..." 10 | @npm run clean 11 | 12 | clean_all: 13 | @echo "🧨 Clean all" 14 | @rm -Rf node_modules package-lock.json 15 | 16 | test: 17 | @echo "Testing..." 18 | @npm run test 19 | 20 | .PHONY: demo 21 | demo: 22 | @cd demo && npm run start 23 | 24 | demo-browser: 25 | @cd demo && npm run start-browser 26 | 27 | build: clean test 28 | @echo "👩‍🏭 Building..." 29 | @npm run build 30 | @echo "✅" 31 | 32 | publish: build 33 | @echo "📦 Publish package..." 34 | @./.scripts/publish.sh 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": false, 9 | "strictNullChecks": true, 10 | "strictFunctionTypes": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "importHelpers": true, 16 | "skipLibCheck": true, 17 | "esModuleInterop": true, 18 | "allowSyntheticDefaultImports": true, 19 | "experimentalDecorators": true, 20 | "sourceMap": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "outDir": "./dist", 23 | "types": [ 24 | "node", 25 | "jest" 26 | ], 27 | "lib": [ 28 | "ES6", 29 | "DOM" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts" 34 | ], 35 | "exclude": [ 36 | "node_modules", 37 | "**/*.test.ts" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { ConstructableCache } from './Cache'; 2 | 3 | 4 | export interface axiosETAGCacheOptions { 5 | cacheClass?: ConstructableCache 6 | enablePost?: boolean 7 | } 8 | 9 | const byLowerCase = toFind => value => toLowerCase(value) === toFind; 10 | const toLowerCase = value => value.toLowerCase(); 11 | const getKeys = headers => Object.keys(headers); 12 | 13 | export const getHeaderCaseInsensitive = (headerName, headers = {}) => { 14 | const key = getKeys(headers).find(byLowerCase(headerName)); 15 | return key ? headers[key] : undefined; 16 | }; 17 | 18 | export const cyrb53 = (str, seed = 0) => { 19 | let h1 = 0xdeadbeef ^ seed, 20 | h2 = 0x41c6ce57 ^ seed; 21 | for (let i = 0, ch; i < str.length; i++) { 22 | ch = str.charCodeAt(i); 23 | h1 = Math.imul(h1 ^ ch, 2654435761); 24 | h2 = Math.imul(h2 ^ ch, 1597334677); 25 | } 26 | 27 | h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); 28 | h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); 29 | 30 | return 4294967296 * (2097151 & h2) + (h1 >>> 0); 31 | }; 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Raúl Anatol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { getHeaderCaseInsensitive } from '../utils'; 2 | 3 | describe('utils', () => { 4 | describe('getHeaderCaseInsensitive', () => { 5 | test('should return the header with no case sensitive', () => { 6 | const headers = { etag: 10 }; 7 | expect(getHeaderCaseInsensitive('etag', headers)).toBe(10); 8 | }); 9 | 10 | test('should return the header with no case sensitive', () => { 11 | const headers = { eTag: 10 }; 12 | expect(getHeaderCaseInsensitive('etag', headers)).toBe(10); 13 | }); 14 | 15 | test('should return the header with no case sensitive', () => { 16 | const headers = { ETAG: 10 }; 17 | expect(getHeaderCaseInsensitive('etag', headers)).toBe(10); 18 | }); 19 | 20 | test('should return undefined is not header found', () => { 21 | const headers = { ETAG: 10 }; 22 | expect(getHeaderCaseInsensitive('etga', headers)).toBeUndefined(); 23 | }); 24 | 25 | test('should return undefined is not headers', () => { 26 | const headers = undefined; 27 | expect(getHeaderCaseInsensitive('etga', headers)).toBeUndefined(); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /.scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | error() { 6 | echo "🚨 $1" 7 | exit 1 8 | } 9 | 10 | is_valid_version() { 11 | case $1 in 12 | patch) ;; 13 | minor) ;; 14 | major) ;; 15 | *) error "🚨 Invalid version >> $1" ;; 16 | esac 17 | } 18 | 19 | assert_ready_to_publish() { 20 | is_valid_version "$1" 21 | if [ ! -d dist ]; then 22 | error "Need build first" 23 | fi 24 | } 25 | 26 | publish() { 27 | echo "Publish $1" 28 | npm version "$1" 29 | npm publish --access public 30 | } 31 | 32 | NEW_VERSION=$1 33 | 34 | if [ -z "$NEW_VERSION" ]; then 35 | while true; do 36 | echo "Specify an version increase (patch minor major) " 37 | read -r answer 38 | case $answer in 39 | patch) 40 | NEW_VERSION="patch" 41 | break 42 | ;; 43 | minor) 44 | NEW_VERSION="minor" 45 | break 46 | ;; 47 | major) 48 | NEW_VERSION="major" 49 | break 50 | ;; 51 | *) 52 | echo "Only patch minor or major, please." 53 | ;; 54 | esac 55 | done 56 | fi 57 | 58 | assert_ready_to_publish $NEW_VERSION 59 | publish $NEW_VERSION 60 | git push --all 61 | git push --tags 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # axios-etag-cache 2 | 3 | Axios etag interceptor to enable If-None-Match request with ETAG support 4 | 5 | ## Basic use: 6 | 7 | ```js 8 | const axios = require('axios'); 9 | const { axiosETAGCache } = require('axios-etag-cache'); 10 | 11 | // Apply the axios ETAG interceptor 12 | const axiosWithETAGCache = axiosETAGCache(axios); 13 | 14 | axiosWithETAGCache 15 | .get('http://example.com') 16 | .then(console.log) 17 | .catch(console.error); 18 | ``` 19 | 20 | ## Allow POST requests 21 | 22 | ```js 23 | const axiosWithETAGCache = axiosETAGCache(axios, {enablePost: true}); 24 | 25 | ``` 26 | 27 | 28 | 29 | ## External Storage 30 | Example implementation using RXDB 31 | ```js 32 | import { axiosETAGCache, BaseCache, CacheValue } from "axios-etag-cache"; 33 | 34 | const cacheSchema = { 35 | title: "response cache schema", 36 | version: 4, 37 | primaryKey: { 38 | key: "url", 39 | fields: ["url"], 40 | separator: "|", 41 | }, 42 | type: "object", 43 | properties: { 44 | url: { 45 | type: "string", maxLength: 100, 46 | }, 47 | etag: {type: "string"}, 48 | lastHitAt: {type: "number"}, 49 | createdAt: {type: "number"}, 50 | value: {type: "object"} 51 | }, 52 | required: ["url", "etag", "value"], 53 | indexes: ["url"] 54 | } 55 | 56 | class RxDBStorageCache extends BaseCache { 57 | flushAll(): any { 58 | } 59 | 60 | async get(key: string): Promise { 61 | const cache = ( 62 | await db["responseCache"].findOne({ 63 | selector: {url: key}, 64 | }).exec())?.toJSON(); 65 | if (cache) { 66 | const payload: CacheValue = { 67 | value: cache.value, 68 | etag: cache.etag, 69 | createdAt: cache.createdAt, 70 | lastHitAt: cache.lastHitAt 71 | } as CacheValue; 72 | return payload; 73 | } 74 | return undefined; 75 | } 76 | 77 | set(key: string, value: CacheValue): any { 78 | db["responseCache"].upsert({ 79 | url: key, 80 | value: value.value, 81 | etag: value.etag, 82 | createdAt: value.createdAt, 83 | lastHitAt: value.lastHitAt, 84 | }); 85 | } 86 | } 87 | 88 | const axiosWithETAGCache = axiosETAGCache(axios, {cacheClass: RxDBStorageCache}); 89 | 90 | ``` 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axios-etag-cache", 3 | "version": "1.4.0", 4 | "description": "Axios etag interceptor to enable If-None-Match request with ETAG support", 5 | "keywords": [ 6 | "axios", 7 | "cache", 8 | "etag" 9 | ], 10 | "homepage": "https://github.com/raulanatol/axios-etag-cache#readme", 11 | "bugs": { 12 | "url": "https://github.com/raulanatol/axios-etag-cache/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/raulanatol/axios-etag-cache.git" 17 | }, 18 | "license": "ISC", 19 | "author": { 20 | "name": "raulanatol", 21 | "email": "raul@natol.es" 22 | }, 23 | "main": "dist/index.js", 24 | "module": "dist/index.esm.js", 25 | "types": "dist/main.d.ts", 26 | "directories": { 27 | "test": "test" 28 | }, 29 | "scripts": { 30 | "build": "rollup -c", 31 | "clean": "rm -rf dist build", 32 | "lint": "eslint --max-warnings 0 . --ext .ts", 33 | "test": "jest", 34 | "prepare": "npm run build" 35 | }, 36 | "jest": { 37 | "moduleFileExtensions": [ 38 | "ts", 39 | "tsx", 40 | "js", 41 | "json" 42 | ], 43 | "moduleNameMapper": { 44 | "\\.(css|jpg|png|svg)$": "/node_modules/jest-css-modules" 45 | }, 46 | "preset": "ts-jest", 47 | "testEnvironment": "node", 48 | "testPathIgnorePatterns": [ 49 | "/node_modules/" 50 | ], 51 | "testRegex": "(/test/.*|\\.(test|spec))\\.(ts|tsx|js)$" 52 | }, 53 | "devDependencies": { 54 | "@cowcoders/eslint-config": "1.5.5", 55 | "@rollup/plugin-babel": "5.3.1", 56 | "@rollup/plugin-node-resolve": "13.3.0", 57 | "@types/axios": "0.14.0", 58 | "@types/jest": "28.1.2", 59 | "@types/node": "18.0.0", 60 | "@typescript-eslint/eslint-plugin": "5.28.0", 61 | "@typescript-eslint/parser": "5.28.0", 62 | "axios": "0.27.2", 63 | "eslint": "8.18.0", 64 | "jest": "28.1.1", 65 | "nock": "13.2.7", 66 | "rollup": "2.75.7", 67 | "rollup-plugin-auto-external": "2.0.0", 68 | "rollup-plugin-typescript2": "0.32.1", 69 | "rollup-plugin-visualizer": "5.6.0", 70 | "shx": "0.3.4", 71 | "ts-jest": "28.0.5", 72 | "ts-loader": "9.3.0", 73 | "ts-node": "10.8.1", 74 | "typescript": "4.7.4", 75 | "webserver.local": "1.0.6" 76 | }, 77 | "peerDependencies": { 78 | "axios": ">=0.24.0" 79 | }, 80 | "pre-push": [ 81 | "lint", 82 | "test", 83 | "build" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /src/Cache.ts: -------------------------------------------------------------------------------- 1 | export interface ConstructableCache { 2 | new(...args: any): T; 3 | } 4 | 5 | export abstract class BaseCache { 6 | abstract get(key: string): Promise 7 | 8 | abstract set(key: string, value: CacheValue) 9 | 10 | abstract flushAll() 11 | 12 | } 13 | 14 | export class DefaultCache extends BaseCache { 15 | private cache = {}; 16 | 17 | get(key: string): Promise { 18 | return Promise.resolve(this.cache[key]); 19 | } 20 | 21 | set(key: string, value: CacheValue) { 22 | this.cache[key] = value; 23 | } 24 | 25 | flushAll() { 26 | this.cache = {}; 27 | } 28 | 29 | 30 | } 31 | 32 | export class LocalStorageCache extends BaseCache { 33 | flushAll() { 34 | for (let i = 0; i < localStorage.length; i++){ 35 | const key = localStorage.key(i); 36 | if (key !== null && key.startsWith('aec-')) { 37 | localStorage.removeItem(key); 38 | } 39 | } 40 | } 41 | 42 | get(key: string): Promise { 43 | return new Promise((resolve) => { 44 | try { 45 | const payload: string | null = localStorage.getItem('aec-' + key); 46 | if (payload !== null) { 47 | resolve(JSON.parse(payload)); 48 | } else { 49 | resolve(undefined); 50 | } 51 | } catch (e) { 52 | resolve(undefined); 53 | } 54 | }); 55 | } 56 | 57 | set(key: string, value: CacheValue) { 58 | try { 59 | const payload = JSON.stringify(value); 60 | localStorage.setItem('aec-' + key, payload); 61 | } catch (e) { 62 | console.log(e); 63 | } 64 | } 65 | 66 | } 67 | 68 | export interface CacheValue { 69 | etag: string; 70 | value: any; 71 | createdAt: number 72 | lastHitAt: number 73 | } 74 | 75 | let instance: ReturnType; 76 | let cache: BaseCache; 77 | 78 | const makeSingleton = (cacheClass: ConstructableCache) => { 79 | /** Closure of the singleton's value to keep it private */ 80 | cache = new cacheClass(); 81 | /** Only the accessors are returned */ 82 | return { 83 | async get(uuid: string): Promise { 84 | let payload: CacheValue | undefined = await cache.get(uuid); 85 | if (payload) { 86 | payload = { ...payload, lastHitAt: Date.now() }; 87 | cache.set(uuid, payload); 88 | } 89 | return payload; 90 | }, 91 | set(uuid: string, etag: string, value: any) { 92 | return cache.set(uuid, { etag, value, createdAt: Date.now(), lastHitAt: 0 }); 93 | }, 94 | reset() { 95 | cache.flushAll(); 96 | } 97 | }; 98 | }; 99 | 100 | export const getCacheInstance = (cacheClass: ConstructableCache) => { 101 | if (!instance) { 102 | instance = makeSingleton(cacheClass); 103 | return instance; 104 | } 105 | return instance; 106 | }; 107 | -------------------------------------------------------------------------------- /demo/index.esm.js: -------------------------------------------------------------------------------- 1 | class BaseCache { 2 | constructor() { 3 | this.cache = {}; 4 | } 5 | get(key) { 6 | return this.cache[key]; 7 | } 8 | set(key, value) { 9 | this.cache[key] = value; 10 | } 11 | flushAll() { 12 | this.cache = {}; 13 | } 14 | } 15 | class DefaultCache extends BaseCache { 16 | } 17 | class Cache { 18 | constructor(cache) { 19 | this.cache = cache; 20 | } 21 | static getInstance() { 22 | if (!this.instance) { 23 | this.instance = new Cache(new DefaultCache()); 24 | } 25 | return this.instance; 26 | } 27 | static get(uuid) { 28 | return this.getInstance().cache.get(uuid); 29 | } 30 | static set(uuid, etag, value) { 31 | return this.getInstance().cache.set(uuid, { etag, value }); 32 | } 33 | static reset() { 34 | this.getInstance().cache.flushAll(); 35 | } 36 | } 37 | 38 | const byLowerCase = toFind => value => toLowerCase(value) === toFind; 39 | const toLowerCase = value => value.toLowerCase(); 40 | const getKeys = headers => Object.keys(headers); 41 | const getHeaderCaseInsensitive = (headerName, headers = {}) => { 42 | const key = getKeys(headers).find(byLowerCase(headerName)); 43 | return key ? headers[key] : undefined; 44 | }; 45 | 46 | function isCacheableMethod(config) { 47 | if (!config.method) { 48 | return false; 49 | } 50 | return ~['GET', 'HEAD'].indexOf(config.method.toUpperCase()); 51 | } 52 | function getUrlByAxiosConfig(config) { 53 | return config.url; 54 | } 55 | const getCacheByAxiosConfig = (config) => { 56 | const url = getUrlByAxiosConfig(config); 57 | if (url) { 58 | return Cache.get(url); 59 | } 60 | return undefined; 61 | }; 62 | function requestInterceptor(config) { 63 | if (isCacheableMethod(config)) { 64 | const url = getUrlByAxiosConfig(config); 65 | if (!url) { 66 | return undefined; 67 | } 68 | const lastCachedResult = Cache.get(url); 69 | if (lastCachedResult) { 70 | config.headers = Object.assign(Object.assign({}, config.headers), { 'If-None-Match': lastCachedResult.etag }); 71 | } 72 | } 73 | return config; 74 | } 75 | function responseInterceptor(response) { 76 | if (isCacheableMethod(response.config)) { 77 | const responseETAG = getHeaderCaseInsensitive('etag', response.headers); 78 | if (responseETAG) { 79 | const url = getUrlByAxiosConfig(response.config); 80 | if (!url) { 81 | return null; 82 | } 83 | Cache.set(url, responseETAG, response.data); 84 | } 85 | } 86 | return response; 87 | } 88 | function responseErrorInterceptor(error) { 89 | if (error.response && error.response.status === 304) { 90 | const getCachedResult = getCacheByAxiosConfig(error.response.config); 91 | if (!getCachedResult) { 92 | return Promise.reject(error); 93 | } 94 | const newResponse = error.response; 95 | newResponse.status = 200; 96 | newResponse.data = getCachedResult.value; 97 | return Promise.resolve(newResponse); 98 | } 99 | return Promise.reject(error); 100 | } 101 | function resetCache() { 102 | Cache.reset(); 103 | } 104 | function axiosETAGCache(axiosInstance) { 105 | axiosInstance.interceptors.request.use(requestInterceptor); 106 | axiosInstance.interceptors.response.use(responseInterceptor, responseErrorInterceptor); 107 | return axiosInstance; 108 | } 109 | 110 | export { BaseCache, Cache, DefaultCache, axiosETAGCache, getCacheByAxiosConfig, resetCache }; 111 | //# sourceMappingURL=index.esm.js.map 112 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | class BaseCache { 6 | constructor() { 7 | this.cache = {}; 8 | } 9 | get(key) { 10 | return this.cache[key]; 11 | } 12 | set(key, value) { 13 | this.cache[key] = value; 14 | } 15 | flushAll() { 16 | this.cache = {}; 17 | } 18 | } 19 | class DefaultCache extends BaseCache { 20 | } 21 | class Cache { 22 | constructor(cache) { 23 | this.cache = cache; 24 | } 25 | static getInstance() { 26 | if (!this.instance) { 27 | this.instance = new Cache(new DefaultCache()); 28 | } 29 | return this.instance; 30 | } 31 | static get(uuid) { 32 | return this.getInstance().cache.get(uuid); 33 | } 34 | static set(uuid, etag, value) { 35 | return this.getInstance().cache.set(uuid, { etag, value }); 36 | } 37 | static reset() { 38 | this.getInstance().cache.flushAll(); 39 | } 40 | } 41 | 42 | const byLowerCase = toFind => value => toLowerCase(value) === toFind; 43 | const toLowerCase = value => value.toLowerCase(); 44 | const getKeys = headers => Object.keys(headers); 45 | const getHeaderCaseInsensitive = (headerName, headers = {}) => { 46 | const key = getKeys(headers).find(byLowerCase(headerName)); 47 | return key ? headers[key] : undefined; 48 | }; 49 | 50 | function isCacheableMethod(config) { 51 | if (!config.method) { 52 | return false; 53 | } 54 | return ~['GET', 'HEAD'].indexOf(config.method.toUpperCase()); 55 | } 56 | function getUrlByAxiosConfig(config) { 57 | return config.url; 58 | } 59 | const getCacheByAxiosConfig = (config) => { 60 | const url = getUrlByAxiosConfig(config); 61 | if (url) { 62 | return Cache.get(url); 63 | } 64 | return undefined; 65 | }; 66 | function requestInterceptor(config) { 67 | if (isCacheableMethod(config)) { 68 | const url = getUrlByAxiosConfig(config); 69 | if (!url) { 70 | return undefined; 71 | } 72 | const lastCachedResult = Cache.get(url); 73 | if (lastCachedResult) { 74 | config.headers = Object.assign(Object.assign({}, config.headers), { 'If-None-Match': lastCachedResult.etag }); 75 | } 76 | } 77 | return config; 78 | } 79 | function responseInterceptor(response) { 80 | if (isCacheableMethod(response.config)) { 81 | const responseETAG = getHeaderCaseInsensitive('etag', response.headers); 82 | if (responseETAG) { 83 | const url = getUrlByAxiosConfig(response.config); 84 | if (!url) { 85 | return null; 86 | } 87 | Cache.set(url, responseETAG, response.data); 88 | } 89 | } 90 | return response; 91 | } 92 | function responseErrorInterceptor(error) { 93 | if (error.response && error.response.status === 304) { 94 | const getCachedResult = getCacheByAxiosConfig(error.response.config); 95 | if (!getCachedResult) { 96 | return Promise.reject(error); 97 | } 98 | const newResponse = error.response; 99 | newResponse.status = 200; 100 | newResponse.data = getCachedResult.value; 101 | return Promise.resolve(newResponse); 102 | } 103 | return Promise.reject(error); 104 | } 105 | function resetCache() { 106 | Cache.reset(); 107 | } 108 | function axiosETAGCache(axiosInstance) { 109 | axiosInstance.interceptors.request.use(requestInterceptor); 110 | axiosInstance.interceptors.response.use(responseInterceptor, responseErrorInterceptor); 111 | return axiosInstance; 112 | } 113 | 114 | exports.BaseCache = BaseCache; 115 | exports.Cache = Cache; 116 | exports.DefaultCache = DefaultCache; 117 | exports.axiosETAGCache = axiosETAGCache; 118 | exports.getCacheByAxiosConfig = getCacheByAxiosConfig; 119 | exports.resetCache = resetCache; 120 | //# sourceMappingURL=index.js.map 121 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {axiosETAGCacheOptions, cyrb53, getHeaderCaseInsensitive} from './utils'; 2 | import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; 3 | import { DefaultCache, getCacheInstance } from './Cache'; 4 | 5 | let Cache; 6 | let cacheableMethods = ['GET', 'HEAD']; 7 | 8 | function isCacheableMethod(config: AxiosRequestConfig) { 9 | if (!config.method) { 10 | return false; 11 | } 12 | return ~cacheableMethods.indexOf(config.method.toUpperCase()); 13 | } 14 | 15 | function getUrlByAxiosConfig(config: AxiosRequestConfig) { 16 | return config.url; 17 | } 18 | 19 | export const getCacheByAxiosConfig = async (config: AxiosRequestConfig) => { 20 | const url = getUrlByAxiosConfig(config); 21 | if (url) { 22 | if (config.data) { 23 | const hash = cyrb53(config.data); 24 | return await Cache.get(hash + url); 25 | } else { 26 | return await Cache.get(url); 27 | } 28 | } 29 | return undefined; 30 | }; 31 | 32 | function requestInterceptor(config: AxiosRequestConfig): Promise { 33 | return new Promise(async (resolve, reject) => { 34 | if (isCacheableMethod(config)) { 35 | const url = getUrlByAxiosConfig(config); 36 | if (!url) { 37 | reject(config); 38 | return; 39 | } 40 | let lastCachedResult; 41 | if (config.data) { 42 | try { 43 | const hash = cyrb53(JSON.stringify(config.data)); 44 | lastCachedResult = await Cache.get(hash + url); 45 | } catch (e) { 46 | console.error(e); 47 | } 48 | } else { 49 | lastCachedResult = await Cache.get(url); 50 | } 51 | 52 | if (lastCachedResult) { 53 | config.headers = { ...config.headers, 'If-None-Match': lastCachedResult.etag }; 54 | } 55 | } 56 | resolve(config); 57 | }); 58 | } 59 | 60 | function responseInterceptor(response: AxiosResponse): Promise { 61 | return new Promise((resolve, reject) => { 62 | if (isCacheableMethod(response.config)) { 63 | const responseETAG = getHeaderCaseInsensitive('etag', response.headers); 64 | if (responseETAG) { 65 | const url = getUrlByAxiosConfig(response.config); 66 | if (!url) { 67 | reject(null); 68 | return; 69 | } 70 | if (response.config.data) { 71 | try { 72 | const hash = cyrb53(response.config.data); 73 | Cache.set(hash + url, responseETAG, response.data); 74 | } catch (e) { 75 | console.error(e); 76 | } 77 | } else { 78 | Cache.set(url, responseETAG, response.data); 79 | } 80 | 81 | } 82 | } 83 | resolve(response); 84 | }); 85 | } 86 | 87 | function responseErrorInterceptor(error: AxiosError): Promise { 88 | return new Promise(async (resolve, reject) => { 89 | if (error.response && error.response.status === 304) { 90 | const getCachedResult = await getCacheByAxiosConfig(error.response.config); 91 | if (!getCachedResult) { 92 | reject(error); 93 | return; 94 | } 95 | const newResponse: AxiosResponse = error.response; 96 | newResponse.status = 200; 97 | newResponse.data = getCachedResult.value; 98 | resolve(newResponse); 99 | return; 100 | } 101 | reject(error); 102 | }); 103 | } 104 | 105 | export async function resetCache() { 106 | await Cache.reset(); 107 | } 108 | 109 | export function axiosETAGCache(axiosInstance: AxiosInstance, options?: axiosETAGCacheOptions): AxiosInstance { 110 | if (options?.cacheClass) { 111 | Cache = getCacheInstance(options.cacheClass); 112 | } else { 113 | Cache = getCacheInstance(DefaultCache); 114 | } 115 | 116 | if (options?.enablePost === true) { 117 | cacheableMethods.push('POST'); 118 | } 119 | 120 | axiosInstance.interceptors.request.use(requestInterceptor); 121 | axiosInstance.interceptors.response.use(responseInterceptor, responseErrorInterceptor); 122 | 123 | return axiosInstance; 124 | } 125 | -------------------------------------------------------------------------------- /src/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { axiosETAGCache, getCacheByAxiosConfig, resetCache } from '../index'; 2 | import nock from 'nock'; 3 | import axios from 'axios'; 4 | import { DefaultCache, getCacheInstance } from '../Cache'; 5 | 6 | const Cache = getCacheInstance(DefaultCache); 7 | 8 | 9 | const USERS = [{ uuid: '123', name: 'John' }]; 10 | const TEST_ETAG_0 = '123ABC'; 11 | const TEST_ETAG_1 = '#123ABC'; 12 | const BASE_PATH = 'http://api.example.com'; 13 | 14 | function replyIfNoneMatchWithEtag(request, etag, results) { 15 | if (request.headers?.['if-none-match'] === etag) { 16 | return [200, results]; 17 | } 18 | return [404, 'Invalid ETAG']; 19 | } 20 | 21 | function replyIfMatchEtag0(request) { 22 | replyIfNoneMatchWithEtag(request, TEST_ETAG_0, USERS); 23 | } 24 | 25 | function replyIfMatchEtag1(request) { 26 | replyIfNoneMatchWithEtag(request, TEST_ETAG_1, USERS); 27 | } 28 | 29 | function replyIfNotEtagHeaders(request) { 30 | if (!request.headers['if-none-match']) { 31 | return [200, USERS]; 32 | } 33 | return [404, 'ETAG headers found']; 34 | } 35 | 36 | describe('Index', () => { 37 | describe('getCachedByAxiosConfig', () => { 38 | axiosETAGCache(axios); 39 | it('should returns undefined when no config url is registered', async () => { 40 | const config = {}; 41 | Cache.get = jest.fn(); 42 | 43 | const result = await getCacheByAxiosConfig(config); 44 | 45 | expect(result).toBeUndefined(); 46 | expect(Cache.get).not.toBeCalled(); 47 | }); 48 | 49 | it('should call to the cache.get method if url is registered', async () => { 50 | const config = { url: 'defined' }; 51 | Cache.get = jest.fn(); 52 | 53 | const result = await getCacheByAxiosConfig(config); 54 | 55 | expect(result).toBeUndefined(); 56 | expect(Cache.get).toBeCalled(); 57 | }); 58 | }); 59 | 60 | it('should do the second request with a If-none-match header', done => { 61 | const call1 = nock(BASE_PATH).get('/users').reply(200, USERS, { Etag: TEST_ETAG_0 }); 62 | const call2 = nock(BASE_PATH).get('/users').reply(200, function () { 63 | replyIfMatchEtag0(this.req); 64 | }); 65 | axiosETAGCache(axios).get('http://api.example.com/users').then(() => { 66 | axiosETAGCache(axios).get('http://api.example.com/users').then(() => { 67 | expect(call1.isDone()).toBeTruthy(); 68 | expect(call2.isDone()).toBeTruthy(); 69 | done(); 70 | }).catch(done); 71 | }).catch(done); 72 | }); 73 | 74 | it('should works with normally when no etag is provided', done => { 75 | const call1 = nock(BASE_PATH).get('/actions').reply(200, USERS); 76 | const call2 = nock(BASE_PATH).get('/actions').reply(200, function () { 77 | replyIfNotEtagHeaders(this.req); 78 | }); 79 | axiosETAGCache(axios).get('http://api.example.com/actions').then(() => { 80 | axiosETAGCache(axios).get('http://api.example.com/actions').then(() => { 81 | expect(call1.isDone()).toBeTruthy(); 82 | expect(call2.isDone()).toBeTruthy(); 83 | done(); 84 | }).catch(done); 85 | }).catch(done); 86 | }); 87 | 88 | it('should update the last ETAG value', done => { 89 | const call0 = nock(BASE_PATH).get('/actionsA').reply(200, USERS, { Etag: TEST_ETAG_0 }); 90 | const call1 = nock(BASE_PATH).get('/actionsA').reply(200, USERS, { Etag: TEST_ETAG_1 }); 91 | const call2 = nock(BASE_PATH).get('/actionsA').reply(200, replyIfMatchEtag1); 92 | axiosETAGCache(axios).get('http://api.example.com/actionsA').then(() => { 93 | axiosETAGCache(axios).get('http://api.example.com/actionsA').then(() => { 94 | axiosETAGCache(axios).get('http://api.example.com/actionsA').then(() => { 95 | expect(call0.isDone()).toBeTruthy(); 96 | expect(call1.isDone()).toBeTruthy(); 97 | expect(call2.isDone()).toBeTruthy(); 98 | done(); 99 | }).catch(done); 100 | }).catch(done); 101 | }).catch(done); 102 | }); 103 | 104 | it('not cacheable methods should works with normally - POST', done => { 105 | const call1 = nock(BASE_PATH).post('/model').reply(200, USERS); 106 | axiosETAGCache(axios).post('http://api.example.com/model').then(() => { 107 | expect(call1.isDone()).toBeTruthy(); 108 | done(); 109 | }).catch(done); 110 | }); 111 | 112 | it('should do second request without etag if cache was reset', done => { 113 | const call1 = nock(BASE_PATH).get('/users').reply(200, USERS, { Etag: TEST_ETAG_0 }); 114 | const call2 = nock(BASE_PATH).get('/users').reply(200, function () { 115 | replyIfNotEtagHeaders(this.req); 116 | }); 117 | axiosETAGCache(axios).get('http://api.example.com/users').then(() => { 118 | resetCache(); 119 | axiosETAGCache(axios).get('http://api.example.com/users').then(() => { 120 | expect(call1.isDone()).toBeTruthy(); 121 | expect(call2.isDone()).toBeTruthy(); 122 | done(); 123 | }).catch(done); 124 | }).catch(done); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /demo/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "demo", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "0.24.0", 13 | "axios-etag-cache": "1.2.1" 14 | }, 15 | "devDependencies": { 16 | "vite": "2.9.16" 17 | } 18 | }, 19 | "node_modules/axios": { 20 | "version": "0.24.0", 21 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", 22 | "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", 23 | "dependencies": { 24 | "follow-redirects": "^1.14.4" 25 | } 26 | }, 27 | "node_modules/axios-etag-cache": { 28 | "version": "1.2.1", 29 | "resolved": "https://registry.npmjs.org/axios-etag-cache/-/axios-etag-cache-1.2.1.tgz", 30 | "integrity": "sha512-PNUZuEdQfW75uGsQEdzTm1UnLr7qMFwS+X48vbVsKj1UCBR7T9DSdtn4gTu2clzOdave8tM3IP6xeqtpGOo17g==", 31 | "peerDependencies": { 32 | "axios": ">=0.24.0" 33 | } 34 | }, 35 | "node_modules/esbuild": { 36 | "version": "0.14.46", 37 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.46.tgz", 38 | "integrity": "sha512-vdm5G1JdZBktva8dwQci/s44VbeBUg8g907xoZx77mqFZ4gU5GlMULNsdGeID+qXCXocsfYSGtE0LvqH3eiNQg==", 39 | "dev": true, 40 | "hasInstallScript": true, 41 | "bin": { 42 | "esbuild": "bin/esbuild" 43 | }, 44 | "engines": { 45 | "node": ">=12" 46 | }, 47 | "optionalDependencies": { 48 | "esbuild-android-64": "0.14.46", 49 | "esbuild-android-arm64": "0.14.46", 50 | "esbuild-darwin-64": "0.14.46", 51 | "esbuild-darwin-arm64": "0.14.46", 52 | "esbuild-freebsd-64": "0.14.46", 53 | "esbuild-freebsd-arm64": "0.14.46", 54 | "esbuild-linux-32": "0.14.46", 55 | "esbuild-linux-64": "0.14.46", 56 | "esbuild-linux-arm": "0.14.46", 57 | "esbuild-linux-arm64": "0.14.46", 58 | "esbuild-linux-mips64le": "0.14.46", 59 | "esbuild-linux-ppc64le": "0.14.46", 60 | "esbuild-linux-riscv64": "0.14.46", 61 | "esbuild-linux-s390x": "0.14.46", 62 | "esbuild-netbsd-64": "0.14.46", 63 | "esbuild-openbsd-64": "0.14.46", 64 | "esbuild-sunos-64": "0.14.46", 65 | "esbuild-windows-32": "0.14.46", 66 | "esbuild-windows-64": "0.14.46", 67 | "esbuild-windows-arm64": "0.14.46" 68 | } 69 | }, 70 | "node_modules/esbuild-android-64": { 71 | "version": "0.14.46", 72 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.46.tgz", 73 | "integrity": "sha512-ZyJqwAcjNbZprs0ZAxnUAOhEhdE5kTKwz+CZuLmZYNLAPyRgBtaC8pT2PCuPifNvV8Cl3yLlrQPaOCjovoyb5g==", 74 | "cpu": [ 75 | "x64" 76 | ], 77 | "dev": true, 78 | "optional": true, 79 | "os": [ 80 | "android" 81 | ], 82 | "engines": { 83 | "node": ">=12" 84 | } 85 | }, 86 | "node_modules/esbuild-android-arm64": { 87 | "version": "0.14.46", 88 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.46.tgz", 89 | "integrity": "sha512-BKcnUksvCijO9ONv6b4SikZE/OZftwJvX91XROODZGQmuwGVg97jmLDVu3lxuHdFlMNNzxh8taJ2mbCWZzH/Iw==", 90 | "cpu": [ 91 | "arm64" 92 | ], 93 | "dev": true, 94 | "optional": true, 95 | "os": [ 96 | "android" 97 | ], 98 | "engines": { 99 | "node": ">=12" 100 | } 101 | }, 102 | "node_modules/esbuild-darwin-64": { 103 | "version": "0.14.46", 104 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.46.tgz", 105 | "integrity": "sha512-/ss2kO92sUJ9/1nHnMb3+oab8w6dyqKrMtPMvSYJ9KZIYGAZxz/WYxfFprY7Xk+ZxWnnlASSyZlG+If1nVmFYg==", 106 | "cpu": [ 107 | "x64" 108 | ], 109 | "dev": true, 110 | "optional": true, 111 | "os": [ 112 | "darwin" 113 | ], 114 | "engines": { 115 | "node": ">=12" 116 | } 117 | }, 118 | "node_modules/esbuild-darwin-arm64": { 119 | "version": "0.14.46", 120 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.46.tgz", 121 | "integrity": "sha512-WX0JOaEFf6t+rIjXO6THsf/0fhQAt2Zb0/PSYlvXnuQQAmOmFAfPsuRNocp5ME0NGaUqZd4FxqqmLEVK3RzPAg==", 122 | "cpu": [ 123 | "arm64" 124 | ], 125 | "dev": true, 126 | "optional": true, 127 | "os": [ 128 | "darwin" 129 | ], 130 | "engines": { 131 | "node": ">=12" 132 | } 133 | }, 134 | "node_modules/esbuild-freebsd-64": { 135 | "version": "0.14.46", 136 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.46.tgz", 137 | "integrity": "sha512-o+ozPFuHRCAGCVWU2bLurOUgVkT0jcPEu082VBUY2Q/yLf+B+/3nXzh4Fjp5O21tOvJRTn7hUVydG9j5+vYE6A==", 138 | "cpu": [ 139 | "x64" 140 | ], 141 | "dev": true, 142 | "optional": true, 143 | "os": [ 144 | "freebsd" 145 | ], 146 | "engines": { 147 | "node": ">=12" 148 | } 149 | }, 150 | "node_modules/esbuild-freebsd-arm64": { 151 | "version": "0.14.46", 152 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.46.tgz", 153 | "integrity": "sha512-9zicZ0X43WDKz3sjNfcqYO38xbfJpSWYXB+FxvYYkmBwGA52K0SAu4oKuTTLi8od8X2IIo1x5C5TUNvKDSVJww==", 154 | "cpu": [ 155 | "arm64" 156 | ], 157 | "dev": true, 158 | "optional": true, 159 | "os": [ 160 | "freebsd" 161 | ], 162 | "engines": { 163 | "node": ">=12" 164 | } 165 | }, 166 | "node_modules/esbuild-linux-32": { 167 | "version": "0.14.46", 168 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.46.tgz", 169 | "integrity": "sha512-ZnTpZMVb0VGvL99R5eh4OrJwbUyvpM6M88VAMuHP4LvFjuvZrhgefjKqEGuWZZW7JRnAjKqjXLjWdhdSjwMFnQ==", 170 | "cpu": [ 171 | "ia32" 172 | ], 173 | "dev": true, 174 | "optional": true, 175 | "os": [ 176 | "linux" 177 | ], 178 | "engines": { 179 | "node": ">=12" 180 | } 181 | }, 182 | "node_modules/esbuild-linux-64": { 183 | "version": "0.14.46", 184 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.46.tgz", 185 | "integrity": "sha512-ECCRRZtX6l4ubeVhHhiVoK/uYAkvzNqfmR4gP4N/9H9RPu+b8YCcN4bQGp7xCuYIV6Xd41WpOMyO+xpcQvjtQQ==", 186 | "cpu": [ 187 | "x64" 188 | ], 189 | "dev": true, 190 | "optional": true, 191 | "os": [ 192 | "linux" 193 | ], 194 | "engines": { 195 | "node": ">=12" 196 | } 197 | }, 198 | "node_modules/esbuild-linux-arm": { 199 | "version": "0.14.46", 200 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.46.tgz", 201 | "integrity": "sha512-RvTJEi4vj13c5FP9YPp+8Y6x6HK1E7uSqfy3y9UoeaNAzNZWA7fN1U3hQjTL/dy5zTJH5KE64mrt5k5+he+CQA==", 202 | "cpu": [ 203 | "arm" 204 | ], 205 | "dev": true, 206 | "optional": true, 207 | "os": [ 208 | "linux" 209 | ], 210 | "engines": { 211 | "node": ">=12" 212 | } 213 | }, 214 | "node_modules/esbuild-linux-arm64": { 215 | "version": "0.14.46", 216 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.46.tgz", 217 | "integrity": "sha512-HX0TXCHyI0NEWG4jg8LlW1PbZQbnz+PUH56yjx996cgM5pC90u32drKs/tyJiyyQmNk9OXOogjKw7LEdp/Qc1w==", 218 | "cpu": [ 219 | "arm64" 220 | ], 221 | "dev": true, 222 | "optional": true, 223 | "os": [ 224 | "linux" 225 | ], 226 | "engines": { 227 | "node": ">=12" 228 | } 229 | }, 230 | "node_modules/esbuild-linux-mips64le": { 231 | "version": "0.14.46", 232 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.46.tgz", 233 | "integrity": "sha512-jnb2NDwGqJUVmxn1v0f7seNdDm0nRNWHP9Z3MrWAGnBCdnnDlsjqRFDnbKoaQvWONEa+rOOr/giK+VL0hgQExA==", 234 | "cpu": [ 235 | "mips64el" 236 | ], 237 | "dev": true, 238 | "optional": true, 239 | "os": [ 240 | "linux" 241 | ], 242 | "engines": { 243 | "node": ">=12" 244 | } 245 | }, 246 | "node_modules/esbuild-linux-ppc64le": { 247 | "version": "0.14.46", 248 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.46.tgz", 249 | "integrity": "sha512-uu3JTQUrwwauKY9z8yq5MnDyOlT3f2DNOzBcYz4dB78HqwEqilCsifoBGd0WcbED5n57dc59X+LZMTZ8Ose44w==", 250 | "cpu": [ 251 | "ppc64" 252 | ], 253 | "dev": true, 254 | "optional": true, 255 | "os": [ 256 | "linux" 257 | ], 258 | "engines": { 259 | "node": ">=12" 260 | } 261 | }, 262 | "node_modules/esbuild-linux-riscv64": { 263 | "version": "0.14.46", 264 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.46.tgz", 265 | "integrity": "sha512-OB29r1EG44ZY34JnXCRERxo7k4pRKoQdaoRg2HIeCavatsXZwW4LCakpLnMQ72vXT1HtpBUABEjHkKkn5JyrUg==", 266 | "cpu": [ 267 | "riscv64" 268 | ], 269 | "dev": true, 270 | "optional": true, 271 | "os": [ 272 | "linux" 273 | ], 274 | "engines": { 275 | "node": ">=12" 276 | } 277 | }, 278 | "node_modules/esbuild-linux-s390x": { 279 | "version": "0.14.46", 280 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.46.tgz", 281 | "integrity": "sha512-XQ/U9TueMSGYyPTKyZsJVraiuvxhwCDIMn/QwFXCRCJ6H/Cy/Rq33u9qhpeSziinHKfzJROGx5A8mQY6aYamdQ==", 282 | "cpu": [ 283 | "s390x" 284 | ], 285 | "dev": true, 286 | "optional": true, 287 | "os": [ 288 | "linux" 289 | ], 290 | "engines": { 291 | "node": ">=12" 292 | } 293 | }, 294 | "node_modules/esbuild-netbsd-64": { 295 | "version": "0.14.46", 296 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.46.tgz", 297 | "integrity": "sha512-i15BwqHaAIFp1vBJkitAbHtwXcLk9TdHs/Ia1xGIAutQYXSJNPLM3Z4B4hyfHNEFl2yBqBIYpglMohv2ClNdOQ==", 298 | "cpu": [ 299 | "x64" 300 | ], 301 | "dev": true, 302 | "optional": true, 303 | "os": [ 304 | "netbsd" 305 | ], 306 | "engines": { 307 | "node": ">=12" 308 | } 309 | }, 310 | "node_modules/esbuild-openbsd-64": { 311 | "version": "0.14.46", 312 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.46.tgz", 313 | "integrity": "sha512-XwOIFCE140Y/PvjrwjFfa/QLWBuvhR1mPCOa35mKx02jt++wPNgf0qhn6HfdVC3vQe7R46RwTp4q2cp99fepEg==", 314 | "cpu": [ 315 | "x64" 316 | ], 317 | "dev": true, 318 | "optional": true, 319 | "os": [ 320 | "openbsd" 321 | ], 322 | "engines": { 323 | "node": ">=12" 324 | } 325 | }, 326 | "node_modules/esbuild-sunos-64": { 327 | "version": "0.14.46", 328 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.46.tgz", 329 | "integrity": "sha512-+kV3JnmfdxBVpHyFvuGXWtu6tXxXApOLPkSrVkMJf6+ns/3PLtPndpzwCzHjD+qYUEk8ln4MA+ufQ2qmjW5mZg==", 330 | "cpu": [ 331 | "x64" 332 | ], 333 | "dev": true, 334 | "optional": true, 335 | "os": [ 336 | "sunos" 337 | ], 338 | "engines": { 339 | "node": ">=12" 340 | } 341 | }, 342 | "node_modules/esbuild-windows-32": { 343 | "version": "0.14.46", 344 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.46.tgz", 345 | "integrity": "sha512-gzGC1Q11B/Bo5A2EX4N22oigWmhL7Z0eDyc8kbSoJjqSrGQuRE7B0uMpluO+q0O/gZ1S3zdw+M4PCWlqOIeXLA==", 346 | "cpu": [ 347 | "ia32" 348 | ], 349 | "dev": true, 350 | "optional": true, 351 | "os": [ 352 | "win32" 353 | ], 354 | "engines": { 355 | "node": ">=12" 356 | } 357 | }, 358 | "node_modules/esbuild-windows-64": { 359 | "version": "0.14.46", 360 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.46.tgz", 361 | "integrity": "sha512-Do2daaskfOjmCB7o3ygz6fD3K6SPjZLERiZLktzHz2oUCwsebKu/gmop0+j/XdrVIXC32wFzHzDS+9CTu9OShw==", 362 | "cpu": [ 363 | "x64" 364 | ], 365 | "dev": true, 366 | "optional": true, 367 | "os": [ 368 | "win32" 369 | ], 370 | "engines": { 371 | "node": ">=12" 372 | } 373 | }, 374 | "node_modules/esbuild-windows-arm64": { 375 | "version": "0.14.46", 376 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.46.tgz", 377 | "integrity": "sha512-VEzMy6bM60/HT/URTDElyhfi2Pk0quCCrEhRlI4MRno/AIqYUGw0rZwkPl6PeoqVI6BgoBHGY576GWTiPmshCA==", 378 | "cpu": [ 379 | "arm64" 380 | ], 381 | "dev": true, 382 | "optional": true, 383 | "os": [ 384 | "win32" 385 | ], 386 | "engines": { 387 | "node": ">=12" 388 | } 389 | }, 390 | "node_modules/follow-redirects": { 391 | "version": "1.14.8", 392 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", 393 | "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", 394 | "funding": [ 395 | { 396 | "type": "individual", 397 | "url": "https://github.com/sponsors/RubenVerborgh" 398 | } 399 | ], 400 | "engines": { 401 | "node": ">=4.0" 402 | }, 403 | "peerDependenciesMeta": { 404 | "debug": { 405 | "optional": true 406 | } 407 | } 408 | }, 409 | "node_modules/fsevents": { 410 | "version": "2.3.2", 411 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 412 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 413 | "dev": true, 414 | "hasInstallScript": true, 415 | "optional": true, 416 | "os": [ 417 | "darwin" 418 | ], 419 | "engines": { 420 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 421 | } 422 | }, 423 | "node_modules/function-bind": { 424 | "version": "1.1.1", 425 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 426 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 427 | "dev": true 428 | }, 429 | "node_modules/has": { 430 | "version": "1.0.3", 431 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 432 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 433 | "dev": true, 434 | "dependencies": { 435 | "function-bind": "^1.1.1" 436 | }, 437 | "engines": { 438 | "node": ">= 0.4.0" 439 | } 440 | }, 441 | "node_modules/is-core-module": { 442 | "version": "2.9.0", 443 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", 444 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", 445 | "dev": true, 446 | "dependencies": { 447 | "has": "^1.0.3" 448 | }, 449 | "funding": { 450 | "url": "https://github.com/sponsors/ljharb" 451 | } 452 | }, 453 | "node_modules/nanoid": { 454 | "version": "3.3.6", 455 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 456 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 457 | "dev": true, 458 | "funding": [ 459 | { 460 | "type": "github", 461 | "url": "https://github.com/sponsors/ai" 462 | } 463 | ], 464 | "bin": { 465 | "nanoid": "bin/nanoid.cjs" 466 | }, 467 | "engines": { 468 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 469 | } 470 | }, 471 | "node_modules/path-parse": { 472 | "version": "1.0.7", 473 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 474 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 475 | "dev": true 476 | }, 477 | "node_modules/picocolors": { 478 | "version": "1.0.0", 479 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 480 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 481 | "dev": true 482 | }, 483 | "node_modules/postcss": { 484 | "version": "8.4.31", 485 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 486 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 487 | "dev": true, 488 | "funding": [ 489 | { 490 | "type": "opencollective", 491 | "url": "https://opencollective.com/postcss/" 492 | }, 493 | { 494 | "type": "tidelift", 495 | "url": "https://tidelift.com/funding/github/npm/postcss" 496 | }, 497 | { 498 | "type": "github", 499 | "url": "https://github.com/sponsors/ai" 500 | } 501 | ], 502 | "dependencies": { 503 | "nanoid": "^3.3.6", 504 | "picocolors": "^1.0.0", 505 | "source-map-js": "^1.0.2" 506 | }, 507 | "engines": { 508 | "node": "^10 || ^12 || >=14" 509 | } 510 | }, 511 | "node_modules/resolve": { 512 | "version": "1.22.1", 513 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 514 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 515 | "dev": true, 516 | "dependencies": { 517 | "is-core-module": "^2.9.0", 518 | "path-parse": "^1.0.7", 519 | "supports-preserve-symlinks-flag": "^1.0.0" 520 | }, 521 | "bin": { 522 | "resolve": "bin/resolve" 523 | }, 524 | "funding": { 525 | "url": "https://github.com/sponsors/ljharb" 526 | } 527 | }, 528 | "node_modules/rollup": { 529 | "version": "2.75.6", 530 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz", 531 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==", 532 | "dev": true, 533 | "bin": { 534 | "rollup": "dist/bin/rollup" 535 | }, 536 | "engines": { 537 | "node": ">=10.0.0" 538 | }, 539 | "optionalDependencies": { 540 | "fsevents": "~2.3.2" 541 | } 542 | }, 543 | "node_modules/source-map-js": { 544 | "version": "1.0.2", 545 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 546 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 547 | "dev": true, 548 | "engines": { 549 | "node": ">=0.10.0" 550 | } 551 | }, 552 | "node_modules/supports-preserve-symlinks-flag": { 553 | "version": "1.0.0", 554 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 555 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 556 | "dev": true, 557 | "engines": { 558 | "node": ">= 0.4" 559 | }, 560 | "funding": { 561 | "url": "https://github.com/sponsors/ljharb" 562 | } 563 | }, 564 | "node_modules/vite": { 565 | "version": "2.9.16", 566 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz", 567 | "integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==", 568 | "dev": true, 569 | "dependencies": { 570 | "esbuild": "^0.14.27", 571 | "postcss": "^8.4.13", 572 | "resolve": "^1.22.0", 573 | "rollup": ">=2.59.0 <2.78.0" 574 | }, 575 | "bin": { 576 | "vite": "bin/vite.js" 577 | }, 578 | "engines": { 579 | "node": ">=12.2.0" 580 | }, 581 | "optionalDependencies": { 582 | "fsevents": "~2.3.2" 583 | }, 584 | "peerDependencies": { 585 | "less": "*", 586 | "sass": "*", 587 | "stylus": "*" 588 | }, 589 | "peerDependenciesMeta": { 590 | "less": { 591 | "optional": true 592 | }, 593 | "sass": { 594 | "optional": true 595 | }, 596 | "stylus": { 597 | "optional": true 598 | } 599 | } 600 | } 601 | }, 602 | "dependencies": { 603 | "axios": { 604 | "version": "0.24.0", 605 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", 606 | "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", 607 | "requires": { 608 | "follow-redirects": "^1.14.4" 609 | } 610 | }, 611 | "axios-etag-cache": { 612 | "version": "1.2.1", 613 | "resolved": "https://registry.npmjs.org/axios-etag-cache/-/axios-etag-cache-1.2.1.tgz", 614 | "integrity": "sha512-PNUZuEdQfW75uGsQEdzTm1UnLr7qMFwS+X48vbVsKj1UCBR7T9DSdtn4gTu2clzOdave8tM3IP6xeqtpGOo17g==", 615 | "requires": {} 616 | }, 617 | "esbuild": { 618 | "version": "0.14.46", 619 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.46.tgz", 620 | "integrity": "sha512-vdm5G1JdZBktva8dwQci/s44VbeBUg8g907xoZx77mqFZ4gU5GlMULNsdGeID+qXCXocsfYSGtE0LvqH3eiNQg==", 621 | "dev": true, 622 | "requires": { 623 | "esbuild-android-64": "0.14.46", 624 | "esbuild-android-arm64": "0.14.46", 625 | "esbuild-darwin-64": "0.14.46", 626 | "esbuild-darwin-arm64": "0.14.46", 627 | "esbuild-freebsd-64": "0.14.46", 628 | "esbuild-freebsd-arm64": "0.14.46", 629 | "esbuild-linux-32": "0.14.46", 630 | "esbuild-linux-64": "0.14.46", 631 | "esbuild-linux-arm": "0.14.46", 632 | "esbuild-linux-arm64": "0.14.46", 633 | "esbuild-linux-mips64le": "0.14.46", 634 | "esbuild-linux-ppc64le": "0.14.46", 635 | "esbuild-linux-riscv64": "0.14.46", 636 | "esbuild-linux-s390x": "0.14.46", 637 | "esbuild-netbsd-64": "0.14.46", 638 | "esbuild-openbsd-64": "0.14.46", 639 | "esbuild-sunos-64": "0.14.46", 640 | "esbuild-windows-32": "0.14.46", 641 | "esbuild-windows-64": "0.14.46", 642 | "esbuild-windows-arm64": "0.14.46" 643 | } 644 | }, 645 | "esbuild-android-64": { 646 | "version": "0.14.46", 647 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.46.tgz", 648 | "integrity": "sha512-ZyJqwAcjNbZprs0ZAxnUAOhEhdE5kTKwz+CZuLmZYNLAPyRgBtaC8pT2PCuPifNvV8Cl3yLlrQPaOCjovoyb5g==", 649 | "dev": true, 650 | "optional": true 651 | }, 652 | "esbuild-android-arm64": { 653 | "version": "0.14.46", 654 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.46.tgz", 655 | "integrity": "sha512-BKcnUksvCijO9ONv6b4SikZE/OZftwJvX91XROODZGQmuwGVg97jmLDVu3lxuHdFlMNNzxh8taJ2mbCWZzH/Iw==", 656 | "dev": true, 657 | "optional": true 658 | }, 659 | "esbuild-darwin-64": { 660 | "version": "0.14.46", 661 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.46.tgz", 662 | "integrity": "sha512-/ss2kO92sUJ9/1nHnMb3+oab8w6dyqKrMtPMvSYJ9KZIYGAZxz/WYxfFprY7Xk+ZxWnnlASSyZlG+If1nVmFYg==", 663 | "dev": true, 664 | "optional": true 665 | }, 666 | "esbuild-darwin-arm64": { 667 | "version": "0.14.46", 668 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.46.tgz", 669 | "integrity": "sha512-WX0JOaEFf6t+rIjXO6THsf/0fhQAt2Zb0/PSYlvXnuQQAmOmFAfPsuRNocp5ME0NGaUqZd4FxqqmLEVK3RzPAg==", 670 | "dev": true, 671 | "optional": true 672 | }, 673 | "esbuild-freebsd-64": { 674 | "version": "0.14.46", 675 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.46.tgz", 676 | "integrity": "sha512-o+ozPFuHRCAGCVWU2bLurOUgVkT0jcPEu082VBUY2Q/yLf+B+/3nXzh4Fjp5O21tOvJRTn7hUVydG9j5+vYE6A==", 677 | "dev": true, 678 | "optional": true 679 | }, 680 | "esbuild-freebsd-arm64": { 681 | "version": "0.14.46", 682 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.46.tgz", 683 | "integrity": "sha512-9zicZ0X43WDKz3sjNfcqYO38xbfJpSWYXB+FxvYYkmBwGA52K0SAu4oKuTTLi8od8X2IIo1x5C5TUNvKDSVJww==", 684 | "dev": true, 685 | "optional": true 686 | }, 687 | "esbuild-linux-32": { 688 | "version": "0.14.46", 689 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.46.tgz", 690 | "integrity": "sha512-ZnTpZMVb0VGvL99R5eh4OrJwbUyvpM6M88VAMuHP4LvFjuvZrhgefjKqEGuWZZW7JRnAjKqjXLjWdhdSjwMFnQ==", 691 | "dev": true, 692 | "optional": true 693 | }, 694 | "esbuild-linux-64": { 695 | "version": "0.14.46", 696 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.46.tgz", 697 | "integrity": "sha512-ECCRRZtX6l4ubeVhHhiVoK/uYAkvzNqfmR4gP4N/9H9RPu+b8YCcN4bQGp7xCuYIV6Xd41WpOMyO+xpcQvjtQQ==", 698 | "dev": true, 699 | "optional": true 700 | }, 701 | "esbuild-linux-arm": { 702 | "version": "0.14.46", 703 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.46.tgz", 704 | "integrity": "sha512-RvTJEi4vj13c5FP9YPp+8Y6x6HK1E7uSqfy3y9UoeaNAzNZWA7fN1U3hQjTL/dy5zTJH5KE64mrt5k5+he+CQA==", 705 | "dev": true, 706 | "optional": true 707 | }, 708 | "esbuild-linux-arm64": { 709 | "version": "0.14.46", 710 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.46.tgz", 711 | "integrity": "sha512-HX0TXCHyI0NEWG4jg8LlW1PbZQbnz+PUH56yjx996cgM5pC90u32drKs/tyJiyyQmNk9OXOogjKw7LEdp/Qc1w==", 712 | "dev": true, 713 | "optional": true 714 | }, 715 | "esbuild-linux-mips64le": { 716 | "version": "0.14.46", 717 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.46.tgz", 718 | "integrity": "sha512-jnb2NDwGqJUVmxn1v0f7seNdDm0nRNWHP9Z3MrWAGnBCdnnDlsjqRFDnbKoaQvWONEa+rOOr/giK+VL0hgQExA==", 719 | "dev": true, 720 | "optional": true 721 | }, 722 | "esbuild-linux-ppc64le": { 723 | "version": "0.14.46", 724 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.46.tgz", 725 | "integrity": "sha512-uu3JTQUrwwauKY9z8yq5MnDyOlT3f2DNOzBcYz4dB78HqwEqilCsifoBGd0WcbED5n57dc59X+LZMTZ8Ose44w==", 726 | "dev": true, 727 | "optional": true 728 | }, 729 | "esbuild-linux-riscv64": { 730 | "version": "0.14.46", 731 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.46.tgz", 732 | "integrity": "sha512-OB29r1EG44ZY34JnXCRERxo7k4pRKoQdaoRg2HIeCavatsXZwW4LCakpLnMQ72vXT1HtpBUABEjHkKkn5JyrUg==", 733 | "dev": true, 734 | "optional": true 735 | }, 736 | "esbuild-linux-s390x": { 737 | "version": "0.14.46", 738 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.46.tgz", 739 | "integrity": "sha512-XQ/U9TueMSGYyPTKyZsJVraiuvxhwCDIMn/QwFXCRCJ6H/Cy/Rq33u9qhpeSziinHKfzJROGx5A8mQY6aYamdQ==", 740 | "dev": true, 741 | "optional": true 742 | }, 743 | "esbuild-netbsd-64": { 744 | "version": "0.14.46", 745 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.46.tgz", 746 | "integrity": "sha512-i15BwqHaAIFp1vBJkitAbHtwXcLk9TdHs/Ia1xGIAutQYXSJNPLM3Z4B4hyfHNEFl2yBqBIYpglMohv2ClNdOQ==", 747 | "dev": true, 748 | "optional": true 749 | }, 750 | "esbuild-openbsd-64": { 751 | "version": "0.14.46", 752 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.46.tgz", 753 | "integrity": "sha512-XwOIFCE140Y/PvjrwjFfa/QLWBuvhR1mPCOa35mKx02jt++wPNgf0qhn6HfdVC3vQe7R46RwTp4q2cp99fepEg==", 754 | "dev": true, 755 | "optional": true 756 | }, 757 | "esbuild-sunos-64": { 758 | "version": "0.14.46", 759 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.46.tgz", 760 | "integrity": "sha512-+kV3JnmfdxBVpHyFvuGXWtu6tXxXApOLPkSrVkMJf6+ns/3PLtPndpzwCzHjD+qYUEk8ln4MA+ufQ2qmjW5mZg==", 761 | "dev": true, 762 | "optional": true 763 | }, 764 | "esbuild-windows-32": { 765 | "version": "0.14.46", 766 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.46.tgz", 767 | "integrity": "sha512-gzGC1Q11B/Bo5A2EX4N22oigWmhL7Z0eDyc8kbSoJjqSrGQuRE7B0uMpluO+q0O/gZ1S3zdw+M4PCWlqOIeXLA==", 768 | "dev": true, 769 | "optional": true 770 | }, 771 | "esbuild-windows-64": { 772 | "version": "0.14.46", 773 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.46.tgz", 774 | "integrity": "sha512-Do2daaskfOjmCB7o3ygz6fD3K6SPjZLERiZLktzHz2oUCwsebKu/gmop0+j/XdrVIXC32wFzHzDS+9CTu9OShw==", 775 | "dev": true, 776 | "optional": true 777 | }, 778 | "esbuild-windows-arm64": { 779 | "version": "0.14.46", 780 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.46.tgz", 781 | "integrity": "sha512-VEzMy6bM60/HT/URTDElyhfi2Pk0quCCrEhRlI4MRno/AIqYUGw0rZwkPl6PeoqVI6BgoBHGY576GWTiPmshCA==", 782 | "dev": true, 783 | "optional": true 784 | }, 785 | "follow-redirects": { 786 | "version": "1.14.8", 787 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", 788 | "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" 789 | }, 790 | "fsevents": { 791 | "version": "2.3.2", 792 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 793 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 794 | "dev": true, 795 | "optional": true 796 | }, 797 | "function-bind": { 798 | "version": "1.1.1", 799 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 800 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 801 | "dev": true 802 | }, 803 | "has": { 804 | "version": "1.0.3", 805 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 806 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 807 | "dev": true, 808 | "requires": { 809 | "function-bind": "^1.1.1" 810 | } 811 | }, 812 | "is-core-module": { 813 | "version": "2.9.0", 814 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", 815 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", 816 | "dev": true, 817 | "requires": { 818 | "has": "^1.0.3" 819 | } 820 | }, 821 | "nanoid": { 822 | "version": "3.3.6", 823 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 824 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 825 | "dev": true 826 | }, 827 | "path-parse": { 828 | "version": "1.0.7", 829 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 830 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 831 | "dev": true 832 | }, 833 | "picocolors": { 834 | "version": "1.0.0", 835 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 836 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 837 | "dev": true 838 | }, 839 | "postcss": { 840 | "version": "8.4.31", 841 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 842 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 843 | "dev": true, 844 | "requires": { 845 | "nanoid": "^3.3.6", 846 | "picocolors": "^1.0.0", 847 | "source-map-js": "^1.0.2" 848 | } 849 | }, 850 | "resolve": { 851 | "version": "1.22.1", 852 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 853 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 854 | "dev": true, 855 | "requires": { 856 | "is-core-module": "^2.9.0", 857 | "path-parse": "^1.0.7", 858 | "supports-preserve-symlinks-flag": "^1.0.0" 859 | } 860 | }, 861 | "rollup": { 862 | "version": "2.75.6", 863 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz", 864 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==", 865 | "dev": true, 866 | "requires": { 867 | "fsevents": "~2.3.2" 868 | } 869 | }, 870 | "source-map-js": { 871 | "version": "1.0.2", 872 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 873 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 874 | "dev": true 875 | }, 876 | "supports-preserve-symlinks-flag": { 877 | "version": "1.0.0", 878 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 879 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 880 | "dev": true 881 | }, 882 | "vite": { 883 | "version": "2.9.16", 884 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz", 885 | "integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==", 886 | "dev": true, 887 | "requires": { 888 | "esbuild": "^0.14.27", 889 | "fsevents": "~2.3.2", 890 | "postcss": "^8.4.13", 891 | "resolve": "^1.22.0", 892 | "rollup": ">=2.59.0 <2.78.0" 893 | } 894 | } 895 | } 896 | } 897 | --------------------------------------------------------------------------------