{"_rev":"3-694a70b25894e648eab449c96bbc2ffa","time":{"created":"2025-11-18T07:54:05.961Z","modified":"2025-11-18T07:54:06.540Z","1.0.0":"2025-11-18T07:43:45.660Z","1.0.1":"2025-11-18T07:54:06.260Z"},"_id":"@alekstar79/reactivity","name":"@alekstar79/reactivity","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.1":{"name":"@alekstar79/reactivity","version":"1.0.1","description":"Production-ready reactive system similar to Vue 3","license":"MIT","author":{"name":"Aleksey Tarasenko","email":"alekstar79@yandex.ru"},"type":"module","private":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./reactivity":{"types":"./dist/reactivity.d.ts","import":"./dist/reactivity.mjs","require":"./dist/reactivity.cjs"},"./package.json":"./package.json"},"main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","scripts":{"dev":"tsc --watch","build":"npm run build:lib && npm run build:demo","build:lib":"node build-lib.js","build:demo":"node build-demo.js","clean":"npm run clean:lib && npm run clean:demo","clean:lib":"rimraf dist","clean:demo":"rimraf demo","type-check":"tsc --noEmit","example:basic":"tsx examples/node-basic.ts","example:advanced":"tsx examples/node-advanced.ts","example:effect-scope":"tsx examples/node-effect-scope.ts","example:all":"npm run example:basic && npm run example:advanced && npm run example:effect-scope","demo:serve":"serve demo -p 3000","demo:dev":"npm run build:demo && npm run demo:serve","test":"cross-env NODE_ENV=test jest","test:watch":"cross-env NODE_ENV=test jest --watch","test:coverage":"cross-env NODE_ENV=test jest --coverage","test:debug":"cross-env NODE_ENV=test node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand","prepublishOnly":"npm run test && npm run build:lib","prepare":"npm run build:lib","release":"npm run test && npm run build:lib && npm publish --access public"},"devDependencies":{"@types/jest":"^30.0.0","@types/node":"^20.0.0","cross-env":"^10.1.0","esbuild":"^0.27.0","jest":"^30.2.0","rimraf":"^5.0.0","serve":"^14.2.5","ts-jest":"^29.4.5","ts-node":"^10.9.2","tsx":"^4.7.0","typescript":"^5.3.0"},"keywords":["reactivity","watch","reactive","effect","computed","ref","typescript","browser","nodejs"],"repository":{"type":"git","url":"git+https://github.com/alekstar79/reactivity.git"},"bugs":{"url":"https://github.com/alekstar79/reactivity/issues"},"homepage":"https://github.com/alekstar79/reactivity#readme","engines":{"node":">=20.0.0"},"_id":"@alekstar79/reactivity@1.0.1","gitHead":"9570c40f0c1da7b159999a93ef7c612b782c6f40","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-QCs/I/iKPb5T0MpTXmSOSrR4hVSgCKOz8sZOmFPmvm8QM8VJ4x7UHK2jhVtntUTn6WZfFs++iW2OoMHj8m7uNg==","shasum":"50a1fb5ba9c9f9b9914433daf4039e85927e5649","tarball":"https://registry.npmjs.org/@alekstar79/reactivity/-/reactivity-1.0.1.tgz","fileCount":30,"unpackedSize":216323,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBITWD1ef7FLZJJeJ6JV30CKefO3z14WB76Ws+Qs+IGcAiB+qpaqQF97+EkGxCqocoFJu7BMKXxbriYfginDs3Yp/w=="}]},"_npmUser":{"name":"alekstar79","email":"alekstar79@yandex.ru"},"directories":{},"maintainers":[{"name":"alekstar79","email":"alekstar79@yandex.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/reactivity_1.0.1_1763452446051_0.07920893264375417"},"_hasShrinkwrap":false}},"maintainers":[{"name":"alekstar79","email":"alekstar79@yandex.ru"}],"description":"Production-ready reactive system similar to Vue 3","homepage":"https://github.com/alekstar79/reactivity#readme","keywords":["reactivity","watch","reactive","effect","computed","ref","typescript","browser","nodejs"],"repository":{"type":"git","url":"git+https://github.com/alekstar79/reactivity.git"},"author":{"name":"Aleksey Tarasenko","email":"alekstar79@yandex.ru"},"bugs":{"url":"https://github.com/alekstar79/reactivity/issues"},"license":"MIT","readme":"# ⚡ Reactivity (Vue3-like reactivity)\n\n[![NPM](https://img.shields.io/npm/v/@alekstar79/reactivity.svg)](https://www.npmjs.com/package/@alekstar79/reactivity)\n[![GitHub repo](https://img.shields.io/badge/github-repo-green.svg?style=flat)](https://github.com/alekstar79/reactivity)\n[![Typescript](https://img.shields.io/badge/TypeScript-Ready-blue?logo=typescript)]()\n[![License](https://img.shields.io/badge/License-MIT-green)]()\n[![Version](https://img.shields.io/badge/Version-1.0.0-orange)]()\n[![Coverage](https://img.shields.io/badge/Coverage-87.7%25-green)]()\n\n![Reactivity](reactivity.jpg)\n\n> Production-ready reactive system inspired by Vue 3. Includes memoization, cycle dependency management, subscription cleanup, effect lifecycle, and complete TypeScript typing.\n\n<!-- TOC -->\n* [⚡ Reactivity (Vue3-like reactivity)](#-reactivity-vue3-like-reactivity)\n  * [🎯 Features](#-features)\n  * [📦 Installation](#-installation)\n  * [🚀 Quick Start](#-quick-start)\n    * [Basic Usage](#basic-usage)\n    * [Using watch with Different Sources](#using-watch-with-different-sources)\n    * [Cleanup Functions](#cleanup-functions)\n  * [📚 API Documentation](#-api-documentation)\n    * [`ref<T>(value: T): Ref<T>`](#reftvalue-t-reft)\n    * [`reactive<T>(target: T): T`](#reactivettarget-t-t)\n    * [`computed<T>(getter: () => T): Ref<T>`](#computedtgetter---t-reft)\n    * [`effect(update: () => void, options?: EffectOptions): () => void`](#effectupdate---void-options-effectoptions---void)\n    * [`watch<T>(source: WatchSource<T>, callback: WatchCallback<T>, options?: WatchOptions<T>): WatchStopHandle`](#watchtsource-watchsourcet-callback-watchcallbackt-options-watchoptionst-watchstophandle)\n    * [`batch<T>(fn: () => T): T`](#batchtfn---t-t)\n    * [Utilities](#utilities)\n  * [💡 Usage Examples](#-usage-examples)\n    * [Example 1: Form with Validation](#example-1-form-with-validation)\n    * [Example 2: Lifecycle Management](#example-2-lifecycle-management)\n    * [Example 3: Reactive Shopping Cart](#example-3-reactive-shopping-cart)\n    * [Example 4: Debounced Search](#example-4-debounced-search)\n  * [🏗️ Architecture](#-architecture)\n  * [🧪 Testing](#-testing)\n  * [📊 Test Coverage](#-test-coverage)\n  * [🔧 Configuration](#-configuration)\n  * [📊 Performance](#-performance)\n  * [🤝 Contributing](#-contributing)\n  * [📖 Additional Resources](#-additional-resources)\n  * [🐛 Known Issues](#-known-issues)\n<!-- TOC -->\n\n## 🎯 Features\n\n- ✅ **Full Reactivity** - automatic dependency tracking\n- ✅ **Advanced watch Function** - like Vue 3 with cleanup, immediate, deep and options\n- ✅ **Memoization** - computed values caching for optimization\n- ✅ **Cycle Management** - automatic detection of circular dependencies\n- ✅ **Batch Updates** - grouping multiple updates for performance\n- ✅ **TypeScript** - full typing of all components\n- ✅ **Lifecycle Management** - proper cleanup of subscriptions and resources\n- ✅ **Debugging** - built-in tools for monitoring and debugging\n- ✅ **Async Support** - correct handling of asynchronous operations\n\n## 📦 Installation\n\n```bash\nnpm install @alekstar79/reactivity\n# or\nyarn add @alekstar79/reactivity\n```\n\n## 🚀 Quick Start\n\n### Basic Usage\n\n```typescript\nimport { ref, reactive, computed, effect, watch } from '@alekstar79/reactivity'\n\n// Create reactive values\nconst count = ref(0)\nconst state = reactive({ name: 'Alice', age: 30 })\n\n// Automatic dependency tracking\neffect(() => {\n  console.log(`Count: ${count.value}`)\n})\n\n// Computed values\nconst doubled = computed(() => count.value * 2)\n\n// Track changes\nwatch(count, (newVal, oldVal) => {\n  console.log(`Count changed from ${oldVal} to ${newVal}`)\n})\n\n// Change values\ncount.value = 5\nstate.name = 'Bob'\n```\n\n### Using watch with Different Sources\n\n```typescript\n// Track ref\nwatch(count, (newVal) => {\n  console.log('Count:', newVal)\n})\n\n// Track function\nwatch(\n  () => state.user.name,\n  (newName) => {\n    console.log('Name:', newName)\n  }\n)\n\n// Track array of sources\nwatch(\n  [count, () => state.name],\n  ([newCount, newName]) => {\n    console.log(`${newName}: ${newCount}`)\n  }\n)\n\n// With options\nwatch(\n  state,\n  (newState) => {\n    console.log('State changed:', newState)\n  },\n  { deep: true, immediate: true, flush: 'post' }\n)\n```\n\n### Cleanup Functions\n\n```typescript\nconst searchQuery = ref('')\n\nlet timeoutId: NodeJS.Timeout | null = null\n\nwatch(\n  searchQuery,\n  (newQuery, oldQuery, cleanup) => {\n    // Clear previous timeout\n    cleanup(() => {\n      if (timeoutId) clearTimeout(timeoutId)\n    })\n\n    // Set new timeout\n    timeoutId = setTimeout(() => {\n      console.log(`Searching for: ${newQuery}`)\n    }, 300)\n  }\n)\n```\n\n## 📚 API Documentation\n\n### `ref<T>(value: T): Ref<T>`\n\nCreates a reactive value for primitive types.\n\n```typescript\nconst count = ref(0)\nconst name = ref('Alice')\n\nconsole.log(count.value) // 0\ncount.value = 1 // Triggers update\n```\n\n### `reactive<T>(target: T): T`\n\nCreates a deeply reactive object using Proxy.\n\n```typescript\nconst state = reactive({\n  user: { name: 'Alice', age: 30 },\n  todos: [{ id: 1, text: 'Learn Vue' }]\n})\n\nstate.user.name = 'Bob' // Reactive\nstate.todos[0].text = 'Learn Vue 3' // Reactive\n```\n\n### `computed<T>(getter: () => T): Ref<T>`\n\nCreates a computed value with automatic caching.\n\n```typescript\nconst a = ref(2)\nconst b = ref(3)\nconst sum = computed(() => a.value + b.value)\n\nconsole.log(sum.value) // 5\na.value = 5\nconsole.log(sum.value) // 8\n```\n\n### `effect(update: () => void, options?: EffectOptions): () => void`\n\nRuns a function, tracking all read reactive properties.\n\n```typescript\nconst count = ref(0)\n\nconst stop = effect(() => {\n  console.log(`Count is: ${count.value}`)\n})\n\ncount.value = 1 // Logs: Count is: 1\nstop() // Stop effect\ncount.value = 2 // Nothing logged\n```\n\n### `watch<T>(source: WatchSource<T>, callback: WatchCallback<T>, options?: WatchOptions<T>): WatchStopHandle`\n\nTracks reactive sources and calls callback on change.\n\n**Parameters**:\n- `source` - what to track (ref, reactive, function, or array)\n- `callback(newVal, oldVal, cleanup)` - function called on change\n- `options` - additional options\n\n**Options**:\n- `immediate?: boolean` - call callback immediately on creation (default `false`)\n- `deep?: boolean` - deep tracking of nested properties (default `false`)\n- `flush?: 'pre' | 'post' | 'sync'` - callback execution time (default `'post'`)\n- `memoize?: boolean` - cache results (default `true`)\n- `scheduler?: (job: () => void) => void` - custom scheduler\n\n**Returns**: function to stop watch\n\n```typescript\nconst count = ref(0)\n\nconst stop = watch(\n  count,\n  (newVal, oldVal, cleanup) => {\n    console.log(`Changed from ${oldVal} to ${newVal}`)\n    \n    cleanup(() => {\n      console.log('Cleaning up...')\n    })\n  },\n  { immediate: true, deep: true }\n)\n\ncount.value = 1\nstop() // Stop watching\n```\n\n### `batch<T>(fn: () => T): T`\n\nGroups multiple updates into a single pass.\n\n```typescript\nconst a = ref(1)\nconst b = ref(2)\n\nlet updateCount = 0\neffect(() => {\n  updateCount++\n})\n\n// Without batch - 2 updates\na.value = 10 // updateCount = 2\nb.value = 20 // updateCount = 3\n\n// With batch - 1 update\nupdateCount = 0\nbatch(() => {\n  a.value = 100\n  b.value = 200\n}) // updateCount = 1\n```\n\n### Utilities\n\n```typescript\n// Check if value is a ref\nisRef(value) // true | false\n\n// Get value from ref or regular value\nunref(refOrValue) // T\n\n// Check if object is reactive\nisReactive(value) // true | false\n\n// Enable debug mode\nenableDebug(true)\n\n// Get effect statistics\nconst stats = getEffectStats()\nconsole.log(stats) // { activeEffects: 2, queuedUpdates: 0 }\n\n// Set configuration\nsetConfig({ enableDebug: true, batchUpdates: false })\n\n// Clear all state (for tests)\nclearReactivityState()\n```\n\n## 💡 Usage Examples\n\n### Example 1: Form with Validation\n\n```typescript\ninterface FormData {\n  email: string\n  password: string\n  confirmPassword: string\n}\n\nconst form = reactive<FormData>({\n  email: '',\n  password: '',\n  confirmPassword: ''\n})\n\nconst errors = reactive<Record<string, string | null>>({})\n\nwatch(\n  () => form.email,\n  (newEmail) => {\n    if (!newEmail.includes('@')) {\n      errors.email = 'Invalid email'\n    } else {\n      errors.email = null\n    }\n  }\n)\n\nwatch(\n  () => form.password,\n  (newPassword) => {\n    if (newPassword.length < 8) {\n      errors.password = 'Password must be at least 8 characters'\n    } else {\n      errors.password = null\n    }\n  }\n)\n\nconst isFormValid = computed(() => {\n  return Object.values(errors).every(e => e === null)\n})\n```\n\n### Example 2: Lifecycle Management\n\n```typescript\nclass UserComponent {\n  private userId = ref(1)\n  private user = ref(null)\n  private stopWatch: (() => void) | null = null\n\n  mount() {\n    // Load user on ID change\n    this.stopWatch = watch(\n      this.userId,\n      async (newId) => {\n        const response = await fetch(`/api/users/${newId}`)\n        this.user.value = await response.json()\n      }\n    )\n  }\n\n  unmount() {\n    // Clean up subscription\n    this.stopWatch?.()\n  }\n\n  setUserId(id: number) {\n    this.userId.value = id\n  }\n}\n```\n\n### Example 3: Reactive Shopping Cart\n\n```typescript\ninterface CartItem {\n  id: number\n  name: string\n  price: number\n  quantity: number\n}\n\nconst cart = reactive<{ items: CartItem[] }>({\n  items: []\n})\n\nconst discount = ref(0)\n\nconst subtotal = computed(() => {\n  return cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0)\n})\n\nconst tax = computed(() => subtotal.value * 0.1)\n\nconst total = computed(() => {\n  const discountAmount = subtotal.value * (discount.value / 100)\n  return subtotal.value - discountAmount + tax.value\n})\n\nwatch(total, (newTotal) => {\n  console.log(`Total: $${newTotal.toFixed(2)}`)\n})\n\n// Usage\ncart.items.push({ id: 1, name: 'Book', price: 20, quantity: 2 })\ndiscount.value = 10 // 10% discount\n```\n\n### Example 4: Debounced Search\n\n```typescript\nconst searchQuery = ref('')\nconst searchResults = ref([])\nlet debounceTimer: NodeJS.Timeout | null = null\n\nwatch(\n  searchQuery,\n  (newQuery, oldQuery, cleanup) => {\n    // Clear previous timeout\n    cleanup(() => {\n      if (debounceTimer) {\n        clearTimeout(debounceTimer)\n      }\n    })\n\n    if (!newQuery) {\n      searchResults.value = []\n      return\n    }\n\n    debounceTimer = setTimeout(async () => {\n      const response = await fetch(`/api/search?q=${newQuery}`)\n      searchResults.value = await response.json()\n    }, 300)\n  }\n)\n```\n\n## 🏗️ Architecture\n\nThe system uses the following key patterns:\n\n1. **Dependency Tracking** - WeakMap for storing dependencies\n2. **Proxy-based reactivity** - Proxy for operation interception\n3. **Effect Stack** - managing nested effects\n4. **Batch Updates** - grouping updates\n5. **Memoization** - caching computations\n6. **Cycle Detection** - detecting circular dependencies\n\nSee [ARCHITECTURE.md](ARCHITECTURE.md) for details\n\n## 🧪 Testing\n\n```typescript\nimport { ref, watch, clearReactivityState } from '@alekstar79/reactivity'\n\ndescribe('watch', () => {\n  afterEach(() => {\n    clearReactivityState()\n  })\n\n  it('should call callback when value changes', () => {\n    const count = ref(0)\n    const callback = jest.fn()\n\n    watch(count, callback)\n    count.value = 1\n\n    expect(callback).toHaveBeenCalledWith(1, 0, expect.any(Function))\n  })\n\n  it('should call cleanup function', () => {\n    const count = ref(0)\n    const cleanup = jest.fn()\n\n    watch(count, (newVal, oldVal, cleanupFn) => {\n      cleanupFn(cleanup)\n    })\n\n    count.value = 1\n    count.value = 2\n\n    expect(cleanup).toHaveBeenCalled()\n  })\n})\n```\n\n## 📊 Test Coverage\n\n| File          | % Stmts | % Branch | % Funcs | % Lines |\n|---------------|---------|----------|---------|---------|\n| All files     | 87.7    | 77.43    | 86.4    | 88.96   |\n| clone.ts      | 100     | 94.73    | 100     | 100     |\n| index.ts      | 100     | 100      | 85.71   | 100     |\n| reactivity.ts | 86.08   | 76.05    | 85.71   | 87.4    |\n\n## 🔧 Configuration\n\n```typescript\nsetConfig({\n  enableDebug: false,              // Debug output\n  enableMemoization: true,         // Enable caching\n  cyclePrevention: true,           // Cycle detection\n  batchUpdates: true,              // Update batching\n  deepReactiveMaxDepth: 10,        // Max depth for reactive\n  trackingDepth: 100               // Max tracking depth\n})\n```\n\n## 📊 Performance\n\n- **Memoization** reduces repeated computations by 90%+\n- **Batch updates** reduce redraws by 80%+\n- **WeakMap** provides automatic memory cleanup\n- Support for reactivity depth up to 10 levels\n\n## 🤝 Contributing\n\nPull requests and issues are welcome!\n\n## 📖 Additional Resources\n\n- [Vue 3 Reactivity API](https://vuejs.org/api/reactivity-core.html)\n- [Proxy and Reflect in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)\n- [WeakMap in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)\n\n## 🐛 Known Issues\n\n- Arrays require using mutation methods for full reactivity\n- Maximum reactivity depth is limited for performance\n- Watch with async operations requires proper cleanup\n\n---\n\n**Version:** 1.0.0  \n**Last Updated:** 2025  \n**Support:** TypeScript 5.0+, Node.js 18+\n","readmeFilename":"README.md"}