├── .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 |

66 |

67 |
68 |
Vite + React + Killa
69 |
70 |
71 |
72 |
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 |
60 |
+ Killa
61 |
62 |
63 |
64 |
65 |
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 |
--------------------------------------------------------------------------------