{"_id":"@anseotmd555/moocha","name":"@anseotmd555/moocha","dist-tags":{"latest":"0.0.0"},"versions":{"0.0.0":{"name":"@anseotmd555/moocha","version":"0.0.0","private":false,"type":"module","description":"LLM-friendly React state helper library.","main":"dist/moocha.cjs","module":"dist/moocha.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/moocha.js","require":"./dist/moocha.cjs"}},"scripts":{"dev":"vite build --watch","build":"vite build","typecheck":"tsc -p tsconfig.json"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0"},"devDependencies":{"@types/react":"^19.2.7","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.1","typescript":"^5.9.3","vite":"^7.3.1","vite-plugin-dts":"^4.5.4"},"dependencies":{"es-toolkit":"^1.44.0","immer":"^11.1.4","overlay-kit":"^1.8.6","sonner":"^2.0.7","valtio":"^2.3.0"},"gitHead":"cfeec0af13d388b13e5195e7f8194f7416a7aaca","_id":"@anseotmd555/moocha@0.0.0","_nodeVersion":"25.6.1","_npmVersion":"11.9.0","dist":{"integrity":"sha512-USrhG9IlkZpuno+ZFOx3mURMjitO8ARz/1o10HmkUY8qKnKRXiOdr0p/aMNECLX0Rxif3ugcjLXvi279wnfPkA==","shasum":"37dea7e5df55c385addbd105dd1eaf7078b5a846","tarball":"https://registry.npmjs.org/@anseotmd555/moocha/-/moocha-0.0.0.tgz","fileCount":19,"unpackedSize":36071,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD9CGhSGiEkoesGZAObFZRSI67GVmWlTiatTGlat6IKjwIhAM58Zv0sc7O5LJIx073YfWxIPBtw0rh1WX1yLPASYeyh"}]},"_npmUser":{"name":"anseotmd555","email":"tmdeoans@snu.ac.kr"},"directories":{},"maintainers":[{"name":"anseotmd555","email":"tmdeoans@snu.ac.kr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/moocha_0.0.0_1771064920344_0.7966392444283914"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-14T10:28:40.267Z","0.0.0":"2026-02-14T10:28:40.534Z","modified":"2026-02-14T10:28:40.724Z"},"maintainers":[{"name":"anseotmd555","email":"tmdeoans@snu.ac.kr"}],"description":"LLM-friendly React state helper library.","readme":"# Yoshi\n\nReact state management library.\nDefine domain state as plain objects, optimize re-renders with immutable snapshots.\n\n## Folder Structure\n\n```\nstate/\n  todo/\n    types.ts\n    model.ts\n    actions/\n      crud.ts\n      bulk.ts\n      counter.ts\n    index.ts\n```\n\nEach domain gets its own folder with a consistent layout:\n\n- `types.ts` — State and Actions interfaces. Read this file to understand the entire domain.\n- `model.ts` — Factory function returning the initial state.\n- `actions/` — One file per concern. Each file exports a single action factory.\n- `index.ts` — Assembles model + actions with `create()` and re-exports types.\n\n## Setup\n\nWrap your app root with `StateProvider`.\n\n```tsx\nimport { StateProvider } from 'moocha'\n\nfunction App() {\n  return (\n    <StateProvider>\n      <YourApp />\n    </StateProvider>\n  )\n}\n```\n\n## Usage\n\n```tsx\nimport { useTodo } from '@/state/todo'\n\nfunction TodoPage() {\n  const todo = useTodo()\n\n  // read state\n  todo.count\n  todo.todos\n\n  // call actions\n  todo.actions.create({ title: 'New todo' })\n  todo.actions.increment()\n\n  return <div>{todo.count}</div>\n}\n```\n\n### Selectors\n\nPass a selector to pick only what you need from a single call.\n\n```tsx\nconst { count, todos, actions } = useTodo(s => ({\n  count: s.count,\n  todos: s.todos,\n  actions: s.actions,\n}))\n```\n\n## Writing Guide\n\nWrite files in this order: **types → model → actions → index**.\n\n### 1. types.ts\n\nAdd JSDoc comments to actions — they show up in editor hover tooltips and help LLMs understand the domain from this file alone.\n\n```ts\n// state/todo/types.ts\nexport type TodoState = {\n  todos: Todo[]\n  errorMessage: string\n}\n\nexport type TodoActions = {\n  /** Create a new todo and append it to the list */\n  create(title: string): Promise<void>\n  /** Delete a todo by id */\n  delete(id: string): Promise<void>\n  /** Bulk delete todos. Requires admin permission. */\n  deleteMany(ids: string[]): Promise<void>\n}\n```\n\n### 2. model.ts\n\n```ts\n// state/todo/model.ts\nimport { model } from 'moocha'\nimport type { TodoState } from './types'\n\nexport const todoModel = model<TodoState>({\n  todos: [],\n  errorMessage: '',\n})\n```\n\n### 3. actions/\n\n```ts\n// state/todo/actions/crud.ts\nimport { action } from 'moocha'\nimport type { TodoActions } from '../types'\nimport { todoModel } from '../model'\n\nexport const todoCrudActions = action<Pick<TodoActions, 'create' | 'delete'>>(({ inject }) => {\n  const model = inject(todoModel)\n\n  return {\n    async create(title) {\n      const todo = await api.createTodo({ title })\n      model.todos.push(todo)\n    },\n    async delete(id) {\n      model.todos = model.todos.filter(t => t.id !== id)\n      await api.deleteTodo(id)\n    },\n  }\n})\n```\n\nYou can also use an inline class, which opens the door to decorators:\n\n```ts\nexport const todoCrudActions = action<Pick<TodoActions, 'create' | 'delete'>>(({ inject }) => {\n  const model = inject(todoModel)\n\n  return new class {\n    async create(title: string) {\n      const todo = await api.createTodo({ title })\n      model.todos.push(todo)\n    }\n    async delete(id: string) {\n      model.todos = model.todos.filter(t => t.id !== id)\n      await api.deleteTodo(id)\n    }\n  }\n})\n```\n\n### 4. index.ts\n\n```ts\n// state/todo/index.ts\nimport { create } from 'moocha'\nimport type { TodoState, TodoActions } from './types'\nimport { todoModel } from './model'\nimport { todoCrudActions } from './actions/crud'\nimport { todoBulkActions } from './actions/bulk'\n\nexport const useTodo = create<TodoState, TodoActions>(todoModel, {\n  actions: [todoCrudActions, todoBulkActions],\n})\n\nexport type { TodoState, TodoActions } from './types'\n```\n\n## Additional Usage Patterns\n\n### Combine Action Modules\n\nYou can split domain logic into multiple action files and compose them in one hook.\n\n```ts\n// state/todo/index.ts\nimport { create } from 'moocha'\nimport type { TodoState, TodoActions } from './types'\nimport { todoModel } from './model'\nimport { todoCrudActions } from './actions/crud'\nimport { todoBulkActions } from './actions/bulk'\nimport { todoFilterActions } from './actions/filter'\n\nexport const useTodo = create<TodoState, TodoActions>(todoModel, {\n  actions: [todoCrudActions, todoBulkActions, todoFilterActions],\n})\n```\n\n### Inject Other Domain State in an Action\n\n`inject()` lets one domain read/write another domain model in a controlled way.\n\n```ts\n// state/order/actions/create.ts\nimport { action, silent } from 'moocha'\nimport { orderModel } from '../model'\nimport { userModel } from '@/state/user/model'\n\nexport const orderActions = action(({ inject }) => {\n  const order = inject(orderModel)\n  const user = inject(userModel)\n\n  return {\n    async create(input: { productId: string }) {\n      if (!user.auth) return\n      const created = await api.createOrder(input)\n      order.items.push(created)\n    },\n    resetToServer(data) {\n      silent(() => {\n        order.items = data\n      })\n    },\n  }\n})\n```\n\n### Avoid Unnecessary Renders\n\nSelectors are compared with deep equality before React emits updates, so derived objects are safe.\n\n```tsx\nimport { useTodo } from '@/state/todo'\n\nconst todoCount = useTodo(s => s.count)\n\n// Only re-render when `count` changes\nconst { count, actions } = useTodo(s => ({\n  count: s.count,\n  actions: s.actions,\n}))\n```\n\n### Use Multiple Providers for Isolation\n\nYou can wrap only part of your tree with a separate `StateProvider` when you want state to be isolated per subtree (for example in tests, storybook stories, or nested apps).\n\n```tsx\n<StateProvider>\n  <AppShell />\n</StateProvider>\n\n<StateProvider>\n  <EmbeddedWidget />\n</StateProvider>\n```\n\n### SSR / Hydration Helpers\n\nWhen initializing state from server data, use `silent()` to avoid client re-renders while bootstrapping.\n\n```ts\nimport { action, silent } from 'moocha'\n\nexport const todoInitActions = action(({ inject }) => {\n  const model = inject(todoModel)\n\n  return {\n    bootstrap(serverTodos: Todo[]) {\n      silent(() => {\n        model.todos = serverTodos\n      })\n    },\n  }\n})\n```\n\n## Interceptors\n\nInterceptors can be used in two styles in `actions`:\n\n1) Decorator style (class-based actions)\n\n```ts\nimport { action, OnError, OnSuccess, Transaction, Debounce } from 'moocha'\n\nexport const todoActions = action(({ inject }) => {\n  const model = inject(todoModel)\n  return new class {\n    @Debounce(300)\n    @OnError((error) => {\n      sonner.error(error.message ?? '요청 처리 중 오류가 발생했습니다')\n      throw error\n    })\n    @Transaction()\n    @OnSuccess((result) => {\n      console.log('saved', result)\n    })\n    async save(payload: { title: string }) {\n      model.todos.push(await api.saveTodo(payload))\n    }\n  }\n})\n```\n\n`@Transaction` is defined in v2-style API as a placeholder for dynamic model tracking.\nTODO: keep snapshots for auto-detected models during execution, and on error rollback before rethrowing.\n\n2) Function style (pipe)\n\n```ts\nimport { action, onError, onSuccess, transaction, debounce, pipe } from 'moocha'\n\nexport const todoActions = action(({ inject }) => {\n  const model = inject(todoModel)\n  const save = async (payload: { title: string }) => {\n    model.todos.push(await api.saveTodo(payload))\n  }\n\n  return {\n    save: pipe(\n      onError(error => {\n        sonner.error(error.message ?? '요청 처리 중 오류가 발생했습니다')\n        throw error\n      }),\n    onSuccess(result => console.log('saved', result)),\n      transaction(),\n      debounce(300),\n    )(save),\n  }\n})\n```\n```\n","readmeFilename":"README.md","_rev":"1-0499d1a294417850518b8e5f1188c4a8"}