vue-model-source
v0.1.1
Published
> Control and detect the source of `v-model` updates in Vue 3.
Maintainers
Readme
vue-model-source
Control and detect the source of
v-modelupdates in Vue 3.
Vue's reactivity system does not provide information about where a change originated (internal vs external).
vue-model-source gives you a deterministic way to classify and control those updates.
✨ Features
- 🔍 Detect if a
v-modelchange is internal or external - 🛡️ Optional strict mode to prevent uncontrolled writes
- ⚡ Zero dependencies (Vue only)
- 🧠 Works with
defineModelor anyRef - 🔄 Handles chained updates safely (no race conditions)
📦 Installation
npm install vue-model-source🚀 Usage
Basic example
import { useModelWithSource } from 'vue-model-source'
const model = defineModel<string>()
const {
model: value,
set,
watchInternal,
watchExternal,
} = useModelWithSource(model)
watchInternal((v) => {
console.log('internal change:', v)
})
watchExternal((v) => {
console.log('external change:', v)
})Internal updates
set('hello')External updates
<Child v-model="parentValue" />🛡️ Strict mode (recommended)
Prevent direct writes:
const { model } = useModelWithSource(model, {
strict: true,
})model.value = 'test' // ❌ throws error🧩 API
useModelWithSource(ref, options)
Options
| Option | Type | Description |
| ------------ | --------------------------- | ---------------------------- |
| strict | boolean | Prevent direct writes |
| devWarn | boolean | Warn on direct writes in dev |
| deep | boolean | Deep watch |
| flush | 'pre' \| 'post' \| 'sync' | Watch flush timing |
| onInternal | (newVal, oldVal) => void | Internal hook |
| onExternal | (newVal, oldVal) => void | External hook |
Returns
| Property | Description |
| ---------------- | -------------------------- |
| model | Proxy-wrapped ref |
| set(value) | Internal update |
| patch(partial) | Partial internal update |
| watchInternal | Listen to internal changes |
| watchExternal | Listen to external changes |
⚠️ Important limitation
This library cannot intercept deep mutations:
model.value.name = 'John' // ❌ not tracked as internalRecommended approach
Use immutable updates:
set({ ...model.value, name: 'John' })🧠 Why this exists
Vue's reactivity system is not source-aware.
When a value changes, Vue does not track:
- who changed it
- where it came from
This library introduces a controlled layer to make updates predictable and explicit.
🧪 Example with defineModel
const model = defineModel<{ name: string }>()
const { model: user, patch } = useModelWithSource(model)
patch({ name: 'Alice' }) // internal📚 Comparison
| Feature | Vue | vue-model-source | | --------------------- | --- | ---------------- | | Detect change origin | ❌ | ✅ | | Prevent direct writes | ❌ | ✅ | | Internal vs external | ❌ | ✅ |
🛠️ Roadmap
- [ ] Devtools integration
- [ ] Deep proxy support
- [ ] Transaction batching
- [ ] Debug timeline
📄 License
MIT
