@sync0/vue
v0.1.2
Published
Vue 3 adapter for @sync0/core — field() returns a Vue Ref<T>
Maintainers
Readme
@sync0/vue
Vue 3 adapter for @sync0/core.
field() returns a real Vue Ref<T> backed by a customRef — so v-model, computed(), watch(), and watchEffect() all work without any changes. Two composables handle server conflict state the idiomatic Vue way.
Installation
npm install @sync0/vue @sync0/coreRequires Vue ≥ 3.3.
Quick start
<script setup lang="ts">
import { computed } from 'vue';
import {
urlField,
storageField,
sessionField,
cookieField,
useServerField,
} from '@sync0/vue';
// URL search param — survives hard refresh, back/forward
const query = urlField('', 'q');
// localStorage — persists + syncs across tabs
const theme = storageField('light', 'theme');
// sessionStorage — tab-local
const draft = sessionField('', 'draft');
// Cookie — sent with requests, SSR-readable
const locale = cookieField('en', 'locale', { maxAge: 31_536_000 });
// Server — optimistic writes, realtime push, conflict state
const { field: notes, conflict, dismiss } = useServerField('', 'notes');
// computed() works on top of any SyncRef just like a normal ref
const resultCount = computed(() => (query.value.length > 0 ? 42 : 0));
</script>
<template>
<input v-model="query" placeholder="Search…" />
<select v-model="theme">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<div v-if="conflict" class="banner">
Updated elsewhere —
<button @click="dismiss">Dismiss</button>
</div>
<textarea v-model="notes" />
</template>All exports
import {
field, // full strategy object → SyncRef<T>
localField, // in-memory (same as ref())
urlField, // URL search param
storageField, // localStorage
sessionField, // sessionStorage
cookieField, // document.cookie
serverField, // server adapter (bare SyncRef, no conflict wiring)
useServerField, // composable: { field, conflict, dismiss }
useOnConflict, // composable: { conflict, dismiss } for existing field
onConflict, // imperative conflict subscription
configureServerAdapter,
} from '@sync0/vue';Conflict handling — two patterns
Pattern A: combined composable (creates + wires everything in one call)
const { field: notes, conflict, dismiss } = useServerField('', 'notes');Pattern B: separate (useful when the field is defined outside <script setup>)
const notes = serverField('', 'notes'); // SyncRef<string>
const { conflict, dismiss } = useOnConflict(notes); // reactive conflict RefBoth composables clean up their subscription automatically via onUnmounted.
SyncRef<T>
Every field() call returns a SyncRef<T>:
type SyncRef<T> = Ref<T> & { readonly __raw: RawObservable<T> };It is a Ref<T> in every sense — v-model, watch, computed, and .value access all work. __raw exposes the underlying engine for advanced use.
Server adapter setup
import { configureServerAdapter } from '@sync0/vue';
configureServerAdapter(new SupabaseAdapter(supabase));Custom codec
const page = urlField(1, 'page', {
codec: { encode: String, decode: Number },
});Links
- Full documentation & examples
@sync0/core— framework-agnostic engine@sync0/angular— Angular Signal adapter@sync0/react— React hooks adapter
