├── .vscode └── settings.json ├── __tests__ ├── setup.ts ├── init-revalidate-on-focus.test.ts ├── deep-equals.test.ts ├── react.test.tsx ├── persist.test.ts └── vanilla.test.ts ├── killa-logo.png ├── src ├── middleware │ ├── index.ts │ └── persist.ts ├── index.ts ├── utils │ ├── constants.ts │ ├── helpers.ts │ └── deep-equals.ts ├── react.ts └── core.ts ├── killa-revalidate.gif ├── examples ├── next │ ├── src │ │ └── app │ │ │ ├── favicon.ico │ │ │ ├── layout.tsx │ │ │ ├── globals.css │ │ │ └── page.tsx │ ├── next.config.js │ ├── postcss.config.js │ ├── .gitignore │ ├── tailwind.config.js │ ├── package.json │ ├── public │ │ ├── vercel.svg │ │ └── next.svg │ ├── tsconfig.json │ └── README.md ├── react │ ├── vite.config.js │ ├── src │ │ ├── main.jsx │ │ ├── App.css │ │ ├── index.css │ │ ├── App.jsx │ │ └── assets │ │ │ └── react.svg │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ └── package-lock.json └── vanilla │ └── index.html ├── .prettierrc ├── .swcrc ├── pre-publish.mjs ├── tsconfig.json ├── LICENSE ├── vitest.config.mjs ├── eslint.config.mjs ├── esbuild.config.mjs ├── .gitignore ├── package.json └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /__tests__/setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest' 2 | -------------------------------------------------------------------------------- /killa-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JesuHrz/killa/HEAD/killa-logo.png -------------------------------------------------------------------------------- /src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export { persist, normalizeStorage } from 'killa/persist' 2 | -------------------------------------------------------------------------------- /killa-revalidate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JesuHrz/killa/HEAD/killa-revalidate.gif -------------------------------------------------------------------------------- /examples/next/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JesuHrz/killa/HEAD/examples/next/src/app/favicon.ico -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export type { Store, Options, Selector, Subscriber } from 'killa/core' 2 | export { createStore } from 'killa/core' 3 | -------------------------------------------------------------------------------- /examples/next/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /examples/next/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "bracketSameLine": true, 6 | "tabWidth": 2, 7 | "printWidth": 80 8 | } 9 | -------------------------------------------------------------------------------- /examples/react/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /examples/react/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /examples/react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const SYMBOL_SUBSCRIBER = Symbol.for('@@killa-subscriber') 2 | export const SYMBOL_STORE = Symbol.for('@@killa-store') 3 | export const SYMBOL_PERSIST = Symbol.for('@@killa-persist') 4 | 5 | export const IS_WINDOW_DEFINED = typeof window !== 'undefined' 6 | export const IS_DOCUMENT_DEFINED = typeof document !== 'undefined' 7 | export const UNDEFINED = 'undefined' 8 | -------------------------------------------------------------------------------- /examples/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsc": { 3 | "target": "es2015", 4 | "parser": { 5 | "syntax": "typescript", 6 | "jsx": true 7 | }, 8 | "transform": { 9 | "react": { 10 | "runtime": "automatic", 11 | "pragma": "React.createElement", 12 | "pragmaFrag": "React.Fragment", 13 | "throwIfNamespace": true, 14 | "useBuiltins": true 15 | } 16 | } 17 | }, 18 | "sourceMaps": true 19 | } -------------------------------------------------------------------------------- /examples/next/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import { Inter } from 'next/font/google' 3 | 4 | const inter = Inter({ subsets: ['latin'] }) 5 | 6 | export const metadata = { 7 | title: 'Create Next App', 8 | description: 'Generated by create next app', 9 | } 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode 15 | }) { 16 | return ( 17 | 18 | {children} 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /examples/next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "killa": "^1.7.2", 13 | "react": "18.2.0", 14 | "react-dom": "18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "18.0.27", 18 | "@types/react-dom": "18.0.10", 19 | "@vitejs/plugin-react": "3.1.0", 20 | "vite": "4.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/next/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /examples/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "20.1.4", 13 | "@types/react": "18.2.6", 14 | "@types/react-dom": "18.2.4", 15 | "autoprefixer": "10.4.14", 16 | "killa": "1.7.2", 17 | "next": "13.4.2", 18 | "postcss": "8.4.23", 19 | "react": "18.2.0", 20 | "react-dom": "18.2.0", 21 | "tailwindcss": "3.3.2", 22 | "typescript": "5.0.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/next/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/next/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /pre-publish.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { createRequire } from 'module' 4 | 5 | const require = createRequire(import.meta.url) 6 | const pkg = require('./package.json') 7 | 8 | const version = pkg.version 9 | 10 | const prePublish = () => { 11 | try { 12 | const readmePath = path.join(process.cwd(), 'README.md') 13 | const readmeFile = fs.readFileSync(readmePath, 'utf8') 14 | const updatedFile = readmeFile.replaceAll( 15 | /\killa@([^/]+)/g, 16 | `killa@${version}` 17 | ) 18 | 19 | fs.writeFileSync(readmePath, updatedFile) 20 | } catch (err) { 21 | console.error('Error when trying to update the README.md', err) 22 | } 23 | } 24 | 25 | prePublish() 26 | -------------------------------------------------------------------------------- /examples/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /examples/react/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 4 | "module": "es2020", 5 | "strict": true, 6 | "jsx": "react-jsx", 7 | "esModuleInterop": true, 8 | "declaration": true, 9 | "emitDeclarationOnly": true, 10 | "moduleResolution": "node", 11 | "baseUrl": ".", 12 | "outDir": "./dist", 13 | "paths": { 14 | "killa": ["./src/index.ts"], 15 | "killa/*": [ 16 | "./src/*", 17 | "./src/middleware/*", 18 | "./src/utils/*" 19 | ] 20 | } 21 | }, 22 | "include": [ 23 | "src/**/*", 24 | "__tests__/**/*", 25 | "pre-publish.js", 26 | "jest.config.js", 27 | "esbuild.config.js" 28 | ], 29 | "exclude": [ 30 | "node_modules", 31 | "dist", 32 | "coverage", 33 | "examples", 34 | ], 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jesus Hernandez 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 | -------------------------------------------------------------------------------- /vitest.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import path from 'path' 3 | import { fileURLToPath } from 'url' 4 | 5 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 6 | 7 | export default defineConfig({ 8 | test: { 9 | environment: 'jsdom', 10 | globals: true, 11 | coverage: { 12 | include: ['src/**/'], 13 | provider: 'v8', 14 | reporter: ['text', 'json', 'html', 'text-summary'] 15 | }, 16 | dir: '__tests__', 17 | setupFiles: ['__tests__/setup.ts'] 18 | }, 19 | resolve: { 20 | alias: { 21 | 'killa/core': path.resolve(__dirname, './src/core.ts'), 22 | 'killa/react': path.resolve(__dirname, './src/react.ts'), 23 | 'killa/deep-equals': path.resolve( 24 | __dirname, 25 | './src/utils/deep-equals.ts' 26 | ), 27 | 'killa/constants': path.resolve(__dirname, './src/utils/constants.ts'), 28 | 'killa/helpers': path.resolve(__dirname, './src/utils/helpers.ts'), 29 | 'killa/persist': path.resolve(__dirname, './src/middleware/persist.ts'), 30 | killa: path.resolve(__dirname, './src/index.ts') 31 | } 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import { IS_WINDOW_DEFINED, IS_DOCUMENT_DEFINED } from 'killa/constants' 2 | 3 | export const noop = () => {} 4 | 5 | export const [addDocumentEvent, removeDocumentEvent] = 6 | IS_DOCUMENT_DEFINED && document.addEventListener 7 | ? [ 8 | document.addEventListener.bind(document), 9 | document.removeEventListener.bind(document) 10 | ] 11 | : [noop, noop] 12 | 13 | export const [addWindowEvent, removeWindowEvent] = 14 | IS_WINDOW_DEFINED && window.addEventListener 15 | ? [ 16 | window.addEventListener.bind(window), 17 | window.removeEventListener.bind(window) 18 | ] 19 | : [noop, noop] 20 | 21 | export const serialize = (value: T): string => JSON.stringify(value) 22 | 23 | export const deserialize = (value: string): T | null => { 24 | if (value === null) return null 25 | 26 | return JSON.parse(value) 27 | } 28 | 29 | export const merge = ( 30 | object: T, 31 | objectToMerge: U 32 | ): T & U => { 33 | return { 34 | ...object, 35 | ...objectToMerge 36 | } 37 | } 38 | 39 | export const messageError = console.error 40 | 41 | export const encoded = (str: string) => btoa(encodeURIComponent(str)) 42 | export const decoded = (str: string) => decodeURIComponent(atob(str)) 43 | -------------------------------------------------------------------------------- /src/react.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | import UseSyncExternalStoreShim from 'use-sync-external-store/shim/with-selector.js' 3 | 4 | // Utils 5 | import { deepEquals } from 'killa/deep-equals' 6 | import { SYMBOL_STORE } from 'killa/constants' 7 | 8 | // Types 9 | import type { Store } from 'killa/core' 10 | 11 | const useSyncExternalStore = 12 | UseSyncExternalStoreShim.useSyncExternalStoreWithSelector 13 | 14 | const fallbackSelector = (state: T) => state as unknown as U 15 | 16 | export const useStore = ( 17 | store: Store, 18 | selector: ((state: T) => U) | null = fallbackSelector, 19 | silect = false 20 | ): [U, Store['setState']] => { 21 | if (store.$$store !== SYMBOL_STORE) { 22 | throw new Error('Provide a valid store for useStore.') 23 | } 24 | 25 | if (selector === null) { 26 | selector = fallbackSelector 27 | silect = true 28 | } 29 | 30 | const silentState = useRef((): Store['getState'] => { 31 | const state = store.getState() 32 | 33 | return () => state 34 | }) 35 | 36 | const state = useSyncExternalStore( 37 | store.subscribe, 38 | silect ? silentState.current() : store.getState, 39 | silect ? silentState.current() : store.getServerState || store.getState, 40 | selector, 41 | deepEquals 42 | ) 43 | 44 | return [state, store.setState] 45 | } 46 | -------------------------------------------------------------------------------- /examples/next/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/next/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /examples/react/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #646cff; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | @media (prefers-color-scheme: light) { 59 | :root { 60 | color: #213547; 61 | background-color: #ffffff; 62 | } 63 | a:hover { 64 | color: #747bff; 65 | } 66 | button { 67 | background-color: #f9f9f9; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/utils/deep-equals.ts: -------------------------------------------------------------------------------- 1 | export const isObject = (object: any): boolean => { 2 | return object !== null && typeof object === 'object' 3 | } 4 | 5 | export const deepEquals = (typeA: any, typeB: any): boolean => { 6 | if (!isObject(typeA) && !isObject(typeB) && Object.is(typeA, typeB)) { 7 | return true 8 | } 9 | 10 | if (typeA instanceof Date && typeB instanceof Date) { 11 | return Object.is(typeA.getTime(), typeB.getTime()) 12 | } 13 | 14 | if ( 15 | !isObject(typeA) || 16 | typeA === null || 17 | !isObject(typeB) || 18 | typeB === null 19 | ) { 20 | return false 21 | } 22 | 23 | if (typeA instanceof Map && typeB instanceof Map) { 24 | if (typeA.size !== typeB.size) { 25 | return false 26 | } 27 | 28 | for (const item of typeA) { 29 | const key = item[0] 30 | const value = item[1] 31 | 32 | if (!Object.is(value, typeB.get(key))) { 33 | return deepEquals(value, typeB.get(key)) 34 | } 35 | } 36 | 37 | return true 38 | } 39 | 40 | if (typeA instanceof Set && typeB instanceof Set) { 41 | if (typeA.size !== typeB.size) { 42 | return false 43 | } 44 | 45 | return deepEquals([...typeA.values()], [...typeB.values()]) 46 | } 47 | 48 | const keysA = Object.keys(typeA) 49 | const keysB = Object.keys(typeB) 50 | 51 | if (keysA.length !== keysB.length) { 52 | return false 53 | } 54 | 55 | for (const key of keysA) { 56 | const valueA = typeA[key] 57 | const valueB = typeB[key] 58 | const areObjects = isObject(valueA) && isObject(valueB) 59 | 60 | if ( 61 | (areObjects && !deepEquals(valueA, valueB)) || 62 | (!areObjects && !Object.is(valueA, valueB)) 63 | ) { 64 | return false 65 | } 66 | } 67 | 68 | return true 69 | } 70 | -------------------------------------------------------------------------------- /examples/react/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { createStore } from 'killa' 3 | import { useStore } from 'killa/react' 4 | import { persist } from 'killa/persist' 5 | import reactLogo from './assets/react.svg' 6 | import './App.css' 7 | 8 | const store = createStore( 9 | { counter: 0, filter: '' }, 10 | { 11 | use: [ 12 | persist({ 13 | name: 'killa-persist', 14 | encrypted: true 15 | }) 16 | ] 17 | } 18 | ) 19 | 20 | const Counter = () => { 21 | const [state, setState] = useStore(store, (state) => { 22 | return { 23 | counter: state.counter, 24 | filter: state.filter 25 | } 26 | }) 27 | 28 | const handleCounter = () => { 29 | setState(() => { 30 | return { 31 | ...state, 32 | counter: state.counter + 1 33 | } 34 | }) 35 | } 36 | 37 | return ( 38 |
39 | 40 |
41 | ) 42 | } 43 | 44 | const Label = ({ silect, selector = null }) => { 45 | const [state, setState] = useStore(store, selector, silect) 46 | 47 | const update = () => { 48 | setState((state) => ({ counter: state.counter + 1 })) 49 | } 50 | 51 | return

Counter: {state.counter}

52 | } 53 | 54 | function App() { 55 | const selector = useCallback((state) => { 56 | return { 57 | counter: state.counter, 58 | filter: state.filter 59 | } 60 | }, []) 61 | 62 | return ( 63 |
64 |
65 | Vite logo 66 | React logo 67 |
68 |

Vite + React + Killa

69 |
73 | ) 74 | } 75 | 76 | export default App 77 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'eslint/config' 2 | import eslint from '@eslint/js' 3 | import tseslint from 'typescript-eslint' 4 | import react from 'eslint-plugin-react' 5 | import reactHooks from 'eslint-plugin-react-hooks' 6 | import vitest from '@vitest/eslint-plugin' 7 | import importPlugin from 'eslint-plugin-import' 8 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' 9 | import testingLibrary from 'eslint-plugin-testing-library' 10 | import jestDom from 'eslint-plugin-jest-dom' 11 | 12 | export default defineConfig( 13 | { 14 | ignores: ['dist/**', 'node_modules/**', 'examples/**', 'coverage/**'] 15 | }, 16 | eslint.configs.recommended, 17 | ...tseslint.configs.recommended, 18 | importPlugin.flatConfigs.recommended, 19 | react.configs.flat.recommended, 20 | react.configs.flat['jsx-runtime'], 21 | eslintPluginPrettierRecommended, 22 | { 23 | files: ['src/**/*.{js,jsx,ts,tsx}'], 24 | plugins: { 25 | 'react-hooks': reactHooks 26 | }, 27 | extends: ['react-hooks/recommended'], 28 | rules: { 29 | 'react-hooks/rules-of-hooks': 'error', 30 | 'react-hooks/exhaustive-deps': 'warn' 31 | } 32 | }, 33 | { 34 | settings: { 35 | react: { 36 | version: 'detect' 37 | }, 38 | 'import/resolver': { 39 | typescript: true 40 | } 41 | }, 42 | rules: { 43 | '@typescript-eslint/no-explicit-any': 'warn', 44 | '@typescript-eslint/no-unused-vars': [ 45 | 'error', 46 | { 47 | argsIgnorePattern: '^_', 48 | varsIgnorePattern: '^_', 49 | caughtErrorsIgnorePattern: '^_' 50 | } 51 | ] 52 | } 53 | }, 54 | { 55 | files: ['__tests__/**/*.{ts,tsx}'], 56 | ...testingLibrary.configs['flat/react'], 57 | ...jestDom.configs['flat/recommended'], 58 | ...vitest.configs.recommended 59 | }, 60 | { 61 | files: ['*.mjs'], 62 | languageOptions: { 63 | globals: { 64 | process: 'readonly', 65 | console: 'readonly' 66 | } 67 | } 68 | } 69 | ) 70 | -------------------------------------------------------------------------------- /examples/next/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useCallback } from 'react' 4 | import Image from 'next/image' 5 | import { createStore } from 'killa' 6 | import { useStore } from 'killa/react' 7 | import { persist } from 'killa/persist' 8 | 9 | const store = createStore( 10 | { counter: 0, filter: '' }, 11 | { 12 | use: [persist({ name: 'next/killa-persist' })] 13 | } 14 | ) 15 | 16 | const Counter = () => { 17 | const [_, setState] = useStore(store, (state) => { 18 | return { 19 | counter: state.counter, 20 | filter: state.filter, 21 | subobject: state.subobject 22 | } 23 | }) 24 | 25 | const handleCounter = useCallback(() => { 26 | setState((state) => { 27 | return { 28 | ...state, 29 | counter: state.counter + 1 30 | } 31 | }) 32 | }, [setState]) 33 | return ( 34 | 39 | ) 40 | } 41 | 42 | const Label = () => { 43 | const [state] = useStore(store, () => store.getState()) 44 | console.log('state', state) 45 | return

Counter: {state.counter}

46 | } 47 | 48 | export default function Home() { 49 | return ( 50 |
51 |
52 | Next.js Logo 60 |

+ Killa

61 |
62 |
63 |
66 |
67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /__tests__/init-revalidate-on-focus.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, beforeEach, afterEach, expect, it, vi } from 'vitest' 2 | import { EventEmitter } from 'events' 3 | 4 | const FOCUS_EVENT = 'focus' 5 | const VISIBILITYCHANGE_EVENT = 'visibilitychange' 6 | 7 | declare global { 8 | // eslint-disable-next-line @typescript-eslint/no-namespace 9 | namespace NodeJS { 10 | interface EventEmitter { 11 | addEventListener: typeof EventEmitter.prototype.on 12 | removeEventListener: typeof EventEmitter.prototype.off 13 | } 14 | } 15 | } 16 | 17 | EventEmitter.prototype['addEventListener'] = EventEmitter.prototype.on 18 | EventEmitter.prototype['removeEventListener'] = EventEmitter.prototype.off 19 | 20 | const eventEmitter = new EventEmitter() 21 | 22 | describe('Init Revalidate On Focus', () => { 23 | type TWindow = typeof global.window 24 | type TDocument = typeof global.document 25 | 26 | const globalSpy = { 27 | window: vi.spyOn(global, 'window', 'get'), 28 | document: vi.spyOn(global, 'document', 'get') 29 | } 30 | 31 | beforeEach(() => { 32 | globalSpy.window.mockImplementation( 33 | () => eventEmitter as unknown as TWindow 34 | ) 35 | globalSpy.document.mockImplementation( 36 | () => eventEmitter as unknown as TDocument 37 | ) 38 | 39 | vi.resetModules() 40 | }) 41 | 42 | afterEach(() => { 43 | globalSpy.window.mockClear() 44 | globalSpy.document.mockClear() 45 | }) 46 | 47 | it('Should trigger focus event', async () => { 48 | const mockFn = vi.fn() 49 | 50 | const { initRevalidateOnFocus } = await import('../src/middleware/persist') 51 | 52 | const revalidateOnFocus = initRevalidateOnFocus(mockFn) 53 | 54 | // Trigger focus event 55 | eventEmitter.emit(FOCUS_EVENT) 56 | 57 | // Remove focus event from window 58 | revalidateOnFocus() 59 | 60 | eventEmitter.emit(FOCUS_EVENT) 61 | 62 | expect(mockFn).toHaveBeenCalledTimes(1) 63 | }) 64 | 65 | it('Should trigger visibilitychange event', async () => { 66 | const mockFn = vi.fn() 67 | 68 | globalSpy.window.mockImplementation( 69 | () => eventEmitter as unknown as TWindow 70 | ) 71 | globalSpy.document.mockImplementation( 72 | () => eventEmitter as unknown as TDocument 73 | ) 74 | 75 | const { initRevalidateOnFocus } = await import('../src/middleware/persist') 76 | 77 | const revalidateOnFocus = initRevalidateOnFocus(mockFn) 78 | 79 | // Trigger visibilitychange event 80 | eventEmitter.emit(VISIBILITYCHANGE_EVENT) 81 | 82 | // Remove visibilitychange event from document 83 | revalidateOnFocus() 84 | 85 | eventEmitter.emit(VISIBILITYCHANGE_EVENT) 86 | 87 | expect(mockFn).toHaveBeenCalledTimes(1) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /examples/vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Store - Vanilla 7 | 8 | 9 |

Counter: 0

10 | 11 | 12 | 13 | 14 |

15 | 16 | 17 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { glob } from 'glob' 3 | import esbuild from 'esbuild' 4 | import { dtsPlugin } from 'esbuild-plugin-d.ts' 5 | 6 | const buildForCustomEnvironment = async ({ 7 | format = 'cjs', 8 | isDev = false, 9 | ...options 10 | } = {}) => { 11 | console.log(`\n🔄 Start building for ${format}`) 12 | 13 | const entryPoints = glob.sync(path.join(process.cwd(), 'src/**/*.ts')) 14 | 15 | try { 16 | const result = await esbuild.build({ 17 | entryPoints, 18 | outdir: `dist/${format}`, 19 | packages: 'external', 20 | format, 21 | platform: 'node', 22 | target: ['node16'], 23 | sourcemap: isDev ? 'inline' : false, 24 | logLevel: isDev ? 'info' : 'warning', 25 | ...options 26 | }) 27 | 28 | console.log(`\n✅ Build for ${format} successful 🚀`) 29 | return result 30 | } catch (_error) { 31 | console.error(`\n❌ Build failed for ${format}:`, _error) 32 | throw _error 33 | } 34 | } 35 | 36 | const buildForBrowser = async ({ 37 | output, 38 | entryPoint, 39 | isDev = false, 40 | ...options 41 | }) => { 42 | try { 43 | console.log(`\n🔄 Start building for browser: ${output}`) 44 | 45 | await esbuild.build({ 46 | entryPoints: [entryPoint], 47 | bundle: true, 48 | outfile: `dist/umd/${output}.min.js`, 49 | minify: !isDev, 50 | sourcemap: isDev ? 'inline' : false, 51 | globalName: `globalThis.${output}`, 52 | platform: 'browser', 53 | format: 'iife', 54 | target: ['chrome58', 'edge18', 'firefox57', 'safari11', 'node16'], 55 | logLevel: isDev ? 'info' : 'warning', 56 | ...options 57 | }) 58 | 59 | console.log(`\n✅ Build for ${output} successful 🚀`) 60 | } catch (_error) { 61 | console.error(`\n❌ Build failed for ${output}:`, _error) 62 | throw _error 63 | } 64 | } 65 | 66 | const init = async (isDev = false) => { 67 | try { 68 | console.log(`⚒️ ${isDev ? 'Development' : 'Production'} build started...`) 69 | 70 | await buildForCustomEnvironment({ 71 | format: 'cjs', 72 | plugins: [dtsPlugin()], 73 | isDev 74 | }) 75 | 76 | await buildForCustomEnvironment({ 77 | format: 'esm', 78 | plugins: [dtsPlugin()], 79 | isDev 80 | }) 81 | 82 | await buildForCustomEnvironment({ 83 | format: 'esm', 84 | outExtension: { '.js': '.mjs' }, 85 | plugins: [dtsPlugin()], 86 | isDev 87 | }) 88 | 89 | const allEntryPoints = [ 90 | { output: 'killa', entryPoint: './src/core.ts' }, 91 | { 92 | output: 'killaMiddlewares', 93 | entryPoint: './src/middleware' 94 | } 95 | ] 96 | 97 | for (let i = 0; i < allEntryPoints.length; i++) { 98 | await buildForBrowser({ ...allEntryPoints[i], isDev }) 99 | } 100 | 101 | console.log('\n🎉 All builds completed successfully!') 102 | } catch (e) { 103 | console.error('\n💥 Build process failed!', e) 104 | process.exit(1) 105 | } 106 | } 107 | 108 | const isDev = process.argv.includes('--dev') 109 | 110 | init(isDev) 111 | -------------------------------------------------------------------------------- /examples/react/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core.ts: -------------------------------------------------------------------------------- 1 | import clone from 'just-clone' 2 | 3 | import { deepEquals, isObject } from 'killa/deep-equals' 4 | import { SYMBOL_STORE, SYMBOL_SUBSCRIBER } from 'killa/constants' 5 | 6 | export interface Options { 7 | compare?: (a: unknown, b: unknown) => boolean 8 | clone?: (state: T) => T 9 | use?: ((store: Store) => void)[] 10 | } 11 | 12 | type State = Record 13 | 14 | export type Selector = (state: T) => U 15 | 16 | export interface Subscriber { 17 | (state: T, prevState: T): void 18 | $$subscriber?: symbol 19 | $$selectorState?: U 20 | $$selector?: Selector 21 | } 22 | 23 | export interface Store { 24 | $$store: symbol 25 | getState: () => T 26 | setState: (fn: (state: T) => Partial, force?: boolean) => void 27 | subscribe: { 28 | (subscriber: Subscriber, selector?: (State: T) => U): () => boolean 29 | } 30 | getServerState: () => T 31 | } 32 | 33 | type InitializerFn = ( 34 | getState: Store['getState'], 35 | setState: Store['setState'] 36 | ) => T 37 | 38 | export function createStore | T>( 39 | initializer: U = Object.assign({}), 40 | options?: Options 41 | ) { 42 | let state: T 43 | const subscribers = new Set>() 44 | 45 | const compare = 46 | typeof options?.compare === 'function' ? options.compare : deepEquals 47 | 48 | const getState = () => clone(state) 49 | 50 | const setState: Store['setState'] = (fn, force = false) => { 51 | const newState = fn(getState()) || {} 52 | 53 | if (!compare(state, newState)) { 54 | const prevState = state 55 | 56 | state = force 57 | ? Object.assign(newState) 58 | : Object.assign(getState(), clone(newState)) 59 | 60 | subscribers.forEach((subscriber) => { 61 | const _prevState = clone(prevState) 62 | const _newState = getState() 63 | 64 | if (subscriber.$$subscriber && subscriber.$$selector) { 65 | const selectorState = subscriber.$$selectorState 66 | const nextselectorState = subscriber.$$selector(state) 67 | 68 | if (!compare(selectorState, nextselectorState)) { 69 | subscriber.$$selectorState = nextselectorState 70 | subscriber(_newState, _prevState) 71 | } 72 | 73 | return 74 | } 75 | 76 | subscriber(_newState, _prevState) 77 | }) 78 | } 79 | } 80 | 81 | const _setState: Store['setState'] = (...args) => { 82 | store.setState(...args) 83 | } 84 | 85 | const initialState: T = 86 | typeof initializer === 'function' 87 | ? initializer(getState, _setState) 88 | : initializer 89 | 90 | const subscribe: Store['subscribe'] = (subscriber, selector) => { 91 | if (typeof selector === 'function') { 92 | subscriber.$$subscriber = SYMBOL_SUBSCRIBER 93 | subscriber.$$selectorState = selector(state) 94 | subscriber.$$selector = selector 95 | } 96 | 97 | subscribers.add(subscriber) 98 | return () => subscribers.delete(subscriber) 99 | } 100 | 101 | if (!isObject(initialState)) throw new Error('Store must be an object.') 102 | 103 | state = clone(initialState) 104 | 105 | const resetState = (state: unknown | null = null) => { 106 | const newState = state && isObject(state) ? state : clone(initialState) 107 | store.setState(() => newState, true) 108 | } 109 | 110 | const destroy = () => subscribers.clear() 111 | 112 | const store = { 113 | $$store: SYMBOL_STORE, 114 | getState, 115 | setState, 116 | subscribe, 117 | getServerState: () => initialState, 118 | resetState, 119 | destroy 120 | } 121 | 122 | if (options?.use && Array.isArray(options.use)) { 123 | options.use.forEach((middleware) => middleware(store)) 124 | } 125 | 126 | if (process.env.NODE_ENV !== 'test') { 127 | return Object.freeze(store) 128 | } 129 | 130 | return store 131 | } 132 | -------------------------------------------------------------------------------- /__tests__/deep-equals.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { deepEquals } from '../src/utils/deep-equals' 4 | 5 | describe('DeepEquals', () => { 6 | it('Should export deepEquals as named export', () => { 7 | expect(deepEquals).toBeInstanceOf(Function) 8 | }) 9 | 10 | it('Should compare strings', () => { 11 | expect(deepEquals('string', 'string')).toBe(true) 12 | expect(deepEquals('string', 'other-string')).toBe(false) 13 | }) 14 | 15 | it('Should compare numbers', () => { 16 | expect(deepEquals(1, 1)).toBe(true) 17 | expect(deepEquals(1, 2)).toBe(false) 18 | }) 19 | 20 | it('Should compare booleans', () => { 21 | expect(deepEquals(true, true)).toBe(true) 22 | expect(deepEquals(true, false)).toBe(false) 23 | }) 24 | 25 | it('Should compare nulls', () => { 26 | expect(deepEquals(null, null)).toBe(true) 27 | expect(deepEquals(null, false)).toBe(false) 28 | }) 29 | 30 | it('Should compare arrays', () => { 31 | expect(deepEquals([1, 2, 3], [1, 2, 3])).toBe(true) 32 | expect(deepEquals([1, 2, 3], [3, 2, 1])).toBe(false) 33 | }) 34 | 35 | it('Should compare arrays of objects', () => { 36 | expect( 37 | deepEquals( 38 | [ 39 | { a: 1, b: 2, c: 'c' }, 40 | { a: 1, b: 2, c: 'c' } 41 | ], 42 | [ 43 | { a: 1, b: 2, c: 'c' }, 44 | { a: 1, b: 2, c: 'c' } 45 | ] 46 | ) 47 | ).toBe(true) 48 | expect( 49 | deepEquals( 50 | [ 51 | { a: 1, b: 2, c: 'c' }, 52 | { a: 1, b: 2, c: 'c' } 53 | ], 54 | [ 55 | { a: 2, b: 1, c: 'c' }, 56 | { a: 1, b: 2, c: 'c' } 57 | ] 58 | ) 59 | ).toBe(false) 60 | }) 61 | 62 | it('Should compare simple objects', () => { 63 | expect(deepEquals({ a: 1, b: 2, c: 'c' }, { a: 1, b: 2, c: 'c' })).toBe( 64 | true 65 | ) 66 | expect(deepEquals({ a: 1, b: 2, c: 'c' }, { a: 2, b: 1 })).toBe(false) 67 | }) 68 | 69 | it('Should compare nested objects', () => { 70 | expect( 71 | deepEquals( 72 | { a: 1, b: 2, c: { y: 2, z: '1' } }, 73 | { a: 1, b: 2, c: { y: 2, z: '1' } } 74 | ) 75 | ).toBe(true) 76 | expect( 77 | deepEquals( 78 | { a: 1, b: 2, c: { y: 2, z: '1' } }, 79 | { a: 1, b: 2, c: { y: 2, z: '2' } } 80 | ) 81 | ).toBe(false) 82 | }) 83 | 84 | it('Should compare functions', () => { 85 | const foo = () => 'foo' 86 | const bar = () => 'bar' 87 | 88 | expect(deepEquals(foo, foo)).toBe(true) 89 | expect(deepEquals(foo, bar)).toBe(false) 90 | }) 91 | 92 | it('Should compare Maps', () => { 93 | const firstMap = new Map([ 94 | ['Map', { name: 'I am a map', phone: '213-555-1234' }] 95 | ]) 96 | const secondMap = new Map([ 97 | ['Map', { name: 'I am a map', phone: '213-555-1234' }] 98 | ]) 99 | const thirdMap = new Map([ 100 | ['Map', { name: 'I am third map', phone: '213-555-1234' }], 101 | [1, 1] 102 | ]) 103 | 104 | expect(deepEquals(firstMap, firstMap)).toBe(true) 105 | expect(deepEquals(firstMap, secondMap)).toBe(true) 106 | expect(deepEquals(firstMap, thirdMap)).toBe(false) 107 | }) 108 | 109 | it('Should compare Sets', () => { 110 | const firstSet = new Set([ 111 | '1', 112 | { name: 'I am a set', phone: '213-555-1234' } 113 | ]) 114 | const secondSet = new Set([ 115 | '1', 116 | { name: 'I am a set', phone: '213-555-1234' } 117 | ]) 118 | const thirdSet = new Set([ 119 | '2', 120 | { name: 'I am another', phone: '213-555-1234' }, 121 | 2 122 | ]) 123 | 124 | expect(deepEquals(firstSet, secondSet)).toBe(true) 125 | expect(deepEquals(firstSet, thirdSet)).toBe(false) 126 | }) 127 | 128 | it('Should compare Dates', () => { 129 | expect( 130 | deepEquals( 131 | new Date('2023-01-04T00:00:00.000Z'), 132 | new Date('2023-01-04T00:00:00.000Z') 133 | ) 134 | ).toBe(true) 135 | 136 | expect( 137 | deepEquals( 138 | new Date('2023-01-04T00:00:00.000Z'), 139 | new Date('2023-01-05T00:00:00.000Z') 140 | ) 141 | ).toBe(false) 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /__tests__/react.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest' 2 | 3 | import { describe, expect, it, beforeEach } from 'vitest' 4 | import { useEffect, useRef } from 'react' 5 | import { 6 | render, 7 | screen, 8 | cleanup, 9 | fireEvent, 10 | renderHook, 11 | act 12 | } from '@testing-library/react' 13 | 14 | import { createStore, Store } from '../src' 15 | import { useStore } from '../src/react' 16 | 17 | describe('React', () => { 18 | let store: Store<{ counter: number; filter: string }> 19 | 20 | const Counter = ({ label = 'Counter +1' }: { label?: string }) => { 21 | const [state, setState] = useStore(store, (state) => { 22 | return { 23 | counter: state.counter 24 | } 25 | }) 26 | 27 | const handleCounter = () => { 28 | setState((state) => { 29 | return { 30 | counter: state.counter + 1 31 | } 32 | }) 33 | } 34 | 35 | return ( 36 |
37 |

Counter: {state.counter}

38 | 39 |
40 | ) 41 | } 42 | 43 | beforeEach(() => { 44 | store = createStore({ 45 | counter: 1, 46 | filter: '' 47 | }) 48 | 49 | cleanup() 50 | }) 51 | 52 | it('Should render Counter with initial state', () => { 53 | render() 54 | const $counter = screen.getByText(/counter: 1/i) 55 | expect($counter).toBeInTheDocument() 56 | }) 57 | 58 | it('Should render Counter with the selector by default', () => { 59 | const Component = () => { 60 | useStore(store) 61 | 62 | return

Component

63 | } 64 | 65 | render() 66 | const $component = screen.getByText(/component/i) 67 | expect($component).toBeInTheDocument() 68 | }) 69 | 70 | it('Should throw a error when providing an invalid store to useStore as param', () => { 71 | try { 72 | renderHook(() => { 73 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 74 | // @ts-ignore 75 | useStore({}) 76 | }) 77 | throw new Error('Should fail because the store is invalid') 78 | } catch (error: any) { 79 | expect(error.message).toBe('Provide a valid store for useStore.') 80 | } 81 | }) 82 | 83 | it('Should update Counter state when clicking on the counter button', () => { 84 | render() 85 | 86 | const $button = screen.getByRole('button') 87 | 88 | fireEvent.click($button) 89 | 90 | const $counter = screen.getByText(/counter: 2/i) 91 | 92 | expect($counter).toBeInTheDocument() 93 | }) 94 | 95 | it('Should rerender the Counter component when the store is updated', () => { 96 | render() 97 | 98 | expect(screen.getByText(/counter: 1/i)).toBeInTheDocument() 99 | 100 | act(() => { 101 | store.setState((state) => { 102 | return { 103 | ...state, 104 | counter: state.counter + 1 105 | } 106 | }) 107 | }) 108 | 109 | expect(screen.getByText(/counter: 2/i)).toBeInTheDocument() 110 | }) 111 | 112 | it('Should rerender second Counter component after updating the store from the first Counter component', () => { 113 | const App = () => { 114 | return ( 115 |
116 | 117 | 118 |
119 | ) 120 | } 121 | render() 122 | 123 | const $buttons = screen.getAllByRole('button') 124 | 125 | expect(screen.getAllByText(/counter: 1/i)).toHaveLength(2) 126 | expect($buttons).toHaveLength(2) 127 | 128 | fireEvent.click($buttons[0]) 129 | 130 | expect(screen.getAllByText(/counter: 2/i)).toHaveLength(2) 131 | }) 132 | 133 | it('should rerender once after updating the store', () => { 134 | const { result } = renderHook(() => { 135 | const countRef = useRef(0) 136 | 137 | const [_, setState] = useStore(store, (state) => { 138 | return { 139 | counter: state.counter, 140 | filter: state.filter 141 | } 142 | }) 143 | 144 | useEffect(() => { 145 | setState((state) => { 146 | return { 147 | ...state, 148 | counter: state.counter + 1 149 | } 150 | }) 151 | }, [setState]) 152 | 153 | countRef.current++ 154 | 155 | return countRef.current 156 | }) 157 | 158 | expect(result.current).toBe(2) 159 | }) 160 | }) 161 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,windows,macos,linux 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | # General 21 | .DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | 25 | # Icon must end with two \r 26 | Icon 27 | 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### macOS Patch ### 49 | # iCloud generated files 50 | *.icloud 51 | 52 | ### Node ### 53 | # Logs 54 | logs 55 | *.log 56 | npm-debug.log* 57 | yarn-debug.log* 58 | yarn-error.log* 59 | lerna-debug.log* 60 | .pnpm-debug.log* 61 | 62 | # Diagnostic reports (https://nodejs.org/api/report.html) 63 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 64 | 65 | # Runtime data 66 | pids 67 | *.pid 68 | *.seed 69 | *.pid.lock 70 | 71 | # Directory for instrumented libs generated by jscoverage/JSCover 72 | lib-cov 73 | 74 | # Coverage directory used by tools like istanbul 75 | coverage 76 | *.lcov 77 | 78 | # nyc test coverage 79 | .nyc_output 80 | 81 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 82 | .grunt 83 | 84 | # Bower dependency directory (https://bower.io/) 85 | bower_components 86 | 87 | # node-waf configuration 88 | .lock-wscript 89 | 90 | # Compiled binary addons (https://nodejs.org/api/addons.html) 91 | build/Release 92 | 93 | # Dependency directories 94 | node_modules/ 95 | jspm_packages/ 96 | 97 | # Snowpack dependency directory (https://snowpack.dev/) 98 | web_modules/ 99 | 100 | # TypeScript cache 101 | *.tsbuildinfo 102 | 103 | # Optional npm cache directory 104 | .npm 105 | 106 | # Optional eslint cache 107 | .eslintcache 108 | 109 | # Optional stylelint cache 110 | .stylelintcache 111 | 112 | # Microbundle cache 113 | .rpt2_cache/ 114 | .rts2_cache_cjs/ 115 | .rts2_cache_es/ 116 | .rts2_cache_umd/ 117 | 118 | # Optional REPL history 119 | .node_repl_history 120 | 121 | # Output of 'npm pack' 122 | *.tgz 123 | 124 | # Yarn Integrity file 125 | .yarn-integrity 126 | 127 | # dotenv environment variable files 128 | .env 129 | .env.development.local 130 | .env.test.local 131 | .env.production.local 132 | .env.local 133 | 134 | # parcel-bundler cache (https://parceljs.org/) 135 | .cache 136 | .parcel-cache 137 | 138 | # Next.js build output 139 | .next 140 | out 141 | 142 | # Nuxt.js build / generate output 143 | .nuxt 144 | dist 145 | 146 | # Gatsby files 147 | .cache/ 148 | # Comment in the public line in if your project uses Gatsby and not Next.js 149 | # https://nextjs.org/blog/next-9-1#public-directory-support 150 | # public 151 | 152 | # vuepress build output 153 | .vuepress/dist 154 | 155 | # vuepress v2.x temp and cache directory 156 | .temp 157 | 158 | # Docusaurus cache and generated files 159 | .docusaurus 160 | 161 | # Serverless directories 162 | .serverless/ 163 | 164 | # FuseBox cache 165 | .fusebox/ 166 | 167 | # DynamoDB Local files 168 | .dynamodb/ 169 | 170 | # TernJS port file 171 | .tern-port 172 | 173 | # Stores VSCode versions used for testing VSCode extensions 174 | .vscode-test 175 | 176 | # yarn v2 177 | .yarn/cache 178 | .yarn/unplugged 179 | .yarn/build-state.yml 180 | .yarn/install-state.gz 181 | .pnp.* 182 | 183 | ### Node Patch ### 184 | # Serverless Webpack directories 185 | .webpack/ 186 | 187 | # Optional stylelint cache 188 | 189 | # SvelteKit build / generate output 190 | .svelte-kit 191 | 192 | ### Windows ### 193 | # Windows thumbnail cache files 194 | Thumbs.db 195 | Thumbs.db:encryptable 196 | ehthumbs.db 197 | ehthumbs_vista.db 198 | 199 | # Dump file 200 | *.stackdump 201 | 202 | # Folder config file 203 | [Dd]esktop.ini 204 | 205 | # Recycle Bin used on file shares 206 | $RECYCLE.BIN/ 207 | 208 | # Windows Installer files 209 | *.cab 210 | *.msi 211 | *.msix 212 | *.msm 213 | *.msp 214 | 215 | # Windows shortcuts 216 | *.lnk 217 | 218 | # End of https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "killa", 3 | "version": "1.10.0", 4 | "description": "State management for Vanilla and React", 5 | "main": "./dist/cjs/index.js", 6 | "exports": { 7 | "./package.json": "./package.json", 8 | ".": { 9 | "import": { 10 | "types": "./dist/esm/index.d.ts", 11 | "default": "./dist/esm/index.mjs" 12 | }, 13 | "types": "./dist/cjs/index.d.ts", 14 | "module": "./dist/esm/index.js", 15 | "default": "./dist/cjs/index.js" 16 | }, 17 | "./core": { 18 | "import": { 19 | "types": "./dist/esm/core.d.ts", 20 | "default": "./dist/esm/core.mjs" 21 | }, 22 | "types": "./dist/cjs/core.d.ts", 23 | "module": "./dist/esm/core.js", 24 | "default": "./dist/cjs/core.js" 25 | }, 26 | "./react": { 27 | "import": { 28 | "types": "./dist/esm/react.d.ts", 29 | "default": "./dist/esm/react.mjs" 30 | }, 31 | "types": "./dist/cjs/react.d.ts", 32 | "module": "./dist/esm/react.js", 33 | "default": "./dist/cjs/react.js" 34 | }, 35 | "./deep-equals": { 36 | "import": { 37 | "types": "./dist/esm/utils/deep-equals.d.ts", 38 | "default": "./dist/esm/utils/deep-equals.mjs" 39 | }, 40 | "types": "./dist/cjs/utils/deep-equals.d.ts", 41 | "module": "./dist/esm/utils/deep-equals.js", 42 | "default": "./dist/cjs/utils/deep-equals.js" 43 | }, 44 | "./constants": { 45 | "import": { 46 | "types": "./dist/esm/utils/constants.d.ts", 47 | "default": "./dist/esm/utils/constants.mjs" 48 | }, 49 | "types": "./dist/cjs/utils/constants.d.ts", 50 | "module": "./dist/esm/utils/constants.js", 51 | "default": "./dist/cjs/utils/constants.js" 52 | }, 53 | "./persist": { 54 | "import": { 55 | "types": "./dist/esm/middleware/persist.d.ts", 56 | "default": "./dist/esm/middleware/persist.mjs" 57 | }, 58 | "types": "./dist/cjs/middleware/persist.d.ts", 59 | "module": "./dist/esm/middleware/persist.js", 60 | "default": "./dist/cjs/middleware/persist.js" 61 | }, 62 | "./helpers": { 63 | "import": { 64 | "types": "./dist/esm/utils/helpers.d.ts", 65 | "default": "./dist/esm/utils/helpers.mjs" 66 | }, 67 | "types": "./dist/cjs/utils/helpers.d.ts", 68 | "module": "./dist/esm/utils/helpers.js", 69 | "default": "./dist/cjs/utils/helpers.js" 70 | } 71 | }, 72 | "files": [ 73 | "dist" 74 | ], 75 | "sideEffects": false, 76 | "scripts": { 77 | "dev:watch": "npx nodemon --ext 'ts,js' --exec 'node dev-build.js'", 78 | "build:watch": "npx nodemon --ext 'ts,js' --exec 'npm run build'", 79 | "build": "npm run prebuild && node esbuild.config.mjs", 80 | "prebuild": "shx rm -rf dist", 81 | "lint": "eslint .", 82 | "lint:fix": "eslint . --fix", 83 | "test": "vitest run --coverage", 84 | "test:watch": "vitest", 85 | "test:ui": "vitest --ui", 86 | "prepublishOnly": "npm run lint && npm run test && npm run build && node pre-publish.mjs" 87 | }, 88 | "engines": { 89 | "node": ">=12" 90 | }, 91 | "keywords": [ 92 | "killa", 93 | "react", 94 | "state", 95 | "management", 96 | "store" 97 | ], 98 | "repository": { 99 | "type": "git", 100 | "url": "git+https://github.com/jesuhrz/killa.git" 101 | }, 102 | "author": "Jesus Hernandez ", 103 | "license": "MIT", 104 | "bugs": { 105 | "url": "https://github.com/jesuhrz/killa/issues" 106 | }, 107 | "homepage": "https://github.com/jesuhrz/killa#readme", 108 | "dependencies": { 109 | "just-clone": "6.2.0", 110 | "use-sync-external-store": ">=1.2.0" 111 | }, 112 | "devDependencies": { 113 | "@eslint/js": "9.36.0", 114 | "@swc/core": "1.13.19", 115 | "@testing-library/jest-dom": "6.9.1", 116 | "@testing-library/react": "16.3.0", 117 | "@testing-library/user-event": "14.6.1", 118 | "@types/clone": "2.1.4", 119 | "@types/node": "24.6.2", 120 | "@types/react": "18.3.1", 121 | "@types/use-sync-external-store": "1.5.0", 122 | "@vitest/coverage-v8": "3.2.4", 123 | "@vitest/eslint-plugin": "1.3.16", 124 | "@vitest/ui": "3.2.4", 125 | "esbuild": "0.25.10", 126 | "esbuild-plugin-d.ts": "1.3.1", 127 | "eslint": "9.36.0", 128 | "eslint-config-prettier": "10.1.8", 129 | "eslint-import-resolver-typescript": "4.4.4", 130 | "eslint-plugin-import": "2.32.0", 131 | "eslint-plugin-jest-dom": "5.5.0", 132 | "eslint-plugin-prettier": "5.5.4", 133 | "eslint-plugin-react": "7.37.5", 134 | "eslint-plugin-react-hooks": "6.1.1", 135 | "eslint-plugin-testing-library": "7.11.0", 136 | "jsdom": "27.0.0", 137 | "prettier": "3.6.2", 138 | "react": "18.3.1", 139 | "react-dom": "18.3.1", 140 | "shx": "0.4.0", 141 | "typescript": "5.9.2", 142 | "typescript-eslint": "8.45.0", 143 | "use-sync-external-store": "1.5.0", 144 | "vitest": "3.2.4" 145 | }, 146 | "peerDependencies": { 147 | "react": ">=18.0.0", 148 | "use-sync-external-store": ">=1.2.0" 149 | }, 150 | "nodemonConfig": { 151 | "ignore": [ 152 | "**/__test__/**", 153 | "**/example/**", 154 | "**/dist/**" 155 | ] 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/middleware/persist.ts: -------------------------------------------------------------------------------- 1 | import { type Store } from 'killa/core' 2 | import { SYMBOL_PERSIST, SYMBOL_STORE } from 'killa/constants' 3 | import { 4 | addDocumentEvent, 5 | removeDocumentEvent, 6 | serialize, 7 | deserialize, 8 | merge, 9 | addWindowEvent, 10 | removeWindowEvent, 11 | encoded, 12 | decoded 13 | } from 'killa/helpers' 14 | 15 | export interface StoreWithPersist { 16 | $$persist: symbol 17 | name: string 18 | destroy: () => void 19 | rehydrate: () => void 20 | hydrated: () => boolean 21 | } 22 | 23 | declare module '../core' { 24 | interface Store { 25 | persist?: StoreWithPersist 26 | } 27 | } 28 | 29 | /** Killa Storage API interface. */ 30 | export interface CustomStorage { 31 | /** 32 | * Returns the current value associated with the given key, 33 | * or null if the given key does not exist. 34 | **/ 35 | getItem: (name: string) => T | null 36 | /** 37 | * Sets the value of the pair identified by key to value, creating a 38 | * new key/value pair if none existed for key previously. 39 | */ 40 | setItem: (name: string, value: unknown) => void 41 | /** 42 | * Removes the key/value pair with the given key, if a key/value pair with 43 | * the given key exists. 44 | */ 45 | removeItem: (name: string) => void 46 | } 47 | 48 | export interface PersistConfig { 49 | /** Storage name (unique) */ 50 | name?: string 51 | /** 52 | * @default () => localStorage 53 | */ 54 | storage?: CustomStorage | (() => CustomStorage) 55 | merge?: (state: T, persistedState: T) => T 56 | /** 57 | * Enable Revalidate mode 58 | * @default true 59 | */ 60 | revalidate?: boolean 61 | /** 62 | * Timeout to trigger the revalidate event in milliseconds 63 | * @default 200 64 | */ 65 | revalidateTimeout?: number 66 | /** 67 | * Encrypt store using btoa and atob 68 | * @default false 69 | */ 70 | encrypted?: boolean 71 | } 72 | 73 | export const normalizeStorage = ( 74 | initializerStorage: () => CustomStorage, 75 | { encrypted = false } = {} 76 | ) => { 77 | try { 78 | const storage = initializerStorage() 79 | if (!storage) return null 80 | 81 | return { 82 | getItem: (name) => { 83 | const _name = encrypted ? encoded(name) : name 84 | const value = storage.getItem(_name) as string 85 | const data = encrypted && value ? decoded(value) : value 86 | 87 | return deserialize(data) 88 | }, 89 | setItem: (name, value) => { 90 | const _name = encrypted ? encoded(name) : name 91 | const data = encrypted ? encoded(serialize(value)) : serialize(value) 92 | 93 | return storage.setItem(_name, data) 94 | }, 95 | removeItem: (name) => { 96 | const _name = encrypted ? encoded(name) : name 97 | storage.removeItem(_name) 98 | } 99 | } as CustomStorage 100 | } catch (_e) { 101 | return null 102 | } 103 | } 104 | 105 | const validateStorage = ( 106 | initializerStorage: CustomStorage | (() => CustomStorage) | null 107 | ) => { 108 | if (typeof initializerStorage === 'function') { 109 | try { 110 | return initializerStorage() 111 | } catch (_e) { 112 | return null 113 | } 114 | } 115 | 116 | return initializerStorage 117 | } 118 | 119 | export const initRevalidateOnFocus = (listener: () => void) => { 120 | addWindowEvent('focus', listener) 121 | addDocumentEvent('visibilitychange', listener) 122 | return () => { 123 | removeWindowEvent('focus', listener) 124 | removeDocumentEvent('visibilitychange', listener) 125 | } 126 | } 127 | 128 | // TODO: Add default config to resolve 129 | // __tests__/persist.test.ts:144 130 | export const persist = 131 | (config: PersistConfig) => 132 | (store: Store) => { 133 | const baseConfig = { 134 | name: '', 135 | storage: normalizeStorage(() => window.localStorage as CustomStorage, { 136 | encrypted: config?.encrypted || false 137 | }), 138 | merge, 139 | revalidate: true, 140 | revalidateTimeout: 200, 141 | ...config 142 | } 143 | const storageName = baseConfig.name 144 | const storage = validateStorage(baseConfig.storage) 145 | 146 | if (store?.$$store !== SYMBOL_STORE) { 147 | console.error( 148 | '[Killa Persist] Provide a valid killa store to persist your store.' 149 | ) 150 | return 151 | } 152 | 153 | if (!storageName) { 154 | console.error('[Killa Persist] Provide a name to persist your store.') 155 | return 156 | } 157 | 158 | if (!storage) { 159 | console.error('[Killa Persist] Provide a storage to persist your store.') 160 | return 161 | } 162 | 163 | const _setState = store.setState 164 | let hydrated = false 165 | 166 | store.setState = (state, force) => { 167 | _setState(state, force) 168 | storage?.setItem(storageName, store.getState()) 169 | } 170 | 171 | const hydrate = () => { 172 | const persistedState = storage?.getItem(storageName) 173 | 174 | store.setState(() => { 175 | return { 176 | ...merge(store.getState(), persistedState) 177 | } 178 | }) 179 | 180 | hydrated = true 181 | } 182 | 183 | if (baseConfig.revalidate) { 184 | const revalidateOnFocusListener = () => { 185 | if (document.visibilityState === 'visible') { 186 | setTimeout(hydrate, baseConfig.revalidateTimeout) 187 | } 188 | } 189 | 190 | initRevalidateOnFocus(revalidateOnFocusListener) 191 | } 192 | 193 | hydrate() 194 | 195 | store.persist = Object.freeze({ 196 | $$persist: SYMBOL_PERSIST, 197 | name: storageName, 198 | destroy: () => storage.removeItem(storageName), 199 | rehydrate: () => hydrate(), 200 | hydrated: () => hydrated 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # KILLA 6 | Killa is a small and lightweight state management library for vanilla and React inspired by Zustand and SWR. 7 | 8 | ```bash 9 | npm install killa 10 | ``` 11 | 12 | ### Installing the vanilla version for Browser 13 | To use directly vanilla minified version in the browser: 14 | 15 | ```html 16 | 17 | ``` 18 | 19 | 20 | ```html 21 | 22 | ``` 23 | 24 | ### How to create your first store 25 | 26 | To create your first store you need to provide an object which will manage your state. **(The internal state is inmutable)** 27 | 28 | ```js 29 | import { createStore } from 'killa' 30 | // or 31 | const { createStore } = require('killa') 32 | 33 | const store = createStore({ counter: 0 }) 34 | ``` 35 | 36 | ## Vanilla 37 | ### How to access to your store 38 | 39 | ```js 40 | store.getState() // { counter: 0 } 41 | ``` 42 | 43 | ### How to update your store 44 | ```js 45 | store.setState(() => { 46 | return { 47 | counter: 1 48 | } 49 | }) 50 | 51 | store.getState() // { counter: 1 } 52 | ``` 53 | 54 | ### How to subscribe to state events 55 | 56 | ```js 57 | // This subscriber will be called every time that our state is updated. 58 | // We could say that this would be a global subscriber. 59 | store.subscribe((state, prevState) => { 60 | console.log(state) // { counter: 1 } 61 | console.log(prevState) // { counter: 0 } 62 | }) 63 | 64 | store.setState(() => { 65 | return { 66 | counter: 1 67 | } 68 | }) 69 | 70 | store.getState() // { counter: 1 } 71 | ``` 72 | 73 | But you can also subscribe to a specific event: 74 | 75 | ```js 76 | const store = createStore({ counter: 0, type: '', filter: '' }) 77 | 78 | // This subscriber will be called only when the counter state is updated. 79 | store.subscribe((state, prevState) => { 80 | console.log(state) // { counter: 1, type: '', filter: '' } 81 | console.log(prevState) // { counter: 0, type: '', filter: '' } 82 | }, (state) => state.counter) 83 | 84 | // This subscriber will be called when the state of counter or filter is updated. 85 | store.subscribe((state) => { 86 | console.log(state) // { counter: 1, type: '', filter: '' } 87 | }, (state) => ({ counter: state.counter, filter: state.filter })) 88 | 89 | // This subscriber will not be called since the type state was not updated. 90 | store.subscribe((state, prevState) => { 91 | console.log(state, prevState) 92 | }, (state) => state.type) 93 | 94 | store.setState((state) => { 95 | return { 96 | ...state, 97 | counter: state.counter + 1 98 | } 99 | }) 100 | 101 | store.getState() // { counter: 1, type: '', filter: '' } 102 | ``` 103 | 104 | ### Resting and overwriting state 105 | To reset or overwrite your store you need to use the method `resetState` 106 | 107 | ```js 108 | store.resetState() // Reseting to initial state 109 | store.getState() // { counter: 0, type: '', filter: '' } 110 | 111 | store.resetState({ notes: [] }) // Overwriting all state to the new state 112 | store.getState() // { notes: [] } 113 | ``` 114 | 115 | ### Destroying all subscribers 116 | To destroy all events to which your store has subscribed, you need to use the method `destroy` and this way events won't longer be triggered 117 | 118 | ```js 119 | store.destroy() 120 | ``` 121 | 122 | ### Using internal Actions 123 | You can also initialize your store using `get` and `set` actions to update state using custom method within your store 124 | 125 | ```js 126 | const store = createStore((get, set) => { 127 | return { 128 | count: 1, 129 | inc: () => set(() => ({ count: get().count + 1 })), 130 | getCount: () => get().count 131 | } 132 | }) 133 | 134 | store.getState().inc() // Increments count state to 2 135 | store.getState().getCount() // 2 136 | ``` 137 | 138 | ## React 139 | 140 | ```jsx 141 | import { createStore } from 'killa' 142 | import { useStore } from 'killa/react' 143 | 144 | const store = createStore({ counter: 0, type: '', filter: '' }) 145 | 146 | const Counter = () => { 147 | // This component will only be rendered when counter or filter state changes 148 | const [state, setState] = useStore(store, (state) => { 149 | return { 150 | counter: state.counter, 151 | filter: state.filter 152 | } 153 | }) 154 | 155 | const handleCounter = (e) => { 156 | setState((state) => { 157 | return { 158 | ...state, 159 | counter: state.counter + 1 160 | } 161 | }) 162 | } 163 | 164 | return ( 165 |
166 |

Counter: {state.counter}

167 | 170 |
171 | ) 172 | } 173 | ``` 174 | 175 | ### Silect states 176 | The silent states allow to memorize the selected state, this means that if any key of our store is updated it will not have any effect inside our component and will not generate a re-render. However you will be able to update the state using `setState` but this will have no any effect within the component. 177 | 178 | ```jsx 179 | // In this way, you get the whole store 180 | const [state, setState] = useStore(store, null) 181 | ``` 182 | 183 | 184 | ```jsx 185 | // In this way, you can get a specifict state from store 186 | const [state, setState] = useStore(store, (state) => state.counter, true) 187 | ``` 188 | 189 | ## Middlewares 190 | To use directly vanilla minified version in the browser: 191 | 192 | ```html 193 | 194 | ``` 195 | 196 | Or from jsdelivr: 197 | 198 | ```html 199 | 200 | ``` 201 | 202 | For vanilla, you can access to the middlewares using: `window.killaMiddlewares` 203 | ### Persist 204 | 205 | Killa Persist uses `localStorage` by default. 206 | 207 | ```js 208 | import { persist } from 'killa/persist' 209 | 210 | const store = createStore( 211 | { counter: 0, filter: '' }, 212 | { 213 | use: [ 214 | persist({ 215 | name: 'killa-persist', 216 | revalidate: false // true by default 217 | revalidateTimeout: 300 // 200 by default 218 | encrypted: true // false by default 219 | }) 220 | ] 221 | } 222 | ) 223 | ``` 224 | 225 | If you wish to use other storage you can do so by using the `normalizeStorage` method to normalize the storage supported by Killa Persist. 226 | 227 | ```js 228 | import { persist, normalizeStorage } from 'killa/persist' 229 | 230 | const store = createStore( 231 | { counter: 0, filter: '' }, 232 | { 233 | use: [ 234 | persist({ 235 | name: 'killa-persist', 236 | storage: normalizeStorage(() => sessionStorage) 237 | }) 238 | ] 239 | } 240 | ) 241 | ``` 242 | 243 | #### Auto Revalidate 244 | 245 | 246 | 247 | ## Support 248 | React >= 18.0.0, Chrome 58, Firefox 57, IE 11, Edge 18, Safari 11, & Node.js 16. 249 | -------------------------------------------------------------------------------- /__tests__/persist.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, beforeEach, vi } from 'vitest' 2 | 3 | import { createStore, Store } from '../src' 4 | import { SYMBOL_PERSIST } from '../src/utils/constants' 5 | import { encoded, decoded, deserialize } from '../src/utils/helpers' 6 | import { persist } from '../src/middleware/persist' 7 | 8 | const createMockedStorage = (persistedState: { counter: number }) => { 9 | type State = typeof persistedState 10 | let state: State | null = persistedState 11 | 12 | return { 13 | getItem: vi.fn(() => { 14 | if (!state) return null 15 | return state 16 | }), 17 | setItem: vi.fn(), 18 | removeItem: vi.fn(() => { 19 | state = null 20 | }) 21 | } 22 | } 23 | 24 | describe('Persist', () => { 25 | let store: Store<{ counter: number }> 26 | const storeName = 'persist-store' 27 | const persistedState = { counter: 1 } 28 | const mockedStorage = createMockedStorage(persistedState) 29 | 30 | beforeEach(() => { 31 | store = createStore({ counter: 0 }) 32 | mockedStorage.getItem.mockClear() 33 | mockedStorage.setItem.mockClear() 34 | mockedStorage.removeItem.mockClear() 35 | }) 36 | 37 | it('Should export persist as named export', () => { 38 | expect(persist).toBeInstanceOf(Function) 39 | }) 40 | 41 | it('Should init the middleware with the storage by default', () => { 42 | persist({ name: storeName })(store) 43 | 44 | expect(store?.persist?.name).toBe(storeName) 45 | expect(store?.persist?.hydrated()).toEqual(true) 46 | expect(store?.persist?.$$persist).toBe(SYMBOL_PERSIST) 47 | expect(store?.persist?.destroy).toBeInstanceOf(Function) 48 | expect(store?.persist?.rehydrate).toBeInstanceOf(Function) 49 | }) 50 | 51 | it('Should decode and encode the store every time the store is updated', () => { 52 | persist({ name: storeName, encrypted: true })(store) 53 | 54 | const getStore = () => 55 | deserialize( 56 | decoded(window.localStorage.getItem(encoded(storeName)) as string) 57 | ) 58 | 59 | expect(store.getState()).toEqual(getStore()) 60 | 61 | store.setState(() => ({ counter: 2 })) 62 | 63 | expect(store.getState()).toEqual(getStore()) 64 | }) 65 | 66 | it('Should init the middleware with a custom storage', () => { 67 | persist({ name: storeName, storage: mockedStorage })(store) 68 | 69 | store.setState(() => ({ counter: 2 })) 70 | 71 | expect(mockedStorage.getItem).toHaveBeenCalledTimes(1) 72 | expect(mockedStorage.setItem).toHaveBeenNthCalledWith(1, storeName, { 73 | counter: 1 74 | }) 75 | expect(mockedStorage.setItem).toHaveBeenNthCalledWith(2, storeName, { 76 | counter: 2 77 | }) 78 | expect(store.getState()).toEqual({ counter: 2 }) 79 | }) 80 | 81 | it('Should hydrate the store with the persisted store', () => { 82 | persist({ name: storeName, storage: mockedStorage })(store) 83 | 84 | expect(mockedStorage.getItem).toHaveBeenCalledTimes(1) 85 | expect(mockedStorage.getItem).toHaveBeenCalledExactlyOnceWith(storeName) 86 | expect(mockedStorage.getItem).toHaveReturnedWith(persistedState) 87 | expect(store.getState()).toEqual(persistedState) 88 | }) 89 | 90 | it('Should rehydrate the store with the persisted store', () => { 91 | persist({ name: storeName, storage: mockedStorage })(store) 92 | 93 | store?.persist?.rehydrate() 94 | 95 | expect(mockedStorage.getItem).toHaveBeenCalledTimes(2) 96 | expect(store?.persist?.hydrated()).toEqual(true) 97 | }) 98 | 99 | it('Should persist the store after updating the store', () => { 100 | persist({ name: storeName, storage: mockedStorage })(store) 101 | 102 | store.setState(() => ({ counter: 2 })) 103 | 104 | expect(mockedStorage.getItem).toHaveBeenCalledTimes(1) 105 | expect(mockedStorage.setItem).toHaveBeenNthCalledWith(1, storeName, { 106 | counter: 1 107 | }) 108 | expect(mockedStorage.setItem).toHaveBeenNthCalledWith(2, storeName, { 109 | counter: 2 110 | }) 111 | expect(store.getState()).toEqual({ counter: 2 }) 112 | }) 113 | 114 | it('Should persist state after updating the store using set method from initializer function', () => { 115 | type StoreState = { counter: number; increment: () => void } 116 | const store = createStore((_, set) => { 117 | return { 118 | counter: 0, 119 | increment: () => { 120 | set((state) => ({ counter: state.counter + 1 })) 121 | } 122 | } 123 | }) 124 | 125 | persist({ name: storeName, storage: mockedStorage })(store) 126 | 127 | store.getState().increment() 128 | 129 | expect(mockedStorage.setItem).toHaveBeenNthCalledWith(1, storeName, { 130 | counter: 1, 131 | increment: expect.any(Function) 132 | }) 133 | }) 134 | 135 | it('Should get persisted state after updating the store using set method from initializer function', () => { 136 | const store = createStore<{ 137 | counter: number 138 | increment: () => void 139 | getCounter: () => number 140 | }>((get, set) => { 141 | return { 142 | counter: 0, 143 | increment: () => { 144 | set((state) => ({ counter: state.counter + 1 })) 145 | }, 146 | getCounter: () => { 147 | return get().counter 148 | } 149 | } 150 | }) 151 | 152 | persist({ name: storeName, storage: mockedStorage })(store) 153 | 154 | store.getState().increment() 155 | 156 | expect(store.getState().getCounter()).toBe(2) 157 | 158 | expect(mockedStorage.getItem).toHaveBeenNthCalledWith(1, storeName) 159 | }) 160 | 161 | it('Should remove the persisted store', () => { 162 | persist({ name: storeName, storage: mockedStorage })(store) 163 | 164 | store.setState(() => ({ counter: 1 })) 165 | 166 | store?.persist?.destroy() 167 | 168 | expect(mockedStorage.removeItem).toHaveBeenCalledTimes(1) 169 | expect(mockedStorage.removeItem).toHaveBeenCalledExactlyOnceWith(storeName) 170 | expect(mockedStorage.getItem()).toBe(null) 171 | }) 172 | 173 | it('Should print an error when name store is empty', () => { 174 | const logSpy = vi.spyOn(console, 'error') 175 | 176 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 177 | // @ts-ignore 178 | persist()(store) 179 | 180 | expect(logSpy).toHaveBeenCalledTimes(1) 181 | expect(logSpy).toHaveBeenCalledExactlyOnceWith( 182 | '[Killa Persist] Provide a name to persist your store.' 183 | ) 184 | logSpy.mockRestore() 185 | }) 186 | 187 | it('Should print an error when name storage is empty', () => { 188 | const logSpy = vi.spyOn(console, 'error') 189 | 190 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 191 | // @ts-ignore 192 | persist({ name: storeName, storage: null })(store) 193 | 194 | expect(logSpy).toHaveBeenCalledTimes(1) 195 | expect(logSpy).toHaveBeenCalledExactlyOnceWith( 196 | '[Killa Persist] Provide a storage to persist your store.' 197 | ) 198 | 199 | logSpy.mockRestore() 200 | }) 201 | 202 | it('Should print an error when killa store is invalid', () => { 203 | const logSpy = vi.spyOn(console, 'error') 204 | 205 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 206 | // @ts-ignore 207 | persist({ name: storeName, storage: mockedStorage })({}) 208 | 209 | expect(logSpy).toHaveBeenCalledTimes(1) 210 | expect(logSpy).toHaveBeenCalledExactlyOnceWith( 211 | '[Killa Persist] Provide a valid killa store to persist your store.' 212 | ) 213 | 214 | logSpy.mockRestore() 215 | }) 216 | 217 | it('Should print an error when the custom storage is invalid', () => { 218 | const logSpy = vi.spyOn(console, 'error') 219 | 220 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 221 | // @ts-ignore 222 | persist({ name: storeName, storage: () => AsyncStore })(store) 223 | 224 | expect(logSpy).toHaveBeenCalledTimes(1) 225 | expect(logSpy).toHaveBeenCalledExactlyOnceWith( 226 | '[Killa Persist] Provide a storage to persist your store.' 227 | ) 228 | 229 | logSpy.mockRestore() 230 | }) 231 | }) 232 | -------------------------------------------------------------------------------- /__tests__/vanilla.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | 3 | import { createStore } from '../src' 4 | 5 | describe('Vanilla', () => { 6 | it('Should export createStore as default export and named export', () => { 7 | expect(createStore).toBe(createStore) 8 | }) 9 | 10 | it('Should fail when the store is not object', () => { 11 | try { 12 | createStore(1) 13 | throw new Error('Should fail because the store is not object') 14 | } catch (e: any) { 15 | expect(e.message).toBe('Store must be an object.') 16 | } 17 | }) 18 | 19 | it('Should create the store and provide the setState, getState and subscribe methods', () => { 20 | const store = createStore({ count: 0 }) 21 | expect(store.setState).toBeInstanceOf(Function) 22 | expect(store.getState).toBeInstanceOf(Function) 23 | expect(store.subscribe).toBeInstanceOf(Function) 24 | }) 25 | 26 | it('Should set the inital state and state must be a new Object', () => { 27 | const initalState = { count: 0 } 28 | const store = createStore<{ count: number }>({ count: 0 }) 29 | const state = store.getState() 30 | 31 | expect(state).toEqual(initalState) 32 | expect(state).not.toBe(initalState) 33 | }) 34 | 35 | it('Should set the inital state as empty object when inital state is not provided', () => { 36 | const store = createStore<{ count: number }>() 37 | expect(store.getState()).toEqual({}) 38 | }) 39 | 40 | it('Should update the state', () => { 41 | const initalState = { count: 0 } 42 | const store = createStore<{ count: number }>(initalState) 43 | const cb = vi.fn(() => ({ count: 1 })) 44 | 45 | store.setState(cb) 46 | 47 | expect(store.getState().count).toBe(1) 48 | expect(store.getState()).not.toBe(initalState) 49 | expect(cb).toHaveBeenCalledTimes(1) 50 | }) 51 | 52 | it('Should pass the current state as param in the setState method', () => { 53 | const initalState = { count: 0 } 54 | const store = createStore<{ count: number }>(initalState) 55 | 56 | store.setState((state) => { 57 | expect(state).toEqual(initalState) 58 | expect(state).not.toBe(initalState) 59 | expect(state).not.toBe(store.getState()) 60 | 61 | return { 62 | count: 1 63 | } 64 | }) 65 | }) 66 | 67 | it('Should be able to use the get and set method to update the state from initializer function', () => { 68 | const store = createStore<{ 69 | count: number 70 | inc: () => void 71 | getCount: () => number 72 | }>((get, set) => { 73 | return { 74 | count: 1, 75 | inc: () => set(() => ({ count: get().count + 1 })), 76 | getCount: () => get().count 77 | } 78 | }) 79 | 80 | store.getState().inc() 81 | 82 | expect(store.getState().count).toEqual(2) 83 | expect(store.getState().getCount()).toEqual(2) 84 | }) 85 | 86 | it('Should just mutate the internal state using the setState method', () => { 87 | const initalState = { count: 0 } 88 | const store = createStore<{ count: number }>(initalState) 89 | const state = store.getState() 90 | const expectedState = { count: 1 } 91 | 92 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 93 | // @ts-ignore 94 | state.foo = 'foo' 95 | 96 | expect(state).not.toBe(initalState) 97 | expect(state).not.toEqual(expectedState) 98 | 99 | store.setState((state) => { 100 | expect(state).toEqual(initalState) 101 | expect(state).not.toBe(initalState) 102 | expect(state).not.toBe(store.getState()) 103 | 104 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 105 | // @ts-ignore 106 | state.bar = 'bar' 107 | 108 | return { 109 | count: 1 110 | } 111 | }) 112 | 113 | expect(store.getState()).toEqual(expectedState) 114 | }) 115 | 116 | it('Should call the global subscriber when updating the state', () => { 117 | const initalState = { count: 0 } 118 | const store = createStore<{ count: number }>(initalState) 119 | const cb = vi.fn() 120 | 121 | store.subscribe(cb) 122 | 123 | store.setState(() => { 124 | return { 125 | count: 1 126 | } 127 | }) 128 | 129 | expect(cb).toHaveBeenCalledTimes(1) 130 | }) 131 | 132 | it('Should pass the current and previous state to subscribers when state is updated', () => { 133 | const initalState = { count: 0 } 134 | const store = createStore(initalState) 135 | 136 | const firstSubscribe = vi.fn((state, prevState) => { 137 | expect(state).toEqual(store.getState()) 138 | expect(state).not.toBe(store.getState()) 139 | 140 | expect(prevState).toEqual(initalState) 141 | expect(prevState).not.toBe(initalState) 142 | 143 | state.count = 2 144 | state.prevState = 2 145 | }) 146 | 147 | const secondSubscribe = vi.fn((state, prevState) => { 148 | expect(state).toEqual(store.getState()) 149 | expect(state).not.toBe(store.getState()) 150 | expect(state.count).toBe(1) 151 | 152 | expect(prevState).toEqual(initalState) 153 | expect(prevState).not.toBe(initalState) 154 | expect(prevState.count).toBe(0) 155 | }) 156 | 157 | store.subscribe(firstSubscribe) 158 | store.subscribe(secondSubscribe) 159 | 160 | store.setState(() => { 161 | return { 162 | count: 1 163 | } 164 | }) 165 | 166 | expect(firstSubscribe).toHaveBeenCalledTimes(1) 167 | expect(secondSubscribe).toHaveBeenCalledTimes(1) 168 | }) 169 | 170 | it('Should just call the subscribers when the state of the selector is updated', () => { 171 | const initalState = { count: 0, text: '' } 172 | const store = createStore(initalState) 173 | const firstSubscribe = vi.fn() 174 | const secondSubscribe = vi.fn() 175 | 176 | store.subscribe(firstSubscribe, (state) => state.count) 177 | store.subscribe(secondSubscribe, (state) => state.text) 178 | 179 | store.setState(() => { 180 | return { 181 | count: 1 182 | } 183 | }) 184 | 185 | expect(firstSubscribe).toHaveBeenCalledTimes(1) 186 | expect(secondSubscribe).toHaveBeenCalledTimes(0) 187 | }) 188 | 189 | it('Should just call a subscribers once when the subscriber function is the same', () => { 190 | const initalState = { count: 0, text: '' } 191 | const store = createStore(initalState) 192 | const firstSubscribe = vi.fn() 193 | 194 | store.subscribe(firstSubscribe) 195 | store.subscribe(firstSubscribe) 196 | 197 | store.setState(() => { 198 | return { 199 | count: 1 200 | } 201 | }) 202 | 203 | expect(firstSubscribe).toHaveBeenCalledTimes(1) 204 | }) 205 | 206 | it('Should unsubscribe to a subscriber', () => { 207 | const initalState = { count: 0, text: '' } 208 | const store = createStore(initalState) 209 | 210 | const firstSubscribe = vi.fn() 211 | const unsubscribe = store.subscribe(firstSubscribe, (state) => state.count) 212 | 213 | store.setState(() => { 214 | return { 215 | count: 1 216 | } 217 | }) 218 | 219 | unsubscribe() 220 | 221 | store.setState(() => { 222 | return { 223 | count: 2 224 | } 225 | }) 226 | 227 | expect(firstSubscribe).toHaveBeenCalledTimes(1) 228 | }) 229 | 230 | it('Should reset the store to the inital state', () => { 231 | const initalState = { count: 0 } 232 | const store = createStore<{ count: number }>({ count: 0 }) 233 | 234 | expect(store.getState()).toEqual(initalState) 235 | store.setState(() => ({ count: 1 })) 236 | expect(store.getState()).toEqual({ count: 1 }) 237 | store.resetState() 238 | expect(store.getState()).toEqual(initalState) 239 | }) 240 | 241 | it('Should force to update all state', () => { 242 | const initalState = { count: 0 } 243 | const forceState = { force: true } 244 | const store = createStore({ count: 0 }) 245 | 246 | expect(store.getState()).toEqual(initalState) 247 | store.setState(() => ({ count: 1 })) 248 | expect(store.getState()).toEqual({ count: 1 }) 249 | store.resetState(forceState) 250 | expect(store.getState()).toEqual(forceState) 251 | }) 252 | 253 | it('Should force to reset all state', () => { 254 | const initalState = { count: 0, text: '' } 255 | const store = createStore(initalState) 256 | 257 | store.setState(() => { 258 | return { force: true } 259 | }, true) 260 | 261 | expect(store.getState()).toEqual({ force: true }) 262 | }) 263 | 264 | it('Should not update the state if the compare function return true', () => { 265 | const compare = vi.fn(() => true) 266 | const firstSubscribe = vi.fn() 267 | 268 | const store = createStore({ count: 0, text: '' }, { compare }) 269 | 270 | store.subscribe(firstSubscribe) 271 | store.setState((state) => state) 272 | 273 | expect(compare).toHaveBeenCalledTimes(1) 274 | expect(firstSubscribe).toHaveBeenCalledTimes(0) 275 | }) 276 | 277 | it('Should update the state if the compare function return false', () => { 278 | const compare = vi.fn(() => false) 279 | const store = createStore({ count: 0, text: '' }, { compare }) 280 | 281 | const firstSubscribe = vi.fn() 282 | 283 | store.subscribe(firstSubscribe) 284 | store.setState((state) => state) 285 | 286 | expect(compare).toHaveBeenCalledTimes(1) 287 | expect(firstSubscribe).toHaveBeenCalledTimes(1) 288 | }) 289 | 290 | it('Should apply middlewares', () => { 291 | const middleware = vi.fn() 292 | const store = createStore({ count: 0, text: '' }, { use: [middleware] }) 293 | 294 | expect(middleware).toHaveBeenCalledTimes(1) 295 | expect(middleware).toHaveBeenCalledExactlyOnceWith(store) 296 | }) 297 | 298 | it('Should fail if use option is not an array', () => { 299 | const middleware = vi.fn() 300 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 301 | // @ts-ignore 302 | createStore({ count: 0, text: '' }, { use: '' }) 303 | 304 | expect(middleware).not.toHaveBeenCalled() 305 | }) 306 | }) 307 | -------------------------------------------------------------------------------- /examples/react/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "react", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "killa": "^1.7.2", 12 | "react": "18.2.0", 13 | "react-dom": "18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "18.0.27", 17 | "@types/react-dom": "18.0.10", 18 | "@vitejs/plugin-react": "3.1.0", 19 | "vite": "4.1.0" 20 | } 21 | }, 22 | "node_modules/@ampproject/remapping": { 23 | "version": "2.2.1", 24 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", 25 | "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", 26 | "dev": true, 27 | "dependencies": { 28 | "@jridgewell/gen-mapping": "^0.3.0", 29 | "@jridgewell/trace-mapping": "^0.3.9" 30 | }, 31 | "engines": { 32 | "node": ">=6.0.0" 33 | } 34 | }, 35 | "node_modules/@babel/code-frame": { 36 | "version": "7.22.13", 37 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", 38 | "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", 39 | "dev": true, 40 | "dependencies": { 41 | "@babel/highlight": "^7.22.13", 42 | "chalk": "^2.4.2" 43 | }, 44 | "engines": { 45 | "node": ">=6.9.0" 46 | } 47 | }, 48 | "node_modules/@babel/compat-data": { 49 | "version": "7.22.9", 50 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", 51 | "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", 52 | "dev": true, 53 | "engines": { 54 | "node": ">=6.9.0" 55 | } 56 | }, 57 | "node_modules/@babel/core": { 58 | "version": "7.22.11", 59 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz", 60 | "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==", 61 | "dev": true, 62 | "dependencies": { 63 | "@ampproject/remapping": "^2.2.0", 64 | "@babel/code-frame": "^7.22.10", 65 | "@babel/generator": "^7.22.10", 66 | "@babel/helper-compilation-targets": "^7.22.10", 67 | "@babel/helper-module-transforms": "^7.22.9", 68 | "@babel/helpers": "^7.22.11", 69 | "@babel/parser": "^7.22.11", 70 | "@babel/template": "^7.22.5", 71 | "@babel/traverse": "^7.22.11", 72 | "@babel/types": "^7.22.11", 73 | "convert-source-map": "^1.7.0", 74 | "debug": "^4.1.0", 75 | "gensync": "^1.0.0-beta.2", 76 | "json5": "^2.2.3", 77 | "semver": "^6.3.1" 78 | }, 79 | "engines": { 80 | "node": ">=6.9.0" 81 | }, 82 | "funding": { 83 | "type": "opencollective", 84 | "url": "https://opencollective.com/babel" 85 | } 86 | }, 87 | "node_modules/@babel/generator": { 88 | "version": "7.22.10", 89 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", 90 | "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", 91 | "dev": true, 92 | "dependencies": { 93 | "@babel/types": "^7.22.10", 94 | "@jridgewell/gen-mapping": "^0.3.2", 95 | "@jridgewell/trace-mapping": "^0.3.17", 96 | "jsesc": "^2.5.1" 97 | }, 98 | "engines": { 99 | "node": ">=6.9.0" 100 | } 101 | }, 102 | "node_modules/@babel/helper-compilation-targets": { 103 | "version": "7.22.10", 104 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", 105 | "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", 106 | "dev": true, 107 | "dependencies": { 108 | "@babel/compat-data": "^7.22.9", 109 | "@babel/helper-validator-option": "^7.22.5", 110 | "browserslist": "^4.21.9", 111 | "lru-cache": "^5.1.1", 112 | "semver": "^6.3.1" 113 | }, 114 | "engines": { 115 | "node": ">=6.9.0" 116 | } 117 | }, 118 | "node_modules/@babel/helper-environment-visitor": { 119 | "version": "7.22.5", 120 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", 121 | "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", 122 | "dev": true, 123 | "engines": { 124 | "node": ">=6.9.0" 125 | } 126 | }, 127 | "node_modules/@babel/helper-function-name": { 128 | "version": "7.22.5", 129 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", 130 | "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", 131 | "dev": true, 132 | "dependencies": { 133 | "@babel/template": "^7.22.5", 134 | "@babel/types": "^7.22.5" 135 | }, 136 | "engines": { 137 | "node": ">=6.9.0" 138 | } 139 | }, 140 | "node_modules/@babel/helper-hoist-variables": { 141 | "version": "7.22.5", 142 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", 143 | "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", 144 | "dev": true, 145 | "dependencies": { 146 | "@babel/types": "^7.22.5" 147 | }, 148 | "engines": { 149 | "node": ">=6.9.0" 150 | } 151 | }, 152 | "node_modules/@babel/helper-module-imports": { 153 | "version": "7.22.5", 154 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", 155 | "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", 156 | "dev": true, 157 | "dependencies": { 158 | "@babel/types": "^7.22.5" 159 | }, 160 | "engines": { 161 | "node": ">=6.9.0" 162 | } 163 | }, 164 | "node_modules/@babel/helper-module-transforms": { 165 | "version": "7.22.9", 166 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", 167 | "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", 168 | "dev": true, 169 | "dependencies": { 170 | "@babel/helper-environment-visitor": "^7.22.5", 171 | "@babel/helper-module-imports": "^7.22.5", 172 | "@babel/helper-simple-access": "^7.22.5", 173 | "@babel/helper-split-export-declaration": "^7.22.6", 174 | "@babel/helper-validator-identifier": "^7.22.5" 175 | }, 176 | "engines": { 177 | "node": ">=6.9.0" 178 | }, 179 | "peerDependencies": { 180 | "@babel/core": "^7.0.0" 181 | } 182 | }, 183 | "node_modules/@babel/helper-plugin-utils": { 184 | "version": "7.22.5", 185 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", 186 | "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", 187 | "dev": true, 188 | "engines": { 189 | "node": ">=6.9.0" 190 | } 191 | }, 192 | "node_modules/@babel/helper-simple-access": { 193 | "version": "7.22.5", 194 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", 195 | "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", 196 | "dev": true, 197 | "dependencies": { 198 | "@babel/types": "^7.22.5" 199 | }, 200 | "engines": { 201 | "node": ">=6.9.0" 202 | } 203 | }, 204 | "node_modules/@babel/helper-split-export-declaration": { 205 | "version": "7.22.6", 206 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", 207 | "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", 208 | "dev": true, 209 | "dependencies": { 210 | "@babel/types": "^7.22.5" 211 | }, 212 | "engines": { 213 | "node": ">=6.9.0" 214 | } 215 | }, 216 | "node_modules/@babel/helper-string-parser": { 217 | "version": "7.22.5", 218 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", 219 | "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", 220 | "dev": true, 221 | "engines": { 222 | "node": ">=6.9.0" 223 | } 224 | }, 225 | "node_modules/@babel/helper-validator-identifier": { 226 | "version": "7.22.5", 227 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", 228 | "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", 229 | "dev": true, 230 | "engines": { 231 | "node": ">=6.9.0" 232 | } 233 | }, 234 | "node_modules/@babel/helper-validator-option": { 235 | "version": "7.22.5", 236 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", 237 | "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", 238 | "dev": true, 239 | "engines": { 240 | "node": ">=6.9.0" 241 | } 242 | }, 243 | "node_modules/@babel/helpers": { 244 | "version": "7.22.11", 245 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", 246 | "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", 247 | "dev": true, 248 | "dependencies": { 249 | "@babel/template": "^7.22.5", 250 | "@babel/traverse": "^7.22.11", 251 | "@babel/types": "^7.22.11" 252 | }, 253 | "engines": { 254 | "node": ">=6.9.0" 255 | } 256 | }, 257 | "node_modules/@babel/highlight": { 258 | "version": "7.22.13", 259 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", 260 | "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", 261 | "dev": true, 262 | "dependencies": { 263 | "@babel/helper-validator-identifier": "^7.22.5", 264 | "chalk": "^2.4.2", 265 | "js-tokens": "^4.0.0" 266 | }, 267 | "engines": { 268 | "node": ">=6.9.0" 269 | } 270 | }, 271 | "node_modules/@babel/parser": { 272 | "version": "7.22.13", 273 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.13.tgz", 274 | "integrity": "sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==", 275 | "dev": true, 276 | "bin": { 277 | "parser": "bin/babel-parser.js" 278 | }, 279 | "engines": { 280 | "node": ">=6.0.0" 281 | } 282 | }, 283 | "node_modules/@babel/plugin-transform-react-jsx-self": { 284 | "version": "7.22.5", 285 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", 286 | "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", 287 | "dev": true, 288 | "dependencies": { 289 | "@babel/helper-plugin-utils": "^7.22.5" 290 | }, 291 | "engines": { 292 | "node": ">=6.9.0" 293 | }, 294 | "peerDependencies": { 295 | "@babel/core": "^7.0.0-0" 296 | } 297 | }, 298 | "node_modules/@babel/plugin-transform-react-jsx-source": { 299 | "version": "7.22.5", 300 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", 301 | "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", 302 | "dev": true, 303 | "dependencies": { 304 | "@babel/helper-plugin-utils": "^7.22.5" 305 | }, 306 | "engines": { 307 | "node": ">=6.9.0" 308 | }, 309 | "peerDependencies": { 310 | "@babel/core": "^7.0.0-0" 311 | } 312 | }, 313 | "node_modules/@babel/template": { 314 | "version": "7.22.5", 315 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", 316 | "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", 317 | "dev": true, 318 | "dependencies": { 319 | "@babel/code-frame": "^7.22.5", 320 | "@babel/parser": "^7.22.5", 321 | "@babel/types": "^7.22.5" 322 | }, 323 | "engines": { 324 | "node": ">=6.9.0" 325 | } 326 | }, 327 | "node_modules/@babel/traverse": { 328 | "version": "7.22.11", 329 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", 330 | "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", 331 | "dev": true, 332 | "dependencies": { 333 | "@babel/code-frame": "^7.22.10", 334 | "@babel/generator": "^7.22.10", 335 | "@babel/helper-environment-visitor": "^7.22.5", 336 | "@babel/helper-function-name": "^7.22.5", 337 | "@babel/helper-hoist-variables": "^7.22.5", 338 | "@babel/helper-split-export-declaration": "^7.22.6", 339 | "@babel/parser": "^7.22.11", 340 | "@babel/types": "^7.22.11", 341 | "debug": "^4.1.0", 342 | "globals": "^11.1.0" 343 | }, 344 | "engines": { 345 | "node": ">=6.9.0" 346 | } 347 | }, 348 | "node_modules/@babel/types": { 349 | "version": "7.22.11", 350 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", 351 | "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", 352 | "dev": true, 353 | "dependencies": { 354 | "@babel/helper-string-parser": "^7.22.5", 355 | "@babel/helper-validator-identifier": "^7.22.5", 356 | "to-fast-properties": "^2.0.0" 357 | }, 358 | "engines": { 359 | "node": ">=6.9.0" 360 | } 361 | }, 362 | "node_modules/@esbuild/android-arm": { 363 | "version": "0.16.17", 364 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", 365 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", 366 | "cpu": [ 367 | "arm" 368 | ], 369 | "dev": true, 370 | "optional": true, 371 | "os": [ 372 | "android" 373 | ], 374 | "engines": { 375 | "node": ">=12" 376 | } 377 | }, 378 | "node_modules/@esbuild/android-arm64": { 379 | "version": "0.16.17", 380 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", 381 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", 382 | "cpu": [ 383 | "arm64" 384 | ], 385 | "dev": true, 386 | "optional": true, 387 | "os": [ 388 | "android" 389 | ], 390 | "engines": { 391 | "node": ">=12" 392 | } 393 | }, 394 | "node_modules/@esbuild/android-x64": { 395 | "version": "0.16.17", 396 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", 397 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", 398 | "cpu": [ 399 | "x64" 400 | ], 401 | "dev": true, 402 | "optional": true, 403 | "os": [ 404 | "android" 405 | ], 406 | "engines": { 407 | "node": ">=12" 408 | } 409 | }, 410 | "node_modules/@esbuild/darwin-arm64": { 411 | "version": "0.16.17", 412 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", 413 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", 414 | "cpu": [ 415 | "arm64" 416 | ], 417 | "dev": true, 418 | "optional": true, 419 | "os": [ 420 | "darwin" 421 | ], 422 | "engines": { 423 | "node": ">=12" 424 | } 425 | }, 426 | "node_modules/@esbuild/darwin-x64": { 427 | "version": "0.16.17", 428 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", 429 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", 430 | "cpu": [ 431 | "x64" 432 | ], 433 | "dev": true, 434 | "optional": true, 435 | "os": [ 436 | "darwin" 437 | ], 438 | "engines": { 439 | "node": ">=12" 440 | } 441 | }, 442 | "node_modules/@esbuild/freebsd-arm64": { 443 | "version": "0.16.17", 444 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", 445 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", 446 | "cpu": [ 447 | "arm64" 448 | ], 449 | "dev": true, 450 | "optional": true, 451 | "os": [ 452 | "freebsd" 453 | ], 454 | "engines": { 455 | "node": ">=12" 456 | } 457 | }, 458 | "node_modules/@esbuild/freebsd-x64": { 459 | "version": "0.16.17", 460 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", 461 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", 462 | "cpu": [ 463 | "x64" 464 | ], 465 | "dev": true, 466 | "optional": true, 467 | "os": [ 468 | "freebsd" 469 | ], 470 | "engines": { 471 | "node": ">=12" 472 | } 473 | }, 474 | "node_modules/@esbuild/linux-arm": { 475 | "version": "0.16.17", 476 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", 477 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", 478 | "cpu": [ 479 | "arm" 480 | ], 481 | "dev": true, 482 | "optional": true, 483 | "os": [ 484 | "linux" 485 | ], 486 | "engines": { 487 | "node": ">=12" 488 | } 489 | }, 490 | "node_modules/@esbuild/linux-arm64": { 491 | "version": "0.16.17", 492 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", 493 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", 494 | "cpu": [ 495 | "arm64" 496 | ], 497 | "dev": true, 498 | "optional": true, 499 | "os": [ 500 | "linux" 501 | ], 502 | "engines": { 503 | "node": ">=12" 504 | } 505 | }, 506 | "node_modules/@esbuild/linux-ia32": { 507 | "version": "0.16.17", 508 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", 509 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", 510 | "cpu": [ 511 | "ia32" 512 | ], 513 | "dev": true, 514 | "optional": true, 515 | "os": [ 516 | "linux" 517 | ], 518 | "engines": { 519 | "node": ">=12" 520 | } 521 | }, 522 | "node_modules/@esbuild/linux-loong64": { 523 | "version": "0.16.17", 524 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", 525 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", 526 | "cpu": [ 527 | "loong64" 528 | ], 529 | "dev": true, 530 | "optional": true, 531 | "os": [ 532 | "linux" 533 | ], 534 | "engines": { 535 | "node": ">=12" 536 | } 537 | }, 538 | "node_modules/@esbuild/linux-mips64el": { 539 | "version": "0.16.17", 540 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", 541 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", 542 | "cpu": [ 543 | "mips64el" 544 | ], 545 | "dev": true, 546 | "optional": true, 547 | "os": [ 548 | "linux" 549 | ], 550 | "engines": { 551 | "node": ">=12" 552 | } 553 | }, 554 | "node_modules/@esbuild/linux-ppc64": { 555 | "version": "0.16.17", 556 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", 557 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", 558 | "cpu": [ 559 | "ppc64" 560 | ], 561 | "dev": true, 562 | "optional": true, 563 | "os": [ 564 | "linux" 565 | ], 566 | "engines": { 567 | "node": ">=12" 568 | } 569 | }, 570 | "node_modules/@esbuild/linux-riscv64": { 571 | "version": "0.16.17", 572 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", 573 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", 574 | "cpu": [ 575 | "riscv64" 576 | ], 577 | "dev": true, 578 | "optional": true, 579 | "os": [ 580 | "linux" 581 | ], 582 | "engines": { 583 | "node": ">=12" 584 | } 585 | }, 586 | "node_modules/@esbuild/linux-s390x": { 587 | "version": "0.16.17", 588 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", 589 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", 590 | "cpu": [ 591 | "s390x" 592 | ], 593 | "dev": true, 594 | "optional": true, 595 | "os": [ 596 | "linux" 597 | ], 598 | "engines": { 599 | "node": ">=12" 600 | } 601 | }, 602 | "node_modules/@esbuild/linux-x64": { 603 | "version": "0.16.17", 604 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", 605 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", 606 | "cpu": [ 607 | "x64" 608 | ], 609 | "dev": true, 610 | "optional": true, 611 | "os": [ 612 | "linux" 613 | ], 614 | "engines": { 615 | "node": ">=12" 616 | } 617 | }, 618 | "node_modules/@esbuild/netbsd-x64": { 619 | "version": "0.16.17", 620 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", 621 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", 622 | "cpu": [ 623 | "x64" 624 | ], 625 | "dev": true, 626 | "optional": true, 627 | "os": [ 628 | "netbsd" 629 | ], 630 | "engines": { 631 | "node": ">=12" 632 | } 633 | }, 634 | "node_modules/@esbuild/openbsd-x64": { 635 | "version": "0.16.17", 636 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", 637 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", 638 | "cpu": [ 639 | "x64" 640 | ], 641 | "dev": true, 642 | "optional": true, 643 | "os": [ 644 | "openbsd" 645 | ], 646 | "engines": { 647 | "node": ">=12" 648 | } 649 | }, 650 | "node_modules/@esbuild/sunos-x64": { 651 | "version": "0.16.17", 652 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", 653 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", 654 | "cpu": [ 655 | "x64" 656 | ], 657 | "dev": true, 658 | "optional": true, 659 | "os": [ 660 | "sunos" 661 | ], 662 | "engines": { 663 | "node": ">=12" 664 | } 665 | }, 666 | "node_modules/@esbuild/win32-arm64": { 667 | "version": "0.16.17", 668 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", 669 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", 670 | "cpu": [ 671 | "arm64" 672 | ], 673 | "dev": true, 674 | "optional": true, 675 | "os": [ 676 | "win32" 677 | ], 678 | "engines": { 679 | "node": ">=12" 680 | } 681 | }, 682 | "node_modules/@esbuild/win32-ia32": { 683 | "version": "0.16.17", 684 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", 685 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", 686 | "cpu": [ 687 | "ia32" 688 | ], 689 | "dev": true, 690 | "optional": true, 691 | "os": [ 692 | "win32" 693 | ], 694 | "engines": { 695 | "node": ">=12" 696 | } 697 | }, 698 | "node_modules/@esbuild/win32-x64": { 699 | "version": "0.16.17", 700 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", 701 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", 702 | "cpu": [ 703 | "x64" 704 | ], 705 | "dev": true, 706 | "optional": true, 707 | "os": [ 708 | "win32" 709 | ], 710 | "engines": { 711 | "node": ">=12" 712 | } 713 | }, 714 | "node_modules/@jridgewell/gen-mapping": { 715 | "version": "0.3.3", 716 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 717 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 718 | "dev": true, 719 | "dependencies": { 720 | "@jridgewell/set-array": "^1.0.1", 721 | "@jridgewell/sourcemap-codec": "^1.4.10", 722 | "@jridgewell/trace-mapping": "^0.3.9" 723 | }, 724 | "engines": { 725 | "node": ">=6.0.0" 726 | } 727 | }, 728 | "node_modules/@jridgewell/resolve-uri": { 729 | "version": "3.1.1", 730 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 731 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 732 | "dev": true, 733 | "engines": { 734 | "node": ">=6.0.0" 735 | } 736 | }, 737 | "node_modules/@jridgewell/set-array": { 738 | "version": "1.1.2", 739 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 740 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 741 | "dev": true, 742 | "engines": { 743 | "node": ">=6.0.0" 744 | } 745 | }, 746 | "node_modules/@jridgewell/sourcemap-codec": { 747 | "version": "1.4.15", 748 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 749 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 750 | "dev": true 751 | }, 752 | "node_modules/@jridgewell/trace-mapping": { 753 | "version": "0.3.19", 754 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", 755 | "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", 756 | "dev": true, 757 | "dependencies": { 758 | "@jridgewell/resolve-uri": "^3.1.0", 759 | "@jridgewell/sourcemap-codec": "^1.4.14" 760 | } 761 | }, 762 | "node_modules/@types/prop-types": { 763 | "version": "15.7.5", 764 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", 765 | "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", 766 | "dev": true 767 | }, 768 | "node_modules/@types/react": { 769 | "version": "18.0.27", 770 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.27.tgz", 771 | "integrity": "sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==", 772 | "dev": true, 773 | "dependencies": { 774 | "@types/prop-types": "*", 775 | "@types/scheduler": "*", 776 | "csstype": "^3.0.2" 777 | } 778 | }, 779 | "node_modules/@types/react-dom": { 780 | "version": "18.0.10", 781 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", 782 | "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", 783 | "dev": true, 784 | "dependencies": { 785 | "@types/react": "*" 786 | } 787 | }, 788 | "node_modules/@types/scheduler": { 789 | "version": "0.16.3", 790 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", 791 | "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", 792 | "dev": true 793 | }, 794 | "node_modules/@vitejs/plugin-react": { 795 | "version": "3.1.0", 796 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz", 797 | "integrity": "sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==", 798 | "dev": true, 799 | "dependencies": { 800 | "@babel/core": "^7.20.12", 801 | "@babel/plugin-transform-react-jsx-self": "^7.18.6", 802 | "@babel/plugin-transform-react-jsx-source": "^7.19.6", 803 | "magic-string": "^0.27.0", 804 | "react-refresh": "^0.14.0" 805 | }, 806 | "engines": { 807 | "node": "^14.18.0 || >=16.0.0" 808 | }, 809 | "peerDependencies": { 810 | "vite": "^4.1.0-beta.0" 811 | } 812 | }, 813 | "node_modules/ansi-styles": { 814 | "version": "3.2.1", 815 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 816 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 817 | "dev": true, 818 | "dependencies": { 819 | "color-convert": "^1.9.0" 820 | }, 821 | "engines": { 822 | "node": ">=4" 823 | } 824 | }, 825 | "node_modules/browserslist": { 826 | "version": "4.21.10", 827 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", 828 | "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", 829 | "dev": true, 830 | "funding": [ 831 | { 832 | "type": "opencollective", 833 | "url": "https://opencollective.com/browserslist" 834 | }, 835 | { 836 | "type": "tidelift", 837 | "url": "https://tidelift.com/funding/github/npm/browserslist" 838 | }, 839 | { 840 | "type": "github", 841 | "url": "https://github.com/sponsors/ai" 842 | } 843 | ], 844 | "dependencies": { 845 | "caniuse-lite": "^1.0.30001517", 846 | "electron-to-chromium": "^1.4.477", 847 | "node-releases": "^2.0.13", 848 | "update-browserslist-db": "^1.0.11" 849 | }, 850 | "bin": { 851 | "browserslist": "cli.js" 852 | }, 853 | "engines": { 854 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 855 | } 856 | }, 857 | "node_modules/caniuse-lite": { 858 | "version": "1.0.30001524", 859 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", 860 | "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", 861 | "dev": true, 862 | "funding": [ 863 | { 864 | "type": "opencollective", 865 | "url": "https://opencollective.com/browserslist" 866 | }, 867 | { 868 | "type": "tidelift", 869 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 870 | }, 871 | { 872 | "type": "github", 873 | "url": "https://github.com/sponsors/ai" 874 | } 875 | ] 876 | }, 877 | "node_modules/chalk": { 878 | "version": "2.4.2", 879 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 880 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 881 | "dev": true, 882 | "dependencies": { 883 | "ansi-styles": "^3.2.1", 884 | "escape-string-regexp": "^1.0.5", 885 | "supports-color": "^5.3.0" 886 | }, 887 | "engines": { 888 | "node": ">=4" 889 | } 890 | }, 891 | "node_modules/color-convert": { 892 | "version": "1.9.3", 893 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 894 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 895 | "dev": true, 896 | "dependencies": { 897 | "color-name": "1.1.3" 898 | } 899 | }, 900 | "node_modules/color-name": { 901 | "version": "1.1.3", 902 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 903 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 904 | "dev": true 905 | }, 906 | "node_modules/convert-source-map": { 907 | "version": "1.9.0", 908 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", 909 | "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", 910 | "dev": true 911 | }, 912 | "node_modules/csstype": { 913 | "version": "3.1.2", 914 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", 915 | "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", 916 | "dev": true 917 | }, 918 | "node_modules/debug": { 919 | "version": "4.3.4", 920 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 921 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 922 | "dev": true, 923 | "dependencies": { 924 | "ms": "2.1.2" 925 | }, 926 | "engines": { 927 | "node": ">=6.0" 928 | }, 929 | "peerDependenciesMeta": { 930 | "supports-color": { 931 | "optional": true 932 | } 933 | } 934 | }, 935 | "node_modules/electron-to-chromium": { 936 | "version": "1.4.505", 937 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.505.tgz", 938 | "integrity": "sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ==", 939 | "dev": true 940 | }, 941 | "node_modules/esbuild": { 942 | "version": "0.16.17", 943 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", 944 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", 945 | "dev": true, 946 | "hasInstallScript": true, 947 | "bin": { 948 | "esbuild": "bin/esbuild" 949 | }, 950 | "engines": { 951 | "node": ">=12" 952 | }, 953 | "optionalDependencies": { 954 | "@esbuild/android-arm": "0.16.17", 955 | "@esbuild/android-arm64": "0.16.17", 956 | "@esbuild/android-x64": "0.16.17", 957 | "@esbuild/darwin-arm64": "0.16.17", 958 | "@esbuild/darwin-x64": "0.16.17", 959 | "@esbuild/freebsd-arm64": "0.16.17", 960 | "@esbuild/freebsd-x64": "0.16.17", 961 | "@esbuild/linux-arm": "0.16.17", 962 | "@esbuild/linux-arm64": "0.16.17", 963 | "@esbuild/linux-ia32": "0.16.17", 964 | "@esbuild/linux-loong64": "0.16.17", 965 | "@esbuild/linux-mips64el": "0.16.17", 966 | "@esbuild/linux-ppc64": "0.16.17", 967 | "@esbuild/linux-riscv64": "0.16.17", 968 | "@esbuild/linux-s390x": "0.16.17", 969 | "@esbuild/linux-x64": "0.16.17", 970 | "@esbuild/netbsd-x64": "0.16.17", 971 | "@esbuild/openbsd-x64": "0.16.17", 972 | "@esbuild/sunos-x64": "0.16.17", 973 | "@esbuild/win32-arm64": "0.16.17", 974 | "@esbuild/win32-ia32": "0.16.17", 975 | "@esbuild/win32-x64": "0.16.17" 976 | } 977 | }, 978 | "node_modules/escalade": { 979 | "version": "3.1.1", 980 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 981 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 982 | "dev": true, 983 | "engines": { 984 | "node": ">=6" 985 | } 986 | }, 987 | "node_modules/escape-string-regexp": { 988 | "version": "1.0.5", 989 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 990 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 991 | "dev": true, 992 | "engines": { 993 | "node": ">=0.8.0" 994 | } 995 | }, 996 | "node_modules/fsevents": { 997 | "version": "2.3.3", 998 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 999 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1000 | "dev": true, 1001 | "hasInstallScript": true, 1002 | "optional": true, 1003 | "os": [ 1004 | "darwin" 1005 | ], 1006 | "engines": { 1007 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1008 | } 1009 | }, 1010 | "node_modules/function-bind": { 1011 | "version": "1.1.1", 1012 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1013 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 1014 | "dev": true 1015 | }, 1016 | "node_modules/gensync": { 1017 | "version": "1.0.0-beta.2", 1018 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 1019 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 1020 | "dev": true, 1021 | "engines": { 1022 | "node": ">=6.9.0" 1023 | } 1024 | }, 1025 | "node_modules/globals": { 1026 | "version": "11.12.0", 1027 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 1028 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 1029 | "dev": true, 1030 | "engines": { 1031 | "node": ">=4" 1032 | } 1033 | }, 1034 | "node_modules/has": { 1035 | "version": "1.0.3", 1036 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1037 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1038 | "dev": true, 1039 | "dependencies": { 1040 | "function-bind": "^1.1.1" 1041 | }, 1042 | "engines": { 1043 | "node": ">= 0.4.0" 1044 | } 1045 | }, 1046 | "node_modules/has-flag": { 1047 | "version": "3.0.0", 1048 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1049 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 1050 | "dev": true, 1051 | "engines": { 1052 | "node": ">=4" 1053 | } 1054 | }, 1055 | "node_modules/is-core-module": { 1056 | "version": "2.13.0", 1057 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", 1058 | "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", 1059 | "dev": true, 1060 | "dependencies": { 1061 | "has": "^1.0.3" 1062 | }, 1063 | "funding": { 1064 | "url": "https://github.com/sponsors/ljharb" 1065 | } 1066 | }, 1067 | "node_modules/js-tokens": { 1068 | "version": "4.0.0", 1069 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1070 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 1071 | }, 1072 | "node_modules/jsesc": { 1073 | "version": "2.5.2", 1074 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 1075 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 1076 | "dev": true, 1077 | "bin": { 1078 | "jsesc": "bin/jsesc" 1079 | }, 1080 | "engines": { 1081 | "node": ">=4" 1082 | } 1083 | }, 1084 | "node_modules/json5": { 1085 | "version": "2.2.3", 1086 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 1087 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 1088 | "dev": true, 1089 | "bin": { 1090 | "json5": "lib/cli.js" 1091 | }, 1092 | "engines": { 1093 | "node": ">=6" 1094 | } 1095 | }, 1096 | "node_modules/just-clone": { 1097 | "version": "6.2.0", 1098 | "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz", 1099 | "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==" 1100 | }, 1101 | "node_modules/killa": { 1102 | "version": "1.7.2", 1103 | "resolved": "https://registry.npmjs.org/killa/-/killa-1.7.2.tgz", 1104 | "integrity": "sha512-LNNRDq4SaHn3vHHYW+tPphNVUQaQPrqXtCvAJjLtnKmgwmkAfDFQy01993yWMwD/1Xm9m5vzCn7PzmaDJUuQQw==", 1105 | "dependencies": { 1106 | "just-clone": "6.2.0", 1107 | "killa": "^1.7.1", 1108 | "use-sync-external-store": "1.2.0" 1109 | }, 1110 | "engines": { 1111 | "node": ">=12" 1112 | }, 1113 | "peerDependencies": { 1114 | "react": ">=16.8" 1115 | } 1116 | }, 1117 | "node_modules/loose-envify": { 1118 | "version": "1.4.0", 1119 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1120 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1121 | "dependencies": { 1122 | "js-tokens": "^3.0.0 || ^4.0.0" 1123 | }, 1124 | "bin": { 1125 | "loose-envify": "cli.js" 1126 | } 1127 | }, 1128 | "node_modules/lru-cache": { 1129 | "version": "5.1.1", 1130 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 1131 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 1132 | "dev": true, 1133 | "dependencies": { 1134 | "yallist": "^3.0.2" 1135 | } 1136 | }, 1137 | "node_modules/magic-string": { 1138 | "version": "0.27.0", 1139 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", 1140 | "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", 1141 | "dev": true, 1142 | "dependencies": { 1143 | "@jridgewell/sourcemap-codec": "^1.4.13" 1144 | }, 1145 | "engines": { 1146 | "node": ">=12" 1147 | } 1148 | }, 1149 | "node_modules/ms": { 1150 | "version": "2.1.2", 1151 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1152 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1153 | "dev": true 1154 | }, 1155 | "node_modules/nanoid": { 1156 | "version": "3.3.6", 1157 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 1158 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 1159 | "dev": true, 1160 | "funding": [ 1161 | { 1162 | "type": "github", 1163 | "url": "https://github.com/sponsors/ai" 1164 | } 1165 | ], 1166 | "bin": { 1167 | "nanoid": "bin/nanoid.cjs" 1168 | }, 1169 | "engines": { 1170 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1171 | } 1172 | }, 1173 | "node_modules/node-releases": { 1174 | "version": "2.0.13", 1175 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", 1176 | "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", 1177 | "dev": true 1178 | }, 1179 | "node_modules/path-parse": { 1180 | "version": "1.0.7", 1181 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1182 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1183 | "dev": true 1184 | }, 1185 | "node_modules/picocolors": { 1186 | "version": "1.0.0", 1187 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1188 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1189 | "dev": true 1190 | }, 1191 | "node_modules/postcss": { 1192 | "version": "8.4.29", 1193 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", 1194 | "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", 1195 | "dev": true, 1196 | "funding": [ 1197 | { 1198 | "type": "opencollective", 1199 | "url": "https://opencollective.com/postcss/" 1200 | }, 1201 | { 1202 | "type": "tidelift", 1203 | "url": "https://tidelift.com/funding/github/npm/postcss" 1204 | }, 1205 | { 1206 | "type": "github", 1207 | "url": "https://github.com/sponsors/ai" 1208 | } 1209 | ], 1210 | "dependencies": { 1211 | "nanoid": "^3.3.6", 1212 | "picocolors": "^1.0.0", 1213 | "source-map-js": "^1.0.2" 1214 | }, 1215 | "engines": { 1216 | "node": "^10 || ^12 || >=14" 1217 | } 1218 | }, 1219 | "node_modules/react": { 1220 | "version": "18.2.0", 1221 | "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", 1222 | "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", 1223 | "dependencies": { 1224 | "loose-envify": "^1.1.0" 1225 | }, 1226 | "engines": { 1227 | "node": ">=0.10.0" 1228 | } 1229 | }, 1230 | "node_modules/react-dom": { 1231 | "version": "18.2.0", 1232 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", 1233 | "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", 1234 | "dependencies": { 1235 | "loose-envify": "^1.1.0", 1236 | "scheduler": "^0.23.0" 1237 | }, 1238 | "peerDependencies": { 1239 | "react": "^18.2.0" 1240 | } 1241 | }, 1242 | "node_modules/react-refresh": { 1243 | "version": "0.14.0", 1244 | "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", 1245 | "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", 1246 | "dev": true, 1247 | "engines": { 1248 | "node": ">=0.10.0" 1249 | } 1250 | }, 1251 | "node_modules/resolve": { 1252 | "version": "1.22.4", 1253 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", 1254 | "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", 1255 | "dev": true, 1256 | "dependencies": { 1257 | "is-core-module": "^2.13.0", 1258 | "path-parse": "^1.0.7", 1259 | "supports-preserve-symlinks-flag": "^1.0.0" 1260 | }, 1261 | "bin": { 1262 | "resolve": "bin/resolve" 1263 | }, 1264 | "funding": { 1265 | "url": "https://github.com/sponsors/ljharb" 1266 | } 1267 | }, 1268 | "node_modules/rollup": { 1269 | "version": "3.28.1", 1270 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", 1271 | "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", 1272 | "dev": true, 1273 | "bin": { 1274 | "rollup": "dist/bin/rollup" 1275 | }, 1276 | "engines": { 1277 | "node": ">=14.18.0", 1278 | "npm": ">=8.0.0" 1279 | }, 1280 | "optionalDependencies": { 1281 | "fsevents": "~2.3.2" 1282 | } 1283 | }, 1284 | "node_modules/scheduler": { 1285 | "version": "0.23.0", 1286 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", 1287 | "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", 1288 | "dependencies": { 1289 | "loose-envify": "^1.1.0" 1290 | } 1291 | }, 1292 | "node_modules/semver": { 1293 | "version": "6.3.1", 1294 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 1295 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 1296 | "dev": true, 1297 | "bin": { 1298 | "semver": "bin/semver.js" 1299 | } 1300 | }, 1301 | "node_modules/source-map-js": { 1302 | "version": "1.0.2", 1303 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1304 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 1305 | "dev": true, 1306 | "engines": { 1307 | "node": ">=0.10.0" 1308 | } 1309 | }, 1310 | "node_modules/supports-color": { 1311 | "version": "5.5.0", 1312 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1313 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1314 | "dev": true, 1315 | "dependencies": { 1316 | "has-flag": "^3.0.0" 1317 | }, 1318 | "engines": { 1319 | "node": ">=4" 1320 | } 1321 | }, 1322 | "node_modules/supports-preserve-symlinks-flag": { 1323 | "version": "1.0.0", 1324 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1325 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1326 | "dev": true, 1327 | "engines": { 1328 | "node": ">= 0.4" 1329 | }, 1330 | "funding": { 1331 | "url": "https://github.com/sponsors/ljharb" 1332 | } 1333 | }, 1334 | "node_modules/to-fast-properties": { 1335 | "version": "2.0.0", 1336 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1337 | "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", 1338 | "dev": true, 1339 | "engines": { 1340 | "node": ">=4" 1341 | } 1342 | }, 1343 | "node_modules/update-browserslist-db": { 1344 | "version": "1.0.11", 1345 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", 1346 | "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", 1347 | "dev": true, 1348 | "funding": [ 1349 | { 1350 | "type": "opencollective", 1351 | "url": "https://opencollective.com/browserslist" 1352 | }, 1353 | { 1354 | "type": "tidelift", 1355 | "url": "https://tidelift.com/funding/github/npm/browserslist" 1356 | }, 1357 | { 1358 | "type": "github", 1359 | "url": "https://github.com/sponsors/ai" 1360 | } 1361 | ], 1362 | "dependencies": { 1363 | "escalade": "^3.1.1", 1364 | "picocolors": "^1.0.0" 1365 | }, 1366 | "bin": { 1367 | "update-browserslist-db": "cli.js" 1368 | }, 1369 | "peerDependencies": { 1370 | "browserslist": ">= 4.21.0" 1371 | } 1372 | }, 1373 | "node_modules/use-sync-external-store": { 1374 | "version": "1.2.0", 1375 | "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", 1376 | "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", 1377 | "peerDependencies": { 1378 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0" 1379 | } 1380 | }, 1381 | "node_modules/vite": { 1382 | "version": "4.1.0", 1383 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.1.0.tgz", 1384 | "integrity": "sha512-YoUKE/9bbK4C8ZeSYMDDEF8H5aypmWUq4WisftDhntR1gkI2zt2SGT/5Wd2xu6ZoVXkCyO3U4844KWG9e4nFoQ==", 1385 | "dev": true, 1386 | "dependencies": { 1387 | "esbuild": "^0.16.14", 1388 | "postcss": "^8.4.21", 1389 | "resolve": "^1.22.1", 1390 | "rollup": "^3.10.0" 1391 | }, 1392 | "bin": { 1393 | "vite": "bin/vite.js" 1394 | }, 1395 | "engines": { 1396 | "node": "^14.18.0 || >=16.0.0" 1397 | }, 1398 | "optionalDependencies": { 1399 | "fsevents": "~2.3.2" 1400 | }, 1401 | "peerDependencies": { 1402 | "@types/node": ">= 14", 1403 | "less": "*", 1404 | "sass": "*", 1405 | "stylus": "*", 1406 | "sugarss": "*", 1407 | "terser": "^5.4.0" 1408 | }, 1409 | "peerDependenciesMeta": { 1410 | "@types/node": { 1411 | "optional": true 1412 | }, 1413 | "less": { 1414 | "optional": true 1415 | }, 1416 | "sass": { 1417 | "optional": true 1418 | }, 1419 | "stylus": { 1420 | "optional": true 1421 | }, 1422 | "sugarss": { 1423 | "optional": true 1424 | }, 1425 | "terser": { 1426 | "optional": true 1427 | } 1428 | } 1429 | }, 1430 | "node_modules/yallist": { 1431 | "version": "3.1.1", 1432 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1433 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 1434 | "dev": true 1435 | } 1436 | } 1437 | } 1438 | --------------------------------------------------------------------------------