Full Code of velopert/sangte for AI

main 08f1a7701f17 cached
64 files
75.5 KB
23.3k tokens
85 symbols
1 requests
Download .txt
Repository: velopert/sangte
Branch: main
Commit: 08f1a7701f17
Files: 64
Total size: 75.5 KB

Directory structure:
gitextract_d21tq2rb/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .prettierrc
├── LICENSE.md
├── README-ko.md
├── README.md
├── babel.config.js
├── examples/
│   ├── .gitignore
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── App.css
│   │   ├── App.tsx
│   │   ├── components/
│   │   │   ├── Counter.tsx
│   │   │   ├── GlobalState.tsx
│   │   │   ├── Hydrate.tsx
│   │   │   ├── Inherit.tsx
│   │   │   ├── Initialize.tsx
│   │   │   ├── MemoizedSelector.tsx
│   │   │   ├── MemoryLeakTester.tsx
│   │   │   ├── MultiProviders.tsx
│   │   │   ├── Selector.tsx
│   │   │   ├── Todos.tsx
│   │   │   └── VisibleToggle.tsx
│   │   ├── index.css
│   │   ├── main.tsx
│   │   └── vite-env.d.ts
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
├── index.html
├── jest.config.ts
├── package.json
├── rollup.config.js
├── src/
│   ├── contexts/
│   │   ├── SangteProvider.tsx
│   │   ├── __tests__/
│   │   │   └── SangteProvider.test.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── __tests__/
│   │   │   ├── useResetAllSangte.test.tsx
│   │   │   ├── useResetSangte.test.ts
│   │   │   ├── useSangte.test.ts
│   │   │   ├── useSangteActions.test.ts
│   │   │   ├── useSangteCallback.test.ts
│   │   │   ├── useSangteStore.test.ts
│   │   │   ├── useSangteValue.test.ts
│   │   │   └── useSetSangte.test.ts
│   │   ├── index.ts
│   │   ├── useResetAllSangte.ts
│   │   ├── useResetSangte.ts
│   │   ├── useSangte.ts
│   │   ├── useSangteActions.ts
│   │   ├── useSangteCallback.ts
│   │   ├── useSangteStore.ts
│   │   ├── useSangteValue.ts
│   │   └── useSetSangte.ts
│   ├── index.ts
│   ├── lib/
│   │   ├── SangteInitializer.ts
│   │   ├── SangteManager.ts
│   │   ├── __tests__/
│   │   │   ├── SangteManager.test.ts
│   │   │   ├── sangte.test.ts
│   │   │   └── shallowEqual.test.ts
│   │   ├── index.ts
│   │   ├── sangte.ts
│   │   └── shallowEqual.ts
│   └── setupTest.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on: [push, pull_request]
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set up Node 18
        uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install dependencies
        run: yarn
      - name: Run tests and collect coverage
        run: yarn test
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
coverage/*

================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true,
  "semi": false,
  "printWidth": 100
}


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) 2022-present velopert

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README-ko.md
================================================
# sangte

[![](https://img.shields.io/npm/v/sangte?style=flat-square)](https://www.npmjs.com/package/sangte)
[![](https://img.shields.io/bundlephobia/min/sangte?style=flat-square)](https://bundlephobia.com/package/sangte)
[![](https://img.shields.io/codecov/c/github/velopert/sangte?style=flat-square)](https://app.codecov.io/gh/velopert/sangte)

Sangte 는 리액트의 상태 관리 라이브러리입니다. 이 라이브러리는 [Redux Toolkit](https://redux-toolkit.js.org/)과 [Recoil](https://recoiljs.org/)에서 영감을 받아 만들어졌습니다.

> Sangte는 "상태"의 발음을 알파벳으로 표기한 것입니다.

## 설치

다음 명령어로 이 라이브러리를 설치하세요:

```
npm install sangte
```

yarn을 사용하신다면:

```
yarn add sangte
```

## 왜 Sangte 를 쓰나요?

- 준비해야 할 코드가 적음 (Less boilerplate)
- 원하는 상태가 업데이트 됐을 때만 리렌더링함
- 사용하기 쉬움
- 여러개의 Provider 허용
- 타입스크립트 지원

## 사용법

### 먼저, 상태를 만드세요

상태를 만드려면 `sangte` 함수를 사용해야 합니다. 이 라이브러리의 상태는 초깃값이 있어야 하며, 상태를 업데이트하는 액션을 가질 수 있습니다. 액션은 선택사항입니다.

Sangte는 내부적으로 [immer](https://immerjs.github.io/immer/)를 사용하여 상태를 업데이트합니다. 그래서 상태를 직접 변경하면서 불변성을 유지할 수 있습니다.

```ts
import { sangte } from 'sangte'

const counterState = sangte(0)
const textState = sangte('text')
const userState = sangte({ id: 1, name: 'John', email: 'john@email.com' }, (prev) => ({
  setName(name: string) {
    prev.name = name
  },
  setEmail(email: string) {
    return {
      ...prev,
      email,
    }
  },
}))

interface Todo {
  id: number
  text: string
  done: boolean
}

const todosState = sangte<Todo[]>([], (prev) => ({
  add(todo: Todo) {
    return prev.push(todo)
  },
  remove(id: number) {
    return prev.filter((todo) => todo.id !== id)
  },
  toggle(id: number) {
    const todo = prev.find((todo) => todo.id === id)
    todo.done = !todo.done
  },
}))
```

### 컴포넌트에서 상태 또는 액션을 사용하기

이 라이브러리는 상태나 액션을 여러분의 컴포넌트에서 사용 할 수 있도록 Hook 함수들을 제공합니다.

#### useSangte

`useSangte`는 `useState`와 비슷하지만, 전역적으로 작동합니다. 이 함수는 상태값과 업데이트 함수를 반환합니다.

```tsx
import { sangte, useSangte } from 'sangte'

const counterState = sangte(0)

function Counter() {
  const [counter, setCounter] = useSangte(counterState)
  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
      <button onClick={() => setCounter(0)}>Reset</button>
    </div>
  )
}

export default Counter
```

#### useSangteValue

만약 컴포넌트에서 상태값만 필요로 한다면 `useSangteValue` 를 사용하세요.

```tsx
import { useSangteValue } from 'sangte'

const counterState = sangte(0)

function CounterValue() {
  const counter = useSangteValue(counterState)
  return <h1>{counter}</h1>
}
```

상태에서 일부분의 값만 필요로 한다면 두번째 인자에 셀렉터 함수를 넣어서 원하는 부분만 선택할 수 있습니다. 객체 형태로 여러 필드를 선택하게 된다면 기본적으로 shallow compare를 하고 나서 리렌더링을 합니다. 비교 함수는 세번째 인자에 임의 비교 함수를 전달하여 override 할 수 있습니다.

```tsx
import { sangte, useSangteValue } from 'sangte'

const userState = sangte({ id: 1, name: 'John', email: 'john@email.com' })

function User() {
  const { name, email } = useSangteValue(userState, (state) => ({
    name: state.name,
    email: state.email,
  }))
  return (
    <div>
      <h1>{name}</h1>
      <h2>{email}</h2>
    </div>
  )
}
```

만약 의존하는 값이 업데이트 되었을 때만 실행되는 memoized 셀렉터를 사용하고 싶으시다면 다음과 같이 읽기 전용 Sangte를 만들어서 사용할 수 있습니다.

```tsx
import { sangte } from 'sangte'
const todosState = sangte([
  { id: 1, text: 'Basic usage', done: true },
  { id: 21, text: 'Ready-only sangte', done: false },
])
const undoneTodosValue = sangte((get) => get(todosState).filter((todo) => !todo.done))
function UndoneTodos() {
  const undoneTodos = useSangteValue(undoneTodosValue)

  return <div>{undoneTodos.length} todos undone.</div>
}
```

#### useSetSangte

만약 컴포넌트에서 상태 업데이트 함수만을 필요로 한다면 `useSetSangte`를 사용하세요.

```tsx
import { sangte, useSetSangte } from 'sangte'

const counterState = sangte(0)

function CounterButtons() {
  const setCounter = useSetSangte(counterState)
  return (
    <div>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
    </div>
  )
}
```

#### useSangteActions

상태를 만들 때 액션을 정의했다면, `useSangteActions`를 사용하여 액션을 가져와서 호출할 수 있습니다.

```tsx
import { sangte, useSangteActions } from 'sangte'

const counterState = sangte(0, (prev) => ({
  increase() {
    return prev + 1
  },
  decreaseBy(amount: number) {
    return prev - amount
  },
}))

function CounterButtons() {
  const { increase, decreaseBy } = useSangteActions(counterState)
  return (
    <div>
      <button onClick={increase}>Increase</button>
      <button onClick={() => decreaseBy(10)}>Decrease</button>
    </div>
  )
}
```

#### useResetSangte

상태를 초깃값으로 바꾸고 싶다면 `useResetSangte`를 사용하세요.

```tsx
import { sangte, useResetSangte } from 'sangte'

const counterState = sangte(0)

function Counter() {
  const [counter, setCounter] = useSangte(counterState)
  const resetCounter = useResetSangte(counterState)
  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
      <button onClick={resetCounter}>Reset</button>
    </div>
  )
}
```

#### useResetAllSangte

모든 상태를 초기화하고 싶다면 `useResetAllSangte`를 사용하세요. 이 hook은 자식 SangteProvider에도 영향을 미칩니다.

```tsx
import { sangte, useResetAllSangte } from 'sangte'

const counterState = sangte(0)
const textState = sangte('text')

function Counter() {
  const [counter, setCounter] = useSangte(counterState)
  const [text, setText] = useSangte(textState)
  const resetAll = useResetAllSangte()

  return (
    <div>
      <h1>
        {counter} | {text}
      </h1>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
      <button onClick={() => setText('Hello World'}>Update Text</button>
      <button onClick={resetAll}>Reset</button>
    </div>
  )
}
```

전역적으로 모든 상태(모든 부모 SangteProvider에 있는 상태 포함)를 초기화하고 싶다면 함수의 첫번째 인자에 `true`를 넘겨주세요.

```tsx
import { sangte, useResetAllSangte } from 'sangte'

function RestAll() {
  const resetAll = useResetAllSangte()
  return <button onClick={() => resetAll(true)}>Reset All</button>
}
```

#### useSangteCallback

콜백 함수 안에서 상태 값을 사용하고 싶지만 상태 값이 바뀔 때마다 컴포넌트가 리렌더링 되는 것을 원하지 않는다면, `useSangteCallback`을 쓰세요.

```tsx
import { sangte, useSangteCallback } from 'sangte'

const valueState = sangte('hello world!')

function ConfirmButton() {
  // 이 컴포넌트는 valueState가 변경되어도 리렌더링 되지 않습니다.
  const confirm = useSangteCallback(({ get }) => {
    const value = get(valueState)
    console.log(value) // value 값을 가지고 어떠한 작업을 하기..
  }, [])

  return <button onClick={confirm}>Confirm</button>
}
```

`useSangteCallback`에서 상태 업데이트 함수나 액션들을 사용할 수 있습니다.

```tsx
import { sangte, useSangteCallback } from 'sangte'

const counterState = sangte(0, (prev) => ({
  add(value: number) {
    return prev + value
  },
}))

function Counter() {
  // add 호출
  const add2 = useSangteCallback(({ actions }) => {
    const { add } = actions(counterState)
    add(2)
  }, [])

  // counterState 10000으로 변경
  const set10000 = useSangteCallback(({ set }) => {
    set(counterState, 10000)
  }, [])

  return (
    <div>
      <button onClick={add2}>add 2</button>
      <button onClick={logCount}>logCount</button>
      <button onClick={set10000}>set 10000</button>
    </div>
  )
}
```


================================================
FILE: README.md
================================================
# sangte

[![](https://img.shields.io/npm/v/sangte?style=flat-square)](https://www.npmjs.com/package/sangte)
[![](https://img.shields.io/bundlephobia/min/sangte?style=flat-square)](https://bundlephobia.com/package/sangte)
[![](https://img.shields.io/codecov/c/github/velopert/sangte?style=flat-square)](https://app.codecov.io/gh/velopert/sangte)

[English](./README.md) | [한국어](./README-ko.md)

Sangte is a state management library for React. This library is inspired by [Redux Toolkit](https://redux-toolkit.js.org/) and [Recoil](https://recoiljs.org/).

> Sangte means "state" in Korean.

## Installation

To install the library, run the following command:

```
npm install sangte
```

Or if you're using yarn:

```
yarn add sangte
```

## Why sangte?

- Less boilerplate
- Rerender only when the state you're using is updated
- Easy to use
- Allows multiple providers
- TypeScript support

## Usage

### First create a state

To create a state you need to use the `sangte` function. A state of sangte should have a default value, and actions to update the state. Actions are optional.

Sangte uses [immer](https://immerjs.github.io/immer/) internally to update the state. So you can mutate the state directly while keeping the immutability.

```ts
import { sangte } from 'sangte'

const counterState = sangte(0)
const textState = sangte('text')
const userState = sangte({ id: 1, name: 'John', email: 'john@email.com' }, (prev) => ({
  setName(name: string) {
    prev.name = name
  },
  setEmail(email: string) {
    return {
      ...prev,
      email,
    }
  },
}))

interface Todo {
  id: number
  text: string
  done: boolean
}

const todosState = sangte<Todo[]>([], (prev) => ({
  add(todo: Todo) {
    return prev.push(todo)
  },
  remove(id: number) {
    return prev.filter((todo) => todo.id !== id)
  },
  toggle(id: number) {
    const todo = prev.find((todo) => todo.id === id)
    todo.done = !todo.done
  },
}))
```

### Use the state or actions from your components

The library provides hooks to utilize the state or actions from your components.

#### useSangte

`useSangte` works like `useState` from React, but it works globally. It returns the state and a setter function to update the state.

```tsx
import { sangte, useSangte } from 'sangte'

const counterState = sangte(0)

function Counter() {
  const [counter, setCounter] = useSangte(counterState)
  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
      <button onClick={() => setCounter(0)}>Reset</button>
    </div>
  )
}

export default Counter
```

#### useSangteValue

If you only need the value of the state, you can use `useSangteValue`.

```tsx
import { useSangteValue } from 'sangte'

const counterState = sangte(0)

function CounterValue() {
  const counter = useSangteValue(counterState)
  return <h1>{counter}</h1>
}
```

If you want to select a part of the state, you can pass selector function as second argument. If you select multiple fields, the component will rerender after shallow comparison. You can override the comparison function by passing a custom equality function to third argument.

```tsx
import { sangte, useSangteValue } from 'sangte'

const userState = sangte({ id: 1, name: 'John', email: 'john@email.com' })

function User() {
  const { name, email } = useSangteValue(userState, (state) => ({
    name: state.name,
    email: state.email,
  }))
  return (
    <div>
      <h1>{name}</h1>
      <h2>{email}</h2>
    </div>
  )
}
```

If you want to use a memoized selector that is prcessed only when its dependencies update, you can create a read-only Sangte as below.

```tsx
import { sangte } from 'sangte'
const todosState = sangte([
  { id: 1, text: 'Basic usage', done: true },
  { id: 21, text: 'Ready-only sangte', done: false },
])
const undoneTodosValue = sangte((get) => get(todosState).filter((todo) => !todo.done))
function UndoneTodos() {
  const undoneTodos = useSangteValue(undoneTodosValue)

  return <div>{undoneTodos.length} todos undone.</div>
}
```

#### useSetSangte

If you only need the updater function of the state, you can use `useSetSangte`.

```tsx
import { sangte, useSetSangte } from 'sangte'

const counterState = sangte(0)

function CounterButtons() {
  const setCounter = useSetSangte(counterState)
  return (
    <div>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
    </div>
  )
}
```

#### useSangteActions

If you have defined actions for the state, you can use `useSangteActions` to get the actions.

```tsx
import { sangte, useSangteActions } from 'sangte'

const counterState = sangte(0, (prev) => ({
  increase() {
    return prev + 1
  },
  decreaseBy(amount: number) {
    return prev - amount
  },
}))

function CounterButtons() {
  const { increase, decreaseBy } = useSangteActions(counterState)
  return (
    <div>
      <button onClick={increase}>Increase</button>
      <button onClick={() => decreaseBy(10)}>Decrease</button>
    </div>
  )
}
```

#### useResetSangte

If you want to reset the state to its default value, you can use `useResetSangte`.

```tsx
import { sangte, useResetSangte } from 'sangte'

const counterState = sangte(0)

function Counter() {
  const [counter, setCounter] = useSangte(counterState)
  const resetCounter = useResetSangte(counterState)
  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
      <button onClick={resetCounter}>Reset</button>
    </div>
  )
}
```

#### useResetAllSangte

If you want to reset all the states to their default values, you can use `useResetAllSangte`. This hook also resets sangte inside the nested providers.

```tsx
import { sangte, useResetAllSangte } from 'sangte'

const counterState = sangte(0)
const textState = sangte('text')

function Counter() {
  const [counter, setCounter] = useSangte(counterState)
  const [text, setText] = useSangte(textState)
  const resetAll = useResetAllSangte()

  return (
    <div>
      <h1>
        {counter} | {text}
      </h1>
      <button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
      <button onClick={() => setText('Hello World'}>Update Text</button>
      <button onClick={resetAll}>Reset</button>
    </div>
  )
}
```

If you want to reset the states globally (including all parent providers), you can pass `true` as first argument.

```tsx
import { sangte, useResetAllSangte } from 'sangte'

function RestAll() {
  const resetAll = useResetAllSangte()
  return <button onClick={() => resetAll(true)}>Reset All</button>
}
```

#### useSangteCallback

If you want to use the state in a callback but you do not want to rerender the component as the state changes, you can use `useSangteCallback`.

```tsx
import { sangte, useSangteCallback } from 'sangte'

const valueState = sangte('hello world!')

function ConfirmButton() {
  // this component won't rerender even when valueState changes
  const confirm = useSangteCallback(({ get }) => {
    const value = get(valueState)
    console.log(value) // do something with value..
  }, [])

  return <button onClick={confirm}>Confirm</button>
}
```

You can also use the setter function or actions with `useSangteCallback`.

```tsx
import { sangte, useSangteCallback } from 'sangte'

const counterState = sangte(0, (prev) => ({
  add(value: number) {
    return prev + value
  },
}))

function Counter() {
  // calls add action
  const add2 = useSangteCallback(({ actions }) => {
    const { add } = actions(counterState)
    add(2)
  }, [])

  // sets counterState to 10000
  const set10000 = useSangteCallback(({ set }) => {
    set(counterState, 10000)
  }, [])

  return (
    <div>
      <button onClick={add2}>add 2</button>
      <button onClick={logCount}>logCount</button>
      <button onClick={set10000}>set 10000</button>
    </div>
  )
}
```

## Recipe

### Using multiple providers

### Inheriting state from parent provider

### Server side rendering

> Docs are still in progress. If you have any questions, please open an issue.


================================================
FILE: babel.config.js
================================================
module.exports = (api, targets) => {
  // https://babeljs.io/docs/en/config-files#config-function-api
  const isTestEnv = api.env('test')

  return {
    babelrc: false,
    ignore: ['./node_modules'],
    presets: [
      [
        '@babel/preset-env',
        {
          loose: true,
          modules: isTestEnv ? 'commonjs' : false,
          targets: isTestEnv ? { node: 'current' } : targets,
        },
      ],
    ],
    plugins: [
      [
        '@babel/plugin-transform-react-jsx',
        {
          runtime: 'automatic',
        },
      ],
      ['@babel/plugin-transform-typescript', { isTSX: true }],
    ],
  }
}


================================================
FILE: examples/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: examples/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: examples/package.json
================================================
{
  "name": "vite-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "sangte": "^0.1.18"
  },
  "devDependencies": {
    "@types/react": "^18.0.17",
    "@types/react-dom": "^18.0.6",
    "@vitejs/plugin-react": "^2.0.1",
    "typescript": "^4.6.4",
    "vite": "^3.0.7"
  }
}


================================================
FILE: examples/src/App.css
================================================
.samples {
  display: flex;
  flex-wrap: wrap;
}

.samples > div {
  display: flex;
  align-items:center;
  justify-content: center;
  border: 1px solid black;
  width: 25%;
  padding: 16px;
}

================================================
FILE: examples/src/App.tsx
================================================
import './App.css'
import Counter from './components/Counter'
import Initialize from './components/Initialize'
import Hydrate from './components/Hydrate'
import Selector from './components/Selector'
import MultiProviders from './components/MultiProviders'
import Inherit from './components/Inherit'
import GlobalState from './components/GlobalState'
import MemoizedSelector from './components/MemoizedSelector'
import Todos from './components/Todos'
import VisibleToggle from './components/VisibleToggle'
import MemoryLeakTester from './components/MemoryLeakTester'

function App() {
  return (
    <div className="samples">
      <div>
        <Counter />
      </div>
      <div>
        <Initialize />
      </div>
      <div>
        <Hydrate />
      </div>
      <div>
        <Selector />
      </div>
      <div>
        <MultiProviders />
      </div>
      <div>
        <VisibleToggle>
          <MultiProviders />
        </VisibleToggle>
      </div>
      <div>
        <Inherit />
      </div>
      <div>
        <GlobalState />
      </div>
      <div>
        <Todos />
      </div>
      <div>
        <VisibleToggle>
          <MemoizedSelector />
        </VisibleToggle>
      </div>
      <div>
        <VisibleToggle>
          <MemoizedSelector />
        </VisibleToggle>
      </div>
      <div>
        <VisibleToggle>
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
          <MemoryLeakTester />
        </VisibleToggle>
      </div>
    </div>
  )
}

export default App


================================================
FILE: examples/src/components/Counter.tsx
================================================
import { sangte, useResetSangte, useSangteActions, useSangteValue } from 'sangte'

const counterState = sangte(0, (prev) => ({
  increase() {
    return prev + 1
  },
  decrease(amount: number) {
    return prev - amount
  },
}))

function Counter() {
  const counter = useSangteValue(counterState)
  const actions = useSangteActions(counterState)
  const reset = useResetSangte(counterState)

  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={actions.increase}>+</button>
      <button onClick={() => actions.decrease(10)}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

export default Counter


================================================
FILE: examples/src/components/GlobalState.tsx
================================================
import { SangteProvider, useSangteActions } from 'sangte'
import { useSangteValue } from 'sangte'
import { sangte } from 'sangte'

const globalCounterState = sangte(
  0,
  (prev) => ({
    increase() {
      return prev + 1
    },
  }),
  {
    global: true,
  }
)

function Counter() {
  const counter = useSangteValue(globalCounterState)
  const actions = useSangteActions(globalCounterState)

  return (
    <div>
      <h3>{counter}</h3>
      <button onClick={actions.increase}>+1</button>
    </div>
  )
}

function GlobalState() {
  return (
    <SangteProvider>
      <Counter />
      <div style={{ padding: 16, border: '1px solid black' }}>
        <SangteProvider>
          <Counter />
        </SangteProvider>
      </div>
    </SangteProvider>
  )
}

export default GlobalState


================================================
FILE: examples/src/components/Hydrate.tsx
================================================
import { sangte, SangteProvider, useSangteValue } from 'sangte'

const numberState = sangte(0, { key: 'number' })
const textState = sangte('hello world', { key: 'text' })

function Values() {
  const number = useSangteValue(numberState)
  const text = useSangteValue(textState)

  return (
    <div>
      <div>
        <b>number:</b> {number}
      </div>
      <div>
        <b>text:</b> {text}
      </div>
    </div>
  )
}

function Hydrate() {
  return (
    <SangteProvider
      dehydratedState={{
        number: 10,
        text: 'bye world',
      }}
    >
      <Values />
    </SangteProvider>
  )
}

export default Hydrate


================================================
FILE: examples/src/components/Inherit.tsx
================================================
import { SangteProvider, useSangteActions } from 'sangte'
import { useSangteValue } from 'sangte'
import { sangte } from 'sangte'

const counterState = sangte(0, (prev) => ({
  increase() {
    return prev + 1
  },
}))

function Counter() {
  const counter = useSangteValue(counterState)
  const actions = useSangteActions(counterState)

  return (
    <div>
      <h3>{counter}</h3>
      <button onClick={actions.increase}>+1</button>
    </div>
  )
}

function Inherit() {
  return (
    <SangteProvider>
      <Counter />
      <div style={{ padding: 16, border: '1px solid black' }}>
        <SangteProvider inheritSangtes={[counterState]}>
          <Counter />
        </SangteProvider>
      </div>
    </SangteProvider>
  )
}

export default Inherit


================================================
FILE: examples/src/components/Initialize.tsx
================================================
import React from 'react'
import { sangte, SangteProvider, useSangteValue } from 'sangte'

const textState = sangte('Default text')

function Text() {
  const text = useSangteValue(textState)
  return <div>{text}</div>
}

function Initialize() {
  return (
    <SangteProvider
      initialize={({ set }) => {
        set(textState, 'Hello World!')
      }}
    >
      <Text />
    </SangteProvider>
  )
}

export default Initialize


================================================
FILE: examples/src/components/MemoizedSelector.tsx
================================================
import { useState } from 'react'
import { resangte, sangte, useSangteActions, useSangteValue } from 'sangte'
import { todosState } from './Todos'

const uncompletedTodosValue = resangte((get) => {
  console.log('filtering..')
  return get(todosState).filter((todo) => !todo.completed)
})

function MemoizedSelector() {
  const uncompletedTodos = useSangteValue(uncompletedTodosValue)
  const actions = useSangteActions(todosState)
  const [value, setValue] = useState(0)
  return (
    <div>
      {uncompletedTodos.map((todo) => (
        <div
          onClick={() => {
            actions.toggle(todo.id)
          }}
        >
          {todo.text}
        </div>
      ))}
      <div>{value}</div>
      <button
        onClick={() => {
          setValue(value + 1)
        }}
      >
        +1
      </button>
    </div>
  )
}

export default MemoizedSelector


================================================
FILE: examples/src/components/MemoryLeakTester.tsx
================================================
import { useEffect, useRef, useState } from 'react'
import { resangte, sangte, SangteProvider, useSangteActions, useSangteValue } from 'sangte'

const array = new Array(1000).fill(0).map((_, i) => ({ id: i, done: false }))

const itemsState = sangte(array, (prev) => ({
  work(id: number) {
    const item = prev.find((item) => item.id === id)
    if (!item) return
    item.done = true
  },
}))
const doneItemsValue = resangte((get) => get(itemsState).filter((item) => item.done))

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

function Work({ onHide }: { onHide(): void }) {
  const called = useRef<boolean>(false)

  const doneItems = useSangteValue(doneItemsValue)
  const { work } = useSangteActions(itemsState)

  useEffect(() => {
    if (called.current) return
    called.current = true
    const random = Math.floor(Math.random() * 10)
    const loop = async () => {
      for (let i = 0; i < random; i++) {
        work(i)
        console.log('workign!')
        await sleep(1)
      }
      onHide()
    }
    loop()
  }, [])

  return <div>{doneItems.length} items done!</div>
}

function Wrapper() {
  const [visible, setVisible] = useState(true)

  useEffect(() => {
    if (!visible) {
      setVisible(true)
    }
  }, [visible])

  if (!visible) return null
  return (
    <SangteProvider>
      <Work
        onHide={() => {
          setVisible(false)
        }}
      />
    </SangteProvider>
  )
}

function MemoryLeakTester() {
  return <Wrapper />
}

export default MemoryLeakTester


================================================
FILE: examples/src/components/MultiProviders.tsx
================================================
import { SangteProvider, useSangteActions } from 'sangte'
import { useSangteValue } from 'sangte'
import { sangte } from 'sangte'

const counterState = sangte(0, (prev) => ({
  increase() {
    return prev + 1
  },
}))

function Counter() {
  const counter = useSangteValue(counterState)
  const actions = useSangteActions(counterState)

  return (
    <div>
      <h3>{counter}</h3>
      <button onClick={actions.increase}>+1</button>
    </div>
  )
}

function MultiProviders() {
  return (
    <SangteProvider>
      <Counter />
      <div style={{ padding: 16, border: '1px solid black' }}>
        <SangteProvider>
          <Counter />
        </SangteProvider>
      </div>
    </SangteProvider>
  )
}

export default MultiProviders


================================================
FILE: examples/src/components/Selector.tsx
================================================
import { sangte, useSangteActions, useSangteValue } from 'sangte'

const userState = sangte(
  {
    id: 1,
    username: 'velopert',
    email: 'public.velopert@gmail.com',
    isActive: false,
  },
  (prev) => ({
    toggleActive() {
      prev.isActive = !prev.isActive
    },
  })
)

function Value() {
  const { username, email } = useSangteValue(userState, (state) => ({
    username: state.username,
    email: state.email,
  }))

  return (
    <div>
      <div>{username}</div>
      <div>{email}</div>
    </div>
  )
}

function Toggle() {
  const { toggleActive } = useSangteActions(userState)
  return <button onClick={toggleActive}>Toggle</button>
}

function Active() {
  const isActive = useSangteValue(userState, (state) => state.isActive)
  return <div>{isActive ? 'active' : 'inactive'}</div>
}

function Selector() {
  return (
    <div>
      <Value />
      <Active />
      <Toggle />
    </div>
  )
}

export default Selector


================================================
FILE: examples/src/components/Todos.tsx
================================================
import { sangte, useSangteActions, useSangteValue } from 'sangte'

export const todosState = sangte(
  [
    {
      id: 1,
      text: 'Learn React',
      completed: true,
    },
    {
      id: 2,
      text: 'Learn Sangte',
      completed: false,
    },
    {
      id: 3,
      text: 'Learn React Router',
      completed: false,
    },
  ],
  (prev) => ({
    toggle(id: number) {
      const todo = prev.find((todo) => todo.id === id)
      if (!todo) return
      todo.completed = !todo.completed
    },
  })
)

function Todos() {
  const todos = useSangteValue(todosState)
  const actions = useSangteActions(todosState)

  return (
    <div>
      {todos.map((todo) => (
        <div
          style={{
            textDecoration: todo.completed ? 'line-through' : 'none',
          }}
          onClick={() => {
            actions.toggle(todo.id)
          }}
        >
          {todo.text}
        </div>
      ))}
    </div>
  )
}

export default Todos


================================================
FILE: examples/src/components/VisibleToggle.tsx
================================================
import React, { useState } from 'react'

function VisibleToggle({ children }: { children: React.ReactNode }) {
  const [visible, setVisible] = useState(false)
  return (
    <div>
      <button onClick={() => setVisible(!visible)}>{visible ? 'Hide' : 'Show'}</button>
      {visible ? children : null}
    </div>
  )
}

export default VisibleToggle


================================================
FILE: examples/src/index.css
================================================
:root {
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;

}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}


================================================
FILE: examples/src/main.tsx
================================================
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)


================================================
FILE: examples/src/vite-env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: examples/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}


================================================
FILE: examples/tsconfig.node.json
================================================
{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}


================================================
FILE: examples/vite.config.ts
================================================
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()]
})


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: jest.config.ts
================================================
/*
 * For a detailed explanation regarding each configuration property and type check, visit:
 * https://jestjs.io/docs/configuration
 */

export default {
  setupFilesAfterEnv: ['<rootDir>/src/setupTest.ts'],
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,

  // The directory where Jest should store its cached dependency information
  // cacheDirectory: "/tmp/jest_rs",

  // Automatically clear mock calls, instances, contexts and results before every test
  clearMocks: true,

  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  // collectCoverageFrom: undefined,

  // The directory where Jest should output its coverage files
  coverageDirectory: 'coverage',

  // An array of regexp pattern strings used to skip coverage collection
  // coveragePathIgnorePatterns: [
  //   "/node_modules/"
  // ],

  // Indicates which provider should be used to instrument code for coverage
  coverageProvider: 'v8',

  // A list of reporter names that Jest uses when writing coverage reports
  // coverageReporters: [
  //   "json",
  //   "text",
  //   "lcov",
  //   "clover"
  // ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: undefined,

  // A path to a custom dependency extractor
  // dependencyExtractor: undefined,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // The default configuration for fake timers
  // fakeTimers: {
  //   "enableGlobally": false
  // },

  // Force coverage collection from ignored files using an array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: undefined,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: undefined,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
  // maxWorkers: "50%",

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  // moduleFileExtensions: [
  //   "js",
  //   "mjs",
  //   "cjs",
  //   "jsx",
  //   "ts",
  //   "tsx",
  //   "json",
  //   "node"
  // ],

  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
  // moduleNameMapper: {},

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  // preset: undefined,

  // Run tests from one or more projects
  // projects: undefined,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state before every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: undefined,

  // Automatically restore mock state and implementation before every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  // rootDir: undefined,

  // A list of paths to directories that Jest should use to search for files in
  // roots: [
  //   "<rootDir>"
  // ],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  // setupFiles: [],

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // The number of seconds after which a test is considered as slow and reported as such in the results.
  // slowTestThreshold: 5,

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  testEnvironment: 'jsdom',

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  // testMatch: [
  //   "**/__tests__/**/*.[jt]s?(x)",
  //   "**/?(*.)+(spec|test).[tj]s?(x)"
  // ],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  // testPathIgnorePatterns: [
  //   "/node_modules/"
  // ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: undefined,

  // This option allows use of a custom test runner
  // testRunner: "jest-circus/runner",

  // A map from regular expressions to paths to transformers
  // transform: undefined,

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  // transformIgnorePatterns: [
  //   "/node_modules/",
  //   "\\.pnp\\.[^\\/]+$"
  // ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: undefined,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  // watchman: true,
}


================================================
FILE: package.json
================================================
{
  "name": "sangte",
  "description": "Sangte is a fancy React state management library.",
  "private": false,
  "version": "0.1.26",
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "typings": "dist/index.d.ts",
  "scripts": {
    "build": "rollup -c",
    "test": "jest"
  },
  "dependencies": {
    "immer": "^9.0.15",
    "use-sync-external-store": "^1.2.0"
  },
  "devDependencies": {
    "@babel/plugin-transform-react-jsx": "^7.18.10",
    "@babel/plugin-transform-typescript": "^7.18.12",
    "@babel/preset-env": "^7.18.10",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.3.0",
    "@testing-library/react-hooks": "^8.0.1",
    "@testing-library/user-event": "^14.4.3",
    "@types/react": "^18.0.0",
    "@types/react-dom": "^18.0.0",
    "@types/use-sync-external-store": "^0.0.3",
    "esbuild": "^0.15.5",
    "jest": "^29.0.1",
    "jest-environment-jsdom": "^29.0.1",
    "jsdom": "^20.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "rollup": "^2.75.7",
    "rollup-plugin-dts": "^4.2.2",
    "rollup-plugin-esbuild": "^4.9.1",
    "ts-node": "^10.9.1",
    "typescript": "^4.6.3"
  },
  "peerDependencies": {
    "react": ">=16.8"
  },
  "files": [
    "/dist"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/velopert/sangte"
  },
  "author": "velopert",
  "license": "MIT",
  "homepage": "https://github.com/velopert/sangte"
}


================================================
FILE: rollup.config.js
================================================
import dts from 'rollup-plugin-dts'
import esbuild from 'rollup-plugin-esbuild'

const name = require('./package.json').main.replace(/\.js$/, '')

const bundle = (config) => ({
  ...config,
  input: 'src/index.ts',
  external: (id) => !/^[./]/.test(id),
})

export default [
  bundle({
    plugins: [
      esbuild({
        jsx: 'automatic',
      }),
    ],
    output: [
      {
        file: `${name}.js`,
        format: 'cjs',
        sourcemap: true,
      },
      {
        file: `${name}.mjs`,
        format: 'es',
        sourcemap: true,
      },
    ],
  }),
  bundle({
    plugins: [dts()],
    output: {
      file: `${name}.d.ts`,
      format: 'es',
    },
  }),
]


================================================
FILE: src/contexts/SangteProvider.tsx
================================================
import { createContext, useCallback, useContext, useEffect, useRef } from 'react'
import { Sangte } from '../lib/sangte'
import { SangteInitializer } from '../lib/SangteInitializer'
import { SangteManager } from '../lib/SangteManager'

const SangteContext = createContext<SangteManager | null>(null)

interface SangteProviderProps {
  children: React.ReactNode
  inheritSangtes?: Sangte<any>[]
  initialize?: (params: { set: <T>(sangte: Sangte<T, any>, value: T) => void }) => void
  dehydratedState?: Record<string, any>
}

export function SangteProvider({
  children,
  inheritSangtes,
  initialize,
  dehydratedState,
}: SangteProviderProps) {
  const parent = useContext(SangteContext)
  const managerRef = useRef<SangteManager | null>(null)
  const initialized = useRef(false)

  const initializeProvider = useCallback(() => {
    if (initialized.current) return

    const manager = managerRef.current ?? new SangteManager()
    managerRef.current = manager

    if (parent) {
      manager.parent = parent
      parent.children.add(manager)
    }
    manager.dehydratedState = dehydratedState
    if (inheritSangtes) {
      if (!parent) {
        throw new Error(
          'Cannot inherit sangtes from default SangteManager. Please wrap your app with SangteProvider.'
        )
      }
      manager.inherit(inheritSangtes)
    }
    if (initialize) {
      initialize({
        set: manager.initializer.set.bind(manager.initializer),
      })
      manager.initializer.initialize()
    }
    initialized.current = true
  }, [])

  initializeProvider()

  /**
   * Reinitialization needed because useEffect runs twice on component mount in future React (and development mode)
   */
  useEffect(() => {
    initializeProvider()
    return () => {
      initialized.current = false
      if (parent && managerRef.current) {
        parent.children.delete(managerRef.current)
      }
    }
  }, [initializeProvider])

  return <SangteContext.Provider value={managerRef.current}>{children}</SangteContext.Provider>
}

/**
 * Default manager used when SangteProvider is not used.
 * To
 */
const defaultManager = new SangteManager(true)

export function useSangteManager() {
  const manager = useContext(SangteContext)
  if (!manager) {
    return defaultManager
  }
  return manager
}


================================================
FILE: src/contexts/__tests__/SangteProvider.test.tsx
================================================
import { fireEvent, render, screen } from '@testing-library/react'
import { SangteProvider } from '../SangteProvider'
import { sangte } from '../../lib'
import { useSangte, useSangteValue } from '../../hooks'

describe('SangteProvider', () => {
  it('should render children', () => {
    render(
      <SangteProvider>
        <div>hello</div>
      </SangteProvider>
    )
    screen.getByText('hello')
  })
  it('should initializeState', () => {
    const state = sangte(0)
    function Child() {
      const value = useSangteValue(state)
      return <div data-testid="value">{value}</div>
    }

    render(
      <SangteProvider
        initialize={({ set }) => {
          set(state, 10)
        }}
      >
        <Child />
      </SangteProvider>
    )
    expect(screen.getByTestId('value')).toHaveTextContent('10')
  })

  it('should use closest provider', () => {
    const state = sangte(0)
    function Child({ testId }: { testId: string }) {
      const [value, setState] = useSangte(state)
      return (
        <div>
          <div data-testid={testId}>{value}</div>
          <button data-testid={`button-${testId}`} onClick={() => setState(15)}>
            Click Me
          </button>
        </div>
      )
    }

    render(
      <SangteProvider>
        <Child testId="parent" />
        <SangteProvider
          initialize={({ set }) => {
            set(state, 10)
          }}
        >
          <Child testId="child" />
        </SangteProvider>
      </SangteProvider>
    )

    expect(screen.getByTestId('parent')).toHaveTextContent('0')
    expect(screen.getByTestId('child')).toHaveTextContent('10')
    fireEvent.click(screen.getByTestId('button-child'))
    expect(screen.getByTestId('parent')).toHaveTextContent('0')
    expect(screen.getByTestId('child')).toHaveTextContent('15')
  })

  it('should inherit state', () => {
    const state = sangte(0)
    function Child({ testId }: { testId: string }) {
      const [value, setState] = useSangte(state)
      return (
        <div>
          <div data-testid={testId}>{value}</div>
          <button data-testid={`button-${testId}`} onClick={() => setState(15)}>
            Click Me
          </button>
        </div>
      )
    }

    render(
      <SangteProvider>
        <Child testId="parent" />
        <SangteProvider
          initialize={({ set }) => {
            set(state, 10)
          }}
          inheritSangtes={[state]}
        >
          <Child testId="child" />
        </SangteProvider>
      </SangteProvider>
    )

    expect(screen.getByTestId('parent')).toHaveTextContent('10')
    expect(screen.getByTestId('child')).toHaveTextContent('10')
    fireEvent.click(screen.getByTestId('button-child'))
    expect(screen.getByTestId('parent')).toHaveTextContent('15')
    expect(screen.getByTestId('child')).toHaveTextContent('15')
  })

  it('should dehydrate', () => {
    const counterState = sangte(0, { key: 'counter' })
    const textState = sangte('hello world', { key: 'text' })

    function Child() {
      const counter = useSangteValue(counterState)
      const text = useSangteValue(textState)

      return (
        <div>
          <div data-testid="counter">{counter}</div>
          <div data-testid="text">{text}</div>
        </div>
      )
    }

    render(
      <SangteProvider
        dehydratedState={{
          counter: 10,
          text: 'bye world',
        }}
      >
        <Child />
      </SangteProvider>
    )

    expect(screen.getByTestId('counter')).toHaveTextContent('10')
    expect(screen.getByTestId('text')).toHaveTextContent('bye world')
  })

  it('cannot inherit from default SangteManager', () => {
    const state = sangte(0)
    expect(() => {
      render(
        <SangteProvider inheritSangtes={[state]}>
          <div>hello</div>
        </SangteProvider>
      )
    }).toThrowError()
  })
})


================================================
FILE: src/contexts/index.ts
================================================
export * from './SangteProvider'


================================================
FILE: src/hooks/__tests__/useResetAllSangte.test.tsx
================================================
import { fireEvent, render, screen } from '@testing-library/react'
import { act, renderHook } from '@testing-library/react-hooks'
import { SangteProvider } from '../../contexts'
import { sangte } from '../../lib'
import { useResetAllSangte } from '../useResetAllSangte'
import { useSangte } from '../useSangte'

describe('useResetAllSangte', () => {
  it('resets to initialState', () => {
    const state = sangte(0)
    const anotherState = sangte(1)

    const { result } = renderHook(() => useSangte(state))
    const { result: result2 } = renderHook(() => useSangte(anotherState))
    act(() => {
      result.current[1](5)
      result2.current[1](6)
    })
    expect(result.current[0]).toBe(5)
    expect(result2.current[0]).toBe(6)

    const {
      result: { current: resetAll },
    } = renderHook(() => useResetAllSangte())
    act(() => {
      resetAll()
    })
    expect(result.current[0]).toBe(0)
    expect(result2.current[0]).toBe(1)
  })

  it('should reset children only', () => {
    const state = sangte(0)
    function Child({ testId }: { testId: string }) {
      const [value, setState] = useSangte(state)
      const resetAll = useResetAllSangte()
      return (
        <div>
          <div data-testid={testId}>{value}</div>
          <button data-testid={`button-${testId}`} onClick={() => setState(15)}>
            Click Me
          </button>
          <button onClick={() => resetAll()} data-testid={`button-reset-${testId}`}></button>
        </div>
      )
    }

    render(
      <SangteProvider
        initialize={({ set }) => {
          set(state, 3)
        }}
      >
        <Child testId="parent" />
        <SangteProvider
          initialize={({ set }) => {
            set(state, 10)
          }}
        >
          <Child testId="child" />
          <SangteProvider
            initialize={({ set }) => {
              set(state, 5)
            }}
          >
            <Child testId="grandchild" />
          </SangteProvider>
        </SangteProvider>
      </SangteProvider>
    )

    expect(screen.getByTestId('parent')).toHaveTextContent('3')
    expect(screen.getByTestId('child')).toHaveTextContent('10')
    expect(screen.getByTestId('grandchild')).toHaveTextContent('5')
    fireEvent.click(screen.getByTestId('button-reset-child'))
    expect(screen.getByTestId('parent')).toHaveTextContent('3')
    expect(screen.getByTestId('child')).toHaveTextContent(/^0$/)
    expect(screen.getByTestId('grandchild')).toHaveTextContent(/^0$/)
  })

  it('should reset globally', () => {
    const state = sangte(0)
    function Child({ testId }: { testId: string }) {
      const [value, setState] = useSangte(state)
      const resetAll = useResetAllSangte()
      return (
        <div>
          <div data-testid={testId}>{value}</div>
          <button data-testid={`button-${testId}`} onClick={() => setState(15)}>
            Click Me
          </button>
          <button onClick={() => resetAll(true)} data-testid={`button-reset-${testId}`}></button>
        </div>
      )
    }

    render(
      <SangteProvider
        initialize={({ set }) => {
          set(state, 3)
        }}
      >
        <Child testId="parent" />
        <SangteProvider
          initialize={({ set }) => {
            set(state, 10)
          }}
        >
          <Child testId="child" />
          <SangteProvider
            initialize={({ set }) => {
              set(state, 5)
            }}
          >
            <Child testId="grandchild" />
          </SangteProvider>
        </SangteProvider>
      </SangteProvider>
    )

    expect(screen.getByTestId('parent')).toHaveTextContent('3')
    expect(screen.getByTestId('child')).toHaveTextContent('10')
    expect(screen.getByTestId('grandchild')).toHaveTextContent('5')
    fireEvent.click(screen.getByTestId('button-reset-grandchild'))
    expect(screen.getByTestId('parent')).toHaveTextContent(/^0$/)
    expect(screen.getByTestId('child')).toHaveTextContent(/^0$/)
    expect(screen.getByTestId('grandchild')).toHaveTextContent(/^0$/)
  })
})


================================================
FILE: src/hooks/__tests__/useResetSangte.test.ts
================================================
import { act, renderHook } from '@testing-library/react-hooks'
import { sangte } from '../../lib'
import { useResetSangte } from '../useResetSangte'
import { useSangte } from '../useSangte'

describe('useResetSangte', () => {
  it('resets to initialState', () => {
    const state = sangte(0)
    const { result } = renderHook(() => useSangte(state))
    act(() => {
      result.current[1](5)
    })
    expect(result.current[0]).toBe(5)
    const {
      result: { current: reset },
    } = renderHook(() => useResetSangte(state))
    act(() => {
      reset()
    })
    expect(result.current[0]).toBe(0)
  })
})


================================================
FILE: src/hooks/__tests__/useSangte.test.ts
================================================
import { sangte } from '../../lib'
import { useSangte } from '..'
import { renderHook, act } from '@testing-library/react-hooks'

describe('useSangte', () => {
  it('can be called', () => {
    const counterState = sangte(0)
    const { result } = renderHook(() => useSangte(counterState))
    expect(result.current[0]).toBe(0)
    expect(typeof result.current[1]).toBe('function')
  })
  it('can update the state', () => {
    const counterState = sangte(0)
    const { result } = renderHook(() => useSangte(counterState))

    act(() => {
      result.current[1](10)
    })
    expect(result.current[0]).toBe(10)
    act(() => {
      result.current[1]((prev) => prev + 1)
    })
    expect(result.current[0]).toBe(11)
  })
})


================================================
FILE: src/hooks/__tests__/useSangteActions.test.ts
================================================
import { renderHook, act } from '@testing-library/react-hooks'
import { sangte } from '../../lib'
import { useSangteActions } from '../useSangteActions'
import { useSangteValue } from '../useSangteValue'

describe('useSangteActions', () => {
  it('returns actions', () => {
    const state = sangte(0, (prev) => ({
      increase() {
        return prev + 1
      },
      decrease() {
        return prev - 1
      },
    }))
    const { result } = renderHook(() => useSangteActions(state))
    expect(typeof result.current.decrease).toBe('function')
  })
  it('updates value according to actions', () => {
    const state = sangte(0, (prev) => ({
      increase() {
        return prev + 1
      },
      decrease() {
        return prev - 1
      },
    }))
    const {
      result: { current: actions },
    } = renderHook(() => useSangteActions(state))
    const { result } = renderHook(() => useSangteValue(state))
    expect(result.current).toBe(0)
    act(() => {
      actions.increase()
    })
    expect(result.current).toBe(1)
  })
  it('throws error when action not defined', () => {
    const state = sangte(0)
    expect(() => {
      const {
        result: { current: actions },
        // @ts-ignore
      } = renderHook(() => useSangteActions(state))
    }).toThrowError()
  })
})


================================================
FILE: src/hooks/__tests__/useSangteCallback.test.ts
================================================
import { act, renderHook } from '@testing-library/react-hooks'
import { sangte } from '../../lib'
import { useSangteCallback } from '../useSangteCallback'
import { useSangteValue } from '../useSangteValue'

describe('useSangteCallback', () => {
  it('get value', () => {
    const state = sangte(5)

    let value
    const { result } = renderHook(() =>
      useSangteCallback(({ get }) => {
        value = get(state)
      }, [])
    )
    act(() => {
      result.current()
    })
    expect(value).toBe(5)
  })

  it('set value', () => {
    const state = sangte(0)

    const { result: sangteValue } = renderHook(() => useSangteValue(state))
    expect(sangteValue.current).toBe(0)

    const { result } = renderHook(() =>
      useSangteCallback(({ set }) => {
        set(state, 100)
      }, [])
    )
    act(() => {
      result.current()
    })
    expect(sangteValue.current).toBe(100)
  })

  it('update value with action', () => {
    const state = sangte(0, (prev) => ({
      increase() {
        return prev + 1
      },
    }))

    const { result: sangteValue } = renderHook(() => useSangteValue(state))
    expect(sangteValue.current).toBe(0)

    const { result } = renderHook(() =>
      useSangteCallback(({ actions }) => {
        const { increase } = actions(state)
        increase()
      }, [])
    )
    act(() => {
      result.current()
    })
    expect(sangteValue.current).toBe(1)
  })

  it('throws error when action not defined ', () => {
    const state = sangte(0)

    const { result } = renderHook(() =>
      useSangteCallback(({ actions }) => {
        // @ts-ignore
        actions(state)
      }, [])
    )

    expect(() => {
      act(() => {
        result.current()
      })
    }).toThrowError()
  })
})


================================================
FILE: src/hooks/__tests__/useSangteStore.test.ts
================================================
import { renderHook } from '@testing-library/react-hooks'
import { sangte } from '../../lib'
import { useSangteStore } from '../useSangteStore'

describe('useSangteStore', () => {
  it('returns store', () => {
    const state = sangte(0)
    const { result } = renderHook(() => useSangteStore(state))
    expect(result.current).toHaveProperty('getState')
  })
})


================================================
FILE: src/hooks/__tests__/useSangteValue.test.ts
================================================
import { renderHook } from '@testing-library/react-hooks'
import { sangte } from '../../lib'
import { useSangteValue } from '../useSangteValue'

describe('useSangteValue', () => {
  it('returns value', () => {
    const state = sangte(0)
    const { result } = renderHook(() => useSangteValue(state))
    expect(result.current).toBe(0)
  })

  it('selects value', () => {
    const state = sangte({ count: 0 })
    const { result } = renderHook(() => useSangteValue(state, (state) => state.count))
    expect(result.current).toBe(0)
  })

  it('selects memoized value', () => {
    const numbersState = sangte([0, 1, 2, 3, 4, 5])
    const filteredNumbersState = sangte((get) => get(numbersState).filter((number) => number > 2))
    const { result } = renderHook(() => useSangteValue(filteredNumbersState))
    expect(result.current).toEqual([3, 4, 5])
  })

  it('selects multiple fields', () => {
    const state = sangte({ count: 0, name: 'foo' })
    const { result } = renderHook(() =>
      useSangteValue(state, (state) => ({
        count: state.count,
        name: state.name,
      }))
    )
    expect(result.current).toEqual({ count: 0, name: 'foo' })
  })
})


================================================
FILE: src/hooks/__tests__/useSetSangte.test.ts
================================================
import { act, renderHook } from '@testing-library/react-hooks'
import { sangte } from '../../lib'
import { useSangteValue } from '../useSangteValue'
import { useSetSangte } from '../useSetSangte'

describe('useSetSangte', () => {
  it('updates value', () => {
    const state = sangte(0)
    const { result } = renderHook(() => useSangteValue(state))
    expect(result.current).toBe(0)
    const {
      result: { current },
    } = renderHook(() => useSetSangte(state))
    act(() => {
      current(5)
    })
    expect(result.current).toBe(5)
  })
})


================================================
FILE: src/hooks/index.ts
================================================
export * from './useResetSangte'
export * from './useSangte'
export * from './useSangteActions'
export * from './useSangteStore'
export * from './useSangteValue'
export * from './useSetSangte'
export * from './useSangteCallback'
export * from './useResetAllSangte'


================================================
FILE: src/hooks/useResetAllSangte.ts
================================================
import { useCallback } from 'react'
import { useSangteManager } from '../contexts'

/**
 * Resets every sangte registered to the current SangteProvider.
 */
export function useResetAllSangte() {
  const sangteManager = useSangteManager()
  return useCallback((global?: boolean) => sangteManager.reset(global), [sangteManager])
}


================================================
FILE: src/hooks/useResetSangte.ts
================================================
import { Sangte } from '../lib/sangte'
import { useSangteStore } from './useSangteStore'

export function useResetSangte<T>(sangte: Sangte<T>) {
  const store = useSangteStore(sangte)
  return store.reset
}


================================================
FILE: src/hooks/useSangte.ts
================================================
import { useSyncExternalStore } from 'use-sync-external-store/shim'
import { Sangte } from '../lib/sangte'
import { useSangteStore } from './useSangteStore'

export function useSangte<T>(sangte: Sangte<T>) {
  const store = useSangteStore(sangte)
  const state = useSyncExternalStore(store.subscribe, store.getState, store.getState)
  return [state, store.setState] as const
}


================================================
FILE: src/hooks/useSangteActions.ts
================================================
import { ActionRecord, Sangte } from '../lib/sangte'
import { useSangteStore } from './useSangteStore'

export function useSangteActions<T, A extends ActionRecord<T>>(sangte: Sangte<T, A>) {
  const store = useSangteStore(sangte)

  if (!store.actions) {
    throw new Error('This sangte does not have createActions')
  }

  return store.actions
}


================================================
FILE: src/hooks/useSangteCallback.ts
================================================
import { DependencyList, useCallback } from 'react'
import { useSangteManager } from '../contexts/SangteProvider'
import { ActionRecord, Sangte } from '../lib/sangte'

interface SanteCallbackParams {
  get: <T>(sangte: Sangte<T>) => T
  set: <T>(sangte: Sangte<T>, value: T) => void
  actions: <T, A extends ActionRecord<T>>(sangte: Sangte<T, A>) => A
}

export function useSangteCallback(
  callback: (params: SanteCallbackParams) => void,
  deps: DependencyList
) {
  const sangteManager = useSangteManager()

  return useCallback(() => {
    callback({
      get: (sangte) => sangteManager.get(sangte).getState(),
      set: (sangte, value) => sangteManager.get(sangte).setState(value),
      actions: <T, A extends ActionRecord<T>>(sangte: Sangte<T, A>) => {
        const actions = sangteManager.get(sangte).actions
        if (!actions) throw new Error('This sangte does not have createActions')

        return actions
      },
    })
  }, [...deps, sangteManager])
}


================================================
FILE: src/hooks/useSangteStore.ts
================================================
import { useSangteManager } from '../contexts/SangteProvider'
import { Sangte } from '../lib/sangte'

export function useSangteStore<T, A>(sangte: Sangte<T, A>) {
  const sangteManager = useSangteManager()
  const store = sangteManager.get(sangte)
  return store
}


================================================
FILE: src/hooks/useSangteValue.ts
================================================
import { useSyncExternalStore } from 'use-sync-external-store/shim'
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'
import { Sangte } from '../lib/sangte'
import { shallowEqual } from '../lib/shallowEqual'
import { useSangteStore } from './useSangteStore'

export function useSangteValue<T>(sangte: Sangte<T>): T
export function useSangteValue<T, S>(sangte: Sangte<T>, selector: (state: T) => S): S

export function useSangteValue<T, S>(
  sangte: Sangte<T>,
  selector?: (state: T) => S,
  compare?: (a: S, b: S) => boolean
) {
  const store = useSangteStore(sangte)
  if (!selector) {
    const state = useSyncExternalStore(store.subscribe, store.getState, store.getState)
    return state
  }
  const state = useSyncExternalStoreWithSelector(
    store.subscribe,
    store.getState,
    store.getState,
    selector,
    compare ?? shallowEqual
  )
  return state
}


================================================
FILE: src/hooks/useSetSangte.ts
================================================
import { Sangte } from '../lib/sangte'
import { useSangteStore } from './useSangteStore'

export function useSetSangte<T>(sangte: Sangte<T>) {
  const store = useSangteStore(sangte)
  return store.setState
}


================================================
FILE: src/index.ts
================================================
export * from './contexts'
export * from './hooks'
export * from './lib'


================================================
FILE: src/lib/SangteInitializer.ts
================================================
import { Sangte } from './sangte'
import { SangteManager } from './SangteManager'

export class SangteInitializer {
  private sangteInitialStateMap = new Map<Sangte<any, any>, any>()

  constructor(private manager: SangteManager) {}

  public set<T>(sangte: Sangte<T, any>, initialState: T) {
    this.sangteInitialStateMap.set(sangte, initialState)
  }

  public initialize() {
    this.sangteInitialStateMap.forEach((initialState, sangte) => {
      const instance = this.manager.get(sangte)
      if (sangte.config.isResangte) return
      instance.setState(initialState)
    })
  }
}


================================================
FILE: src/lib/SangteManager.ts
================================================
import { Sangte, SangteInstance } from './sangte'
import { SangteInitializer } from './SangteInitializer'

export class SangteManager {
  private instanceMap = new Map<Sangte<any, any>, SangteInstance<any, any>>()
  public initializer: SangteInitializer = new SangteInitializer(this)
  public children = new Set<SangteManager>()

  constructor(public isDefault: boolean = false) {}
  public get<T, A>(sangte: Sangte<T, A>): SangteInstance<T, A> {
    const manager = sangte.config.global ? this.getRootSangteManager() : this
    const instance = manager.instanceMap.get(sangte)

    if (instance) {
      return instance
    }
    const newInstance = sangte(this)
    if (sangte.config.key && this.dehydratedState && !sangte.config.isResangte) {
      const selected = this.dehydratedState[sangte.config.key]
      if (selected) {
        newInstance.setState(selected)
      }
    }

    manager.instanceMap.set(sangte, newInstance)
    return newInstance
  }

  public parent: SangteManager | null = null
  public dehydratedState?: Record<string, any> | null

  public getRootSangteManager(): SangteManager {
    let manager: SangteManager | undefined = this
    while (manager.parent) {
      manager = manager.parent
    }
    return manager
  }

  public inherit(sangtes: Sangte<any>[]) {
    const parent = this.parent
    if (!parent) return
    sangtes.forEach((sangte) => {
      const sangteInstance = parent.get(sangte)
      this.instanceMap.set(sangte, sangteInstance)
    })
  }

  /** resets all sangte registered to this SangteManager */
  public reset(global: boolean = false) {
    if (global) {
      this.getRootSangteManager().reset()
      return
    }
    Array.from(this.instanceMap.entries()).forEach(([sangte, instance]) => {
      if (!sangte.config.isResangte) {
        instance.reset()
      }
    })
    this.children.forEach((child) => child.reset())
  }
}


================================================
FILE: src/lib/__tests__/SangteManager.test.ts
================================================
import { sangte } from '../sangte'
import { SangteManager } from '../SangteManager'

describe('SangteManager', () => {
  it('creates instance', () => {
    const manager = new SangteManager()
    expect(manager).toBeInstanceOf(SangteManager)
  })
  it('gets root manager', () => {
    const grandParent = new SangteManager()
    const parent = new SangteManager()
    parent.parent = grandParent
    const child = new SangteManager()
    child.parent = parent
    expect(child.getRootSangteManager()).toBe(grandParent)
  })
  it('resets all sangte', () => {
    const counterState = sangte(0)
    const textState = sangte('')
    const manager = new SangteManager()
    manager.get(counterState).setState(1)
    manager.get(textState).setState('hello')
    expect(manager.get(counterState).getState()).toBe(1)
    expect(manager.get(textState).getState()).toBe('hello')
    manager.reset()
    expect(manager.get(counterState).getState()).toBe(0)
    expect(manager.get(textState).getState()).toBe('')
  })
})


================================================
FILE: src/lib/__tests__/sangte.test.ts
================================================
import { sangte } from '../sangte'
import { SangteManager } from '../SangteManager'

describe('sangte', () => {
  it('can be created', () => {
    const create = sangte({ count: 0 })
    const store = create()
    expect(store.getState().count).toBe(0)
  })

  it('can be updated with setState', () => {
    const create = sangte({ count: 0 })
    const store = create()
    store.setState({ count: 1 })
    expect(store.getState().count).toBe(1)
    store.setState((prev) => ({ count: prev.count + 5 }))
    expect(store.getState().count).toBe(6)
  })

  it('calls subscriptions when updated', () => {
    const create = sangte({ count: 0 })
    const store = create()
    const callback = jest.fn()
    const callback2 = jest.fn()
    store.subscribe(callback)
    store.subscribe(callback2)
    store.setState({ count: 1 })
    expect(callback).toBeCalled()
    expect(callback2).toBeCalled()
  })

  it('unsubcribes properly', () => {
    const create = sangte({ count: 0 })
    const store = create()
    const callback = jest.fn()
    const unsubcribe = store.subscribe(callback)
    store.setState({ count: 1 })
    expect(callback).toBeCalled()
    unsubcribe()
    callback.mockReset()
    store.setState({ count: 2 })
    expect(callback).not.toBeCalled()
  })

  it('calls actions properly', () => {
    const create = sangte({ count: 0 }, (prev) => ({
      increase() {
        prev.count += 1
      },
      decreaseBy(amount: number) {
        return {
          ...prev,
          count: prev.count - amount,
        }
      },
    }))
    const store = create()
    const callback = jest.fn()

    store.subscribe(callback)

    expect(store.getState().count).toBe(0)
    store.actions?.increase()
    expect(store.getState().count).toBe(1)
    store.actions?.decreaseBy(5)
    expect(store.getState().count).toBe(-4)

    expect(callback).toBeCalledTimes(2)
  })

  it('can be reset', () => {
    const create = sangte({ count: 0 })
    const store = create()
    store.setState({ count: 1 })
    expect(store.getState().count).toBe(1)
    store.reset()
    expect(store.getState().count).toBe(0)
  })

  it('should keep immutability', () => {
    const create = sangte({ count: 0 }, (prev) => ({
      increase() {
        prev.count += 1
      },
    }))
    const store = create()
    const prev = store.getState()
    store.actions?.increase()
    const next = store.getState()
    expect(prev).not.toBe(next)
  })

  it('should have config', () => {
    const create = sangte(
      { count: 0 },
      {
        global: true,
        key: 'counter',
      }
    )
    expect(create.config).toEqual({
      global: true,
      key: 'counter',
    })
  })
})

describe('resangte', () => {
  it('can be created', () => {
    const state = sangte([1, 2, 3, 4, 5])
    const manager = new SangteManager()
    manager.get(state)
    const selectedState = sangte((get) => get(state).filter((number) => number > 2))
    const store = manager.get(selectedState)
    expect(store.getState()).toEqual([3, 4, 5])
  })
  it('properly updates when dependencies change', () => {
    const state = sangte([1, 2, 3, 4, 5])
    const manager = new SangteManager()
    manager.get(state)
    const selectedState = sangte((get) => get(state).filter((number) => number > 2))
    const store = manager.get(selectedState)
    const callback = jest.fn()
    store.subscribe(callback)
    manager.get(state).setState([1, 2, 3, 4, 5, 6, 7])
    expect(callback).toBeCalledTimes(1)
    expect(store.getState()).toEqual([3, 4, 5, 6, 7])
  })
  it('properly handles unmount & remount', () => {
    const state = sangte([1, 2, 3, 4, 5])
    const manager = new SangteManager()
    manager.get(state)
    const selectedState = sangte((get) => get(state).filter((number) => number > 2))
    const store = manager.get(selectedState)
    const callback = jest.fn()
    let unsubscribe = store.subscribe(callback)
    unsubscribe()
    manager.get(state).setState([1, 2, 3, 4, 5, 6, 7])
    expect(callback).not.toBeCalled()
    // still gets updated because getState calls selector when unmounted
    expect(store.getState()).toEqual([3, 4, 5, 6, 7])
  })
  it('warns when calling setState or reset', () => {
    const state = sangte([1, 2, 3, 4, 5])
    const manager = new SangteManager()
    manager.get(state)
    const selectedState = sangte((get) => get(state).filter((number) => number > 2))
    const store = manager.get(selectedState)
    const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation()
    store.reset()
    store.setState([1, 2, 3])
    expect(consoleWarnMock).toBeCalledTimes(2)
    consoleWarnMock.mockRestore()
  })
  it('throws error when manager is not used', () => {
    const state = sangte([1, 2, 3, 4, 5])
    const selectedState = sangte((get) => get(state).filter((number) => number > 2))
    expect(() => {
      const store = selectedState()
    }).toThrowError()
  })
})


================================================
FILE: src/lib/__tests__/shallowEqual.test.ts
================================================
import { shallowEqual } from '../shallowEqual'

describe('shallowEqual', () => {
  it('returns true if comparing same object', () => {
    const obj = { a: 1, b: 2 }
    expect(shallowEqual(obj, obj)).toBe(true)
  })
  it('returns true if they are shallowly equal', () => {
    const obj1 = { a: 1, b: 2 }
    const obj2 = { a: 1, b: 2 }
    expect(shallowEqual(obj1, obj2)).toBe(true)
  })
  it('returns false if they are shallowly different', () => {
    const obj1 = { a: 1, b: 2 }
    const obj2 = { a: 1, b: 3 }
    expect(shallowEqual(obj1, obj2)).toBe(false)
  })
  it('returns true when arrays are shallowly equal', () => {
    const arr1 = [1, 2]
    const arr2 = [1, 2]
    expect(shallowEqual(arr1, arr2)).toBe(true)
  })
  it('returns false when arrays are shallowly different', () => {
    const arr1 = [1, 2]
    const arr2 = [1, 5]
    expect(shallowEqual(arr1, arr2)).toBe(false)
  })
})


================================================
FILE: src/lib/index.ts
================================================
export * from './sangte'
export * from './SangteInitializer'
export * from './SangteManager'


================================================
FILE: src/lib/sangte.ts
================================================
import produce, { Draft, isDraftable } from 'immer'
import { SangteManager } from './SangteManager'

type Fn = () => void
type UpdateFn<T> = (state: T) => T
export type ActionRecord<T> = Record<string, (...params: any[]) => T | Draft<T> | void>
type Action<T, A> = A extends ActionRecord<T> ? A : never
export type Actions<T, A> = (prevState: Draft<T> | T) => Action<T, A>
export interface SangteConfig {
  key?: string
  global?: boolean
  isResangte?: boolean
}

export type Getter = {
  <T>(sangte: Sangte<T>): T
}

export type SangteInstance<T, A> = {
  initialState: T
  getState: () => T
  setState: (update: UpdateFn<T> | T) => void
  subscribe: (callback: Fn) => Fn
  actions: Action<T, A> | null
  reset: () => void
}
export type Sangte<T, A = any> = {
  (manager?: SangteManager): SangteInstance<T, A>
  config: SangteConfig
}

export type UnwrapSangteValue<T> = T extends Sangte<infer U> ? U : never
export type UnwrapSangteAction<T> = T extends Sangte<any, infer U> ? U : never

type Selector<T> = (get: Getter) => T
function isSelector<T>(fn: any): fn is Selector<T> {
  return typeof fn === 'function'
}

function isUpdateFn<T>(value: any): value is UpdateFn<T> {
  return typeof value === 'function'
}

function createSangte<T, A>(initialState: T, createActions?: Actions<T, A>): SangteInstance<T, A> {
  let state = initialState
  const callbacks = new Set<Fn>()

  function getState() {
    return state
  }

  function setState(update: UpdateFn<T> | T) {
    if (isUpdateFn<T>(update)) {
      state = update(state)
    } else {
      state = update
    }
    callbacks.forEach((cb) => cb())
  }

  function subscribe(callback: Fn): Fn {
    callbacks.add(callback)
    return () => callbacks.delete(callback)
  }

  function reset() {
    setState(initialState)
  }

  const actions = (() => {
    if (!createActions) return null
    type ActionKey = keyof Action<T, A>
    const record = createActions(initialState)
    const keys = Object.keys(record) as ActionKey[]
    keys.forEach((key) => {
      record[key] = ((...params: any[]) => {
        setState((prevState) => {
          if (!isDraftable(prevState)) {
            const action = createActions(prevState)[key]
            const next = action(...params)
            return next as any
          }
          const produced = produce(prevState, (draft) => {
            const action = createActions(draft)[key]
            const result = action(...params)
            if (result !== undefined) {
              return result as Draft<T>
            }
          })
          return produced
        })
      }) as Action<T, A>[ActionKey]
    })
    return record
  })()

  return {
    initialState,
    getState,
    setState,
    subscribe,
    actions,
    reset,
  }
}

export function sangte<T>(selector: (get: Getter) => T): Sangte<T>
export function sangte<T>(initialState: T): Sangte<T>
export function sangte<T, A>(initialState: T, config?: SangteConfig): Sangte<T, A>
export function sangte<T, A>(
  initialState: T,
  actions: Actions<T, A>,
  config?: SangteConfig
): Sangte<T, A>
export function sangte<T, A>(
  selectorOrInitialState: T | ((get: Getter) => T),
  actionsOrConfig?: Actions<T, A> | SangteConfig,
  config?: SangteConfig
) {
  if (isSelector(selectorOrInitialState)) {
    return resangte(selectorOrInitialState)
  }

  const hasActions = typeof actionsOrConfig === 'function'
  const sangte = function () {
    if (hasActions) {
      return createSangte(selectorOrInitialState, actionsOrConfig)
    }
    return createSangte(selectorOrInitialState)
  }
  if (hasActions) {
    sangte.config = config || {}
  } else {
    sangte.config = actionsOrConfig || {}
  }

  return sangte
}

function createResangte<T>(
  selector: (getter: Getter) => T,
  sangteManager: SangteManager
): SangteInstance<T, any> {
  let unmounted = false
  const sangteDeps = new Set<Sangte<any, any>>()
  const subscriptions = new Set<() => void>()
  const getter: Getter = (sangte) => {
    if (!sangteDeps.has(sangte)) {
      sangteDeps.add(sangte)
    }
    return sangteManager.get(sangte).getState()
  }
  let state = selector(getter)
  const callbacks = new Set<Fn>()
  const getState = () => {
    if (unmounted) {
      unmounted = false
      state = selector(getter)
    }
    return state
  }

  const update = () => {
    state = selector(getter)
    callbacks.forEach((cb) => cb())
  }

  const subscribe = (callback: Fn) => {
    if (callbacks.size === 0) {
      sangteDeps.forEach((sangte) => {
        const unsubscribe = sangteManager.get(sangte).subscribe(update)
        subscriptions.add(unsubscribe)
      })
    }
    callbacks.add(callback)
    return () => {
      callbacks.delete(callback)
      if (callbacks.size === 0) {
        subscriptions.forEach((unsubscribe) => unsubscribe())
        subscriptions.clear()
        unmounted = true
      }
    }
  }

  const setState = () => {
    console.warn('setState is not supported in resangte')
  }

  const reset = () => {
    console.warn('reset is not supported in resangte')
  }

  return {
    initialState: state,
    actions: null,
    getState,
    subscribe,
    setState,
    reset,
  }
}

export function resangte<T>(selector: (getter: Getter) => T): Sangte<T> {
  const resangte = function (sangteManager?: SangteManager) {
    if (!sangteManager) {
      throw new Error('Cannot create resangte without a manager')
    }
    return createResangte(selector, sangteManager)
  }
  resangte.config = {
    isResangte: true,
  }

  return resangte
}


================================================
FILE: src/lib/shallowEqual.ts
================================================
export function shallowEqual<T extends Record<any, any>>(a: T, b: T) {
  if (a === b) return true
  if (!(a instanceof Object) || !(b instanceof Object)) return false

  const keys = Object.keys(a) as (keyof T)[]
  const length = keys.length

  for (let i = 0; i < length; i++) if (!(keys[i] in b)) return false

  for (let i = 0; i < length; i++) if (a[keys[i]] !== b[keys[i]]) return false

  return length === Object.keys(b).length
}


================================================
FILE: src/setupTest.ts
================================================
import '@testing-library/jest-dom/extend-expect'


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}
Download .txt
gitextract_d21tq2rb/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .prettierrc
├── LICENSE.md
├── README-ko.md
├── README.md
├── babel.config.js
├── examples/
│   ├── .gitignore
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── App.css
│   │   ├── App.tsx
│   │   ├── components/
│   │   │   ├── Counter.tsx
│   │   │   ├── GlobalState.tsx
│   │   │   ├── Hydrate.tsx
│   │   │   ├── Inherit.tsx
│   │   │   ├── Initialize.tsx
│   │   │   ├── MemoizedSelector.tsx
│   │   │   ├── MemoryLeakTester.tsx
│   │   │   ├── MultiProviders.tsx
│   │   │   ├── Selector.tsx
│   │   │   ├── Todos.tsx
│   │   │   └── VisibleToggle.tsx
│   │   ├── index.css
│   │   ├── main.tsx
│   │   └── vite-env.d.ts
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
├── index.html
├── jest.config.ts
├── package.json
├── rollup.config.js
├── src/
│   ├── contexts/
│   │   ├── SangteProvider.tsx
│   │   ├── __tests__/
│   │   │   └── SangteProvider.test.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── __tests__/
│   │   │   ├── useResetAllSangte.test.tsx
│   │   │   ├── useResetSangte.test.ts
│   │   │   ├── useSangte.test.ts
│   │   │   ├── useSangteActions.test.ts
│   │   │   ├── useSangteCallback.test.ts
│   │   │   ├── useSangteStore.test.ts
│   │   │   ├── useSangteValue.test.ts
│   │   │   └── useSetSangte.test.ts
│   │   ├── index.ts
│   │   ├── useResetAllSangte.ts
│   │   ├── useResetSangte.ts
│   │   ├── useSangte.ts
│   │   ├── useSangteActions.ts
│   │   ├── useSangteCallback.ts
│   │   ├── useSangteStore.ts
│   │   ├── useSangteValue.ts
│   │   └── useSetSangte.ts
│   ├── index.ts
│   ├── lib/
│   │   ├── SangteInitializer.ts
│   │   ├── SangteManager.ts
│   │   ├── __tests__/
│   │   │   ├── SangteManager.test.ts
│   │   │   ├── sangte.test.ts
│   │   │   └── shallowEqual.test.ts
│   │   ├── index.ts
│   │   ├── sangte.ts
│   │   └── shallowEqual.ts
│   └── setupTest.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (85 symbols across 30 files)

FILE: examples/src/App.tsx
  function App (line 14) | function App() {

FILE: examples/src/components/Counter.tsx
  method increase (line 4) | increase() {
  method decrease (line 7) | decrease(amount: number) {
  function Counter (line 12) | function Counter() {

FILE: examples/src/components/GlobalState.tsx
  method increase (line 8) | increase() {
  function Counter (line 17) | function Counter() {
  function GlobalState (line 29) | function GlobalState() {

FILE: examples/src/components/Hydrate.tsx
  function Values (line 6) | function Values() {
  function Hydrate (line 22) | function Hydrate() {

FILE: examples/src/components/Inherit.tsx
  method increase (line 6) | increase() {
  function Counter (line 11) | function Counter() {
  function Inherit (line 23) | function Inherit() {

FILE: examples/src/components/Initialize.tsx
  function Text (line 6) | function Text() {
  function Initialize (line 11) | function Initialize() {

FILE: examples/src/components/MemoizedSelector.tsx
  function MemoizedSelector (line 10) | function MemoizedSelector() {

FILE: examples/src/components/MemoryLeakTester.tsx
  method work (line 7) | work(id: number) {
  function Work (line 17) | function Work({ onHide }: { onHide(): void }) {
  function Wrapper (line 41) | function Wrapper() {
  function MemoryLeakTester (line 62) | function MemoryLeakTester() {

FILE: examples/src/components/MultiProviders.tsx
  method increase (line 6) | increase() {
  function Counter (line 11) | function Counter() {
  function MultiProviders (line 23) | function MultiProviders() {

FILE: examples/src/components/Selector.tsx
  method toggleActive (line 11) | toggleActive() {
  function Value (line 17) | function Value() {
  function Toggle (line 31) | function Toggle() {
  function Active (line 36) | function Active() {
  function Selector (line 41) | function Selector() {

FILE: examples/src/components/Todos.tsx
  method toggle (line 22) | toggle(id: number) {
  function Todos (line 30) | function Todos() {

FILE: examples/src/components/VisibleToggle.tsx
  function VisibleToggle (line 3) | function VisibleToggle({ children }: { children: React.ReactNode }) {

FILE: src/contexts/SangteProvider.tsx
  type SangteProviderProps (line 8) | interface SangteProviderProps {
  function SangteProvider (line 15) | function SangteProvider({
  function useSangteManager (line 77) | function useSangteManager() {

FILE: src/contexts/__tests__/SangteProvider.test.tsx
  function Child (line 17) | function Child() {
  function Child (line 36) | function Child({ testId }: { testId: string }) {
  function Child (line 70) | function Child({ testId }: { testId: string }) {
  function Child (line 107) | function Child() {

FILE: src/hooks/__tests__/useResetAllSangte.test.tsx
  function Child (line 34) | function Child({ testId }: { testId: string }) {
  function Child (line 83) | function Child({ testId }: { testId: string }) {

FILE: src/hooks/__tests__/useSangteActions.test.ts
  method increase (line 9) | increase() {
  method decrease (line 12) | decrease() {
  method increase (line 21) | increase() {
  method decrease (line 24) | decrease() {

FILE: src/hooks/__tests__/useSangteCallback.test.ts
  method increase (line 41) | increase() {

FILE: src/hooks/useResetAllSangte.ts
  function useResetAllSangte (line 7) | function useResetAllSangte() {

FILE: src/hooks/useResetSangte.ts
  function useResetSangte (line 4) | function useResetSangte<T>(sangte: Sangte<T>) {

FILE: src/hooks/useSangte.ts
  function useSangte (line 5) | function useSangte<T>(sangte: Sangte<T>) {

FILE: src/hooks/useSangteActions.ts
  function useSangteActions (line 4) | function useSangteActions<T, A extends ActionRecord<T>>(sangte: Sangte<T...

FILE: src/hooks/useSangteCallback.ts
  type SanteCallbackParams (line 5) | interface SanteCallbackParams {
  function useSangteCallback (line 11) | function useSangteCallback(

FILE: src/hooks/useSangteStore.ts
  function useSangteStore (line 4) | function useSangteStore<T, A>(sangte: Sangte<T, A>) {

FILE: src/hooks/useSangteValue.ts
  function useSangteValue (line 10) | function useSangteValue<T, S>(

FILE: src/hooks/useSetSangte.ts
  function useSetSangte (line 4) | function useSetSangte<T>(sangte: Sangte<T>) {

FILE: src/lib/SangteInitializer.ts
  class SangteInitializer (line 4) | class SangteInitializer {
    method constructor (line 7) | constructor(private manager: SangteManager) {}
    method set (line 9) | public set<T>(sangte: Sangte<T, any>, initialState: T) {
    method initialize (line 13) | public initialize() {

FILE: src/lib/SangteManager.ts
  class SangteManager (line 4) | class SangteManager {
    method constructor (line 9) | constructor(public isDefault: boolean = false) {}
    method get (line 10) | public get<T, A>(sangte: Sangte<T, A>): SangteInstance<T, A> {
    method getRootSangteManager (line 32) | public getRootSangteManager(): SangteManager {
    method inherit (line 40) | public inherit(sangtes: Sangte<any>[]) {
    method reset (line 50) | public reset(global: boolean = false) {

FILE: src/lib/__tests__/sangte.test.ts
  method increase (line 47) | increase() {
  method decreaseBy (line 50) | decreaseBy(amount: number) {
  method increase (line 82) | increase() {

FILE: src/lib/sangte.ts
  type Fn (line 4) | type Fn = () => void
  type UpdateFn (line 5) | type UpdateFn<T> = (state: T) => T
  type ActionRecord (line 6) | type ActionRecord<T> = Record<string, (...params: any[]) => T | Draft<T>...
  type Action (line 7) | type Action<T, A> = A extends ActionRecord<T> ? A : never
  type Actions (line 8) | type Actions<T, A> = (prevState: Draft<T> | T) => Action<T, A>
  type SangteConfig (line 9) | interface SangteConfig {
  type Getter (line 15) | type Getter = {
  type SangteInstance (line 19) | type SangteInstance<T, A> = {
  type Sangte (line 27) | type Sangte<T, A = any> = {
  type UnwrapSangteValue (line 32) | type UnwrapSangteValue<T> = T extends Sangte<infer U> ? U : never
  type UnwrapSangteAction (line 33) | type UnwrapSangteAction<T> = T extends Sangte<any, infer U> ? U : never
  type Selector (line 35) | type Selector<T> = (get: Getter) => T
  function isSelector (line 36) | function isSelector<T>(fn: any): fn is Selector<T> {
  function isUpdateFn (line 40) | function isUpdateFn<T>(value: any): value is UpdateFn<T> {
  function createSangte (line 44) | function createSangte<T, A>(initialState: T, createActions?: Actions<T, ...
  function sangte (line 115) | function sangte<T, A>(
  function createResangte (line 140) | function createResangte<T>(
  function resangte (line 204) | function resangte<T>(selector: (getter: Getter) => T): Sangte<T> {

FILE: src/lib/shallowEqual.ts
  function shallowEqual (line 1) | function shallowEqual<T extends Record<any, any>>(a: T, b: T) {
Condensed preview — 64 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (86K chars).
[
  {
    "path": ".github/workflows/test.yml",
    "chars": 452,
    "preview": "name: Test\non: [push, pull_request]\njobs:\n  run:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        us"
  },
  {
    "path": ".gitignore",
    "chars": 263,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": ".prettierrc",
    "chars": 64,
    "preview": "{\n  \"singleQuote\": true,\n  \"semi\": false,\n  \"printWidth\": 100\n}\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1083,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2022-present velopert\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "README-ko.md",
    "chars": 7036,
    "preview": "# sangte\n\n[![](https://img.shields.io/npm/v/sangte?style=flat-square)](https://www.npmjs.com/package/sangte)\n[![](https:"
  },
  {
    "path": "README.md",
    "chars": 8074,
    "preview": "# sangte\n\n[![](https://img.shields.io/npm/v/sangte?style=flat-square)](https://www.npmjs.com/package/sangte)\n[![](https:"
  },
  {
    "path": "babel.config.js",
    "chars": 633,
    "preview": "module.exports = (api, targets) => {\n  // https://babeljs.io/docs/en/config-files#config-function-api\n  const isTestEnv "
  },
  {
    "path": "examples/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "examples/index.html",
    "chars": 366,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "examples/package.json",
    "chars": 476,
    "preview": "{\n  \"name\": \"vite-project\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite"
  },
  {
    "path": "examples/src/App.css",
    "chars": 192,
    "preview": ".samples {\n  display: flex;\n  flex-wrap: wrap;\n}\n\n.samples > div {\n  display: flex;\n  align-items:center;\n  justify-cont"
  },
  {
    "path": "examples/src/App.tsx",
    "chars": 1825,
    "preview": "import './App.css'\nimport Counter from './components/Counter'\nimport Initialize from './components/Initialize'\nimport Hy"
  },
  {
    "path": "examples/src/components/Counter.tsx",
    "chars": 640,
    "preview": "import { sangte, useResetSangte, useSangteActions, useSangteValue } from 'sangte'\n\nconst counterState = sangte(0, (prev)"
  },
  {
    "path": "examples/src/components/GlobalState.tsx",
    "chars": 794,
    "preview": "import { SangteProvider, useSangteActions } from 'sangte'\nimport { useSangteValue } from 'sangte'\nimport { sangte } from"
  },
  {
    "path": "examples/src/components/Hydrate.tsx",
    "chars": 636,
    "preview": "import { sangte, SangteProvider, useSangteValue } from 'sangte'\n\nconst numberState = sangte(0, { key: 'number' })\nconst "
  },
  {
    "path": "examples/src/components/Inherit.tsx",
    "chars": 759,
    "preview": "import { SangteProvider, useSangteActions } from 'sangte'\nimport { useSangteValue } from 'sangte'\nimport { sangte } from"
  },
  {
    "path": "examples/src/components/Initialize.tsx",
    "chars": 434,
    "preview": "import React from 'react'\nimport { sangte, SangteProvider, useSangteValue } from 'sangte'\n\nconst textState = sangte('Def"
  },
  {
    "path": "examples/src/components/MemoizedSelector.tsx",
    "chars": 868,
    "preview": "import { useState } from 'react'\nimport { resangte, sangte, useSangteActions, useSangteValue } from 'sangte'\nimport { to"
  },
  {
    "path": "examples/src/components/MemoryLeakTester.tsx",
    "chars": 1541,
    "preview": "import { useEffect, useRef, useState } from 'react'\nimport { resangte, sangte, SangteProvider, useSangteActions, useSang"
  },
  {
    "path": "examples/src/components/MultiProviders.tsx",
    "chars": 741,
    "preview": "import { SangteProvider, useSangteActions } from 'sangte'\nimport { useSangteValue } from 'sangte'\nimport { sangte } from"
  },
  {
    "path": "examples/src/components/Selector.tsx",
    "chars": 949,
    "preview": "import { sangte, useSangteActions, useSangteValue } from 'sangte'\n\nconst userState = sangte(\n  {\n    id: 1,\n    username"
  },
  {
    "path": "examples/src/components/Todos.tsx",
    "chars": 968,
    "preview": "import { sangte, useSangteActions, useSangteValue } from 'sangte'\n\nexport const todosState = sangte(\n  [\n    {\n      id:"
  },
  {
    "path": "examples/src/components/VisibleToggle.tsx",
    "chars": 349,
    "preview": "import React, { useState } from 'react'\n\nfunction VisibleToggle({ children }: { children: React.ReactNode }) {\n  const ["
  },
  {
    "path": "examples/src/index.css",
    "chars": 1141,
    "preview": ":root {\n  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  font-weigh"
  },
  {
    "path": "examples/src/main.tsx",
    "chars": 245,
    "preview": "import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM"
  },
  {
    "path": "examples/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "examples/tsconfig.json",
    "chars": 559,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\","
  },
  {
    "path": "examples/tsconfig.node.json",
    "chars": 184,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSynthe"
  },
  {
    "path": "examples/vite.config.ts",
    "chars": 162,
    "preview": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\n// https://vitejs.dev/config/\nexport defau"
  },
  {
    "path": "index.html",
    "chars": 364,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "jest.config.ts",
    "chars": 6496,
    "preview": "/*\n * For a detailed explanation regarding each configuration property and type check, visit:\n * https://jestjs.io/docs/"
  },
  {
    "path": "package.json",
    "chars": 1431,
    "preview": "{\n  \"name\": \"sangte\",\n  \"description\": \"Sangte is a fancy React state management library.\",\n  \"private\": false,\n  \"versi"
  },
  {
    "path": "rollup.config.js",
    "chars": 683,
    "preview": "import dts from 'rollup-plugin-dts'\nimport esbuild from 'rollup-plugin-esbuild'\n\nconst name = require('./package.json')."
  },
  {
    "path": "src/contexts/SangteProvider.tsx",
    "chars": 2290,
    "preview": "import { createContext, useCallback, useContext, useEffect, useRef } from 'react'\nimport { Sangte } from '../lib/sangte'"
  },
  {
    "path": "src/contexts/__tests__/SangteProvider.test.tsx",
    "chars": 3863,
    "preview": "import { fireEvent, render, screen } from '@testing-library/react'\nimport { SangteProvider } from '../SangteProvider'\nim"
  },
  {
    "path": "src/contexts/index.ts",
    "chars": 33,
    "preview": "export * from './SangteProvider'\n"
  },
  {
    "path": "src/hooks/__tests__/useResetAllSangte.test.tsx",
    "chars": 4053,
    "preview": "import { fireEvent, render, screen } from '@testing-library/react'\nimport { act, renderHook } from '@testing-library/rea"
  },
  {
    "path": "src/hooks/__tests__/useResetSangte.test.ts",
    "chars": 616,
    "preview": "import { act, renderHook } from '@testing-library/react-hooks'\nimport { sangte } from '../../lib'\nimport { useResetSangt"
  },
  {
    "path": "src/hooks/__tests__/useSangte.test.ts",
    "chars": 729,
    "preview": "import { sangte } from '../../lib'\nimport { useSangte } from '..'\nimport { renderHook, act } from '@testing-library/reac"
  },
  {
    "path": "src/hooks/__tests__/useSangteActions.test.ts",
    "chars": 1301,
    "preview": "import { renderHook, act } from '@testing-library/react-hooks'\nimport { sangte } from '../../lib'\nimport { useSangteActi"
  },
  {
    "path": "src/hooks/__tests__/useSangteCallback.test.ts",
    "chars": 1754,
    "preview": "import { act, renderHook } from '@testing-library/react-hooks'\nimport { sangte } from '../../lib'\nimport { useSangteCall"
  },
  {
    "path": "src/hooks/__tests__/useSangteStore.test.ts",
    "chars": 363,
    "preview": "import { renderHook } from '@testing-library/react-hooks'\nimport { sangte } from '../../lib'\nimport { useSangteStore } f"
  },
  {
    "path": "src/hooks/__tests__/useSangteValue.test.ts",
    "chars": 1173,
    "preview": "import { renderHook } from '@testing-library/react-hooks'\nimport { sangte } from '../../lib'\nimport { useSangteValue } f"
  },
  {
    "path": "src/hooks/__tests__/useSetSangte.test.ts",
    "chars": 554,
    "preview": "import { act, renderHook } from '@testing-library/react-hooks'\nimport { sangte } from '../../lib'\nimport { useSangteValu"
  },
  {
    "path": "src/hooks/index.ts",
    "chars": 265,
    "preview": "export * from './useResetSangte'\nexport * from './useSangte'\nexport * from './useSangteActions'\nexport * from './useSang"
  },
  {
    "path": "src/hooks/useResetAllSangte.ts",
    "chars": 329,
    "preview": "import { useCallback } from 'react'\nimport { useSangteManager } from '../contexts'\n\n/**\n * Resets every sangte registere"
  },
  {
    "path": "src/hooks/useResetSangte.ts",
    "chars": 207,
    "preview": "import { Sangte } from '../lib/sangte'\nimport { useSangteStore } from './useSangteStore'\n\nexport function useResetSangte"
  },
  {
    "path": "src/hooks/useSangte.ts",
    "chars": 377,
    "preview": "import { useSyncExternalStore } from 'use-sync-external-store/shim'\nimport { Sangte } from '../lib/sangte'\nimport { useS"
  },
  {
    "path": "src/hooks/useSangteActions.ts",
    "chars": 348,
    "preview": "import { ActionRecord, Sangte } from '../lib/sangte'\nimport { useSangteStore } from './useSangteStore'\n\nexport function "
  },
  {
    "path": "src/hooks/useSangteCallback.ts",
    "chars": 975,
    "preview": "import { DependencyList, useCallback } from 'react'\nimport { useSangteManager } from '../contexts/SangteProvider'\nimport"
  },
  {
    "path": "src/hooks/useSangteStore.ts",
    "chars": 265,
    "preview": "import { useSangteManager } from '../contexts/SangteProvider'\nimport { Sangte } from '../lib/sangte'\n\nexport function us"
  },
  {
    "path": "src/hooks/useSangteValue.ts",
    "chars": 911,
    "preview": "import { useSyncExternalStore } from 'use-sync-external-store/shim'\nimport { useSyncExternalStoreWithSelector } from 'us"
  },
  {
    "path": "src/hooks/useSetSangte.ts",
    "chars": 208,
    "preview": "import { Sangte } from '../lib/sangte'\nimport { useSangteStore } from './useSangteStore'\n\nexport function useSetSangte<T"
  },
  {
    "path": "src/index.ts",
    "chars": 73,
    "preview": "export * from './contexts'\nexport * from './hooks'\nexport * from './lib'\n"
  },
  {
    "path": "src/lib/SangteInitializer.ts",
    "chars": 588,
    "preview": "import { Sangte } from './sangte'\nimport { SangteManager } from './SangteManager'\n\nexport class SangteInitializer {\n  pr"
  },
  {
    "path": "src/lib/SangteManager.ts",
    "chars": 1889,
    "preview": "import { Sangte, SangteInstance } from './sangte'\nimport { SangteInitializer } from './SangteInitializer'\n\nexport class "
  },
  {
    "path": "src/lib/__tests__/SangteManager.test.ts",
    "chars": 1010,
    "preview": "import { sangte } from '../sangte'\nimport { SangteManager } from '../SangteManager'\n\ndescribe('SangteManager', () => {\n "
  },
  {
    "path": "src/lib/__tests__/sangte.test.ts",
    "chars": 4911,
    "preview": "import { sangte } from '../sangte'\nimport { SangteManager } from '../SangteManager'\n\ndescribe('sangte', () => {\n  it('ca"
  },
  {
    "path": "src/lib/__tests__/shallowEqual.test.ts",
    "chars": 904,
    "preview": "import { shallowEqual } from '../shallowEqual'\n\ndescribe('shallowEqual', () => {\n  it('returns true if comparing same ob"
  },
  {
    "path": "src/lib/index.ts",
    "chars": 93,
    "preview": "export * from './sangte'\nexport * from './SangteInitializer'\nexport * from './SangteManager'\n"
  },
  {
    "path": "src/lib/sangte.ts",
    "chars": 5519,
    "preview": "import produce, { Draft, isDraftable } from 'immer'\nimport { SangteManager } from './SangteManager'\n\ntype Fn = () => voi"
  },
  {
    "path": "src/lib/shallowEqual.ts",
    "chars": 437,
    "preview": "export function shallowEqual<T extends Record<any, any>>(a: T, b: T) {\n  if (a === b) return true\n  if (!(a instanceof O"
  },
  {
    "path": "src/setupTest.ts",
    "chars": 49,
    "preview": "import '@testing-library/jest-dom/extend-expect'\n"
  },
  {
    "path": "tsconfig.json",
    "chars": 505,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\","
  }
]

About this extraction

This page contains the full source code of the velopert/sangte GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 64 files (75.5 KB), approximately 23.3k tokens, and a symbol index with 85 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!