Skip to content

Composables Reference

RecachedPlugin

ts
app.use(RecachedPlugin, options?: CacheOptions)

Vue plugin that creates the cache and provides it to the entire app via inject. Install it before app.mount.

Options

OptionTypeDescription
connect{ url: string; password?: string }WebSocket URL of the Recached server and optional auth password.
persistencebooleanLoad the IndexedDB WAL on startup; write every mutation through for cross-refresh persistence.
broadcastChannelstringBroadcastChannel name for cross-tab mutation sharing without a server.

Examples

ts
// With server connection
app.use(RecachedPlugin, { connect: { url: 'ws://localhost:6380' } })

// With persistence only (no server)
app.use(RecachedPlugin, { persistence: true })

// With cross-tab sync only
app.use(RecachedPlugin, { broadcastChannel: 'my-app' })

// Full setup
app.use(RecachedPlugin, {
  persistence: true,
  broadcastChannel: 'my-app',
  connect: { url: 'wss://cache.example.com', password: 'secret' },
})

useRecached()

ts
function useRecached(): Cache

Returns the Cache instance provided by RecachedPlugin. Use it to call write and imperative methods: set, setEx, setJSON, del, publish, subscribe, clearPersistence.

Throws if RecachedPlugin has not been installed.

Example

vue
<script setup lang="ts">
import { useRecached } from '@recached/vue'

const cache = useRecached()

function save() {
  cache.setJSON('user:42', { id: 42, name: 'Alice' }, 300)
}
</script>

<template>
  <button @click="save">Save</button>
</template>

Writing from event handlers

Reads (useKey, useKeyJSON) are reactive. Writes are always done imperatively via useRecached():

vue
<script setup lang="ts">
import { useKey, useRecached } from '@recached/vue'

const cache = useRecached()
const count = useKey('count')
const n = computed(() => Number(count.value ?? 0))
</script>

<template>
  <div>
    <p>{{ n }}</p>
    <button @click="cache.set('count', String(n + 1))">+</button>
    <button @click="cache.set('count', String(n - 1))">−</button>
  </div>
</template>

useKey(key)

ts
function useKey(key: string): Ref<string | null>

Reactively reads a string value from the cache. Returns a Ref<string | null>null when the key does not exist or has expired.

The ref updates automatically whenever key is mutated — from any source:

  • A write in the same component or another component in the same tab
  • A write received from the server (WebSocket fan-out)
  • A write from another tab (BroadcastChannel sync)

The onMutation listener is cleaned up automatically via onUnmounted.

Example

vue
<script setup lang="ts">
import { useKey } from '@recached/vue'

const status = useKey('system:status')
</script>

<template>
  <span :class="`badge badge--${status ?? 'unknown'}`">
    {{ status ?? 'unknown' }}
  </span>
</template>

Dynamic keys

Pass any reactive expression's resolved value; the composable reads the current key at call time. To track a dynamic key reactively, use watchEffect with useRecached:

vue
<script setup lang="ts">
import { watchEffect, ref } from 'vue'
import { useRecached } from '@recached/vue'

const props = defineProps<{ userId: string }>()
const cache = useRecached()
const status = ref<string | null>(null)

watchEffect(() => {
  status.value = cache.get(`user:${props.userId}:status`)
  return cache.onMutation(() => {
    status.value = cache.get(`user:${props.userId}:status`)
  })
})
</script>

For a fixed key, useKey is simpler and preferred.


useKeyJSON<T>(key)

ts
function useKeyJSON<T>(key: string): Ref<T | null>

Same as useKey but JSON-parses the stored string. Returns null when the key is missing, expired, or the stored value is not valid JSON.

Example

vue
<script setup lang="ts">
import { useKeyJSON, useRecached } from '@recached/vue'

interface CartItem {
  id: string
  name: string
  qty: number
  price: number
}

const cache = useRecached()
const items = useKeyJSON<CartItem[]>('cart')
const total = computed(() =>
  (items.value ?? []).reduce((sum, item) => sum + item.price * item.qty, 0)
)

function removeItem(id: string) {
  cache.setJSON('cart', (items.value ?? []).filter((i) => i.id !== id))
}
</script>

<template>
  <div>
    <ul>
      <li v-for="item in items ?? []" :key="item.id">
        {{ item.name }} × {{ item.qty }}
        <button @click="removeItem(item.id)">Remove</button>
      </li>
    </ul>
    <p>Total: ${{ total.toFixed(2) }}</p>
  </div>
</template>

Writing JSON

Use cache.setJSON<T>() from useRecached() to write structured data:

ts
cache.setJSON<CartItem[]>('cart', updatedItems)          // no expiry
cache.setJSON<CartItem[]>('cart', updatedItems, 3600)    // expires in 1 hour

usePubSub(channel, handler)

ts
function usePubSub(channel: string, handler: (msg: string) => void): void

Subscribe to a server pub/sub channel for the lifetime of the component. Sends SUBSCRIBE on setup and UNSUBSCRIBE on onUnmounted. The handler is called with each incoming message string.

Example

vue
<script setup lang="ts">
import { ref } from 'vue'
import { usePubSub } from '@recached/vue'

const notifications = ref<string[]>([])

usePubSub('alerts', (msg) => {
  notifications.value.push(msg)
})
</script>

<template>
  <ul>
    <li v-for="(n, i) in notifications" :key="i">{{ n }}</li>
  </ul>
</template>

Publishing

Use cache.publish() from useRecached() to send messages:

ts
const cache = useRecached()
cache.publish('alerts', 'Deployment complete')

All subscribers — Vue components, React components, server-side RESP clients — receive the message.


Reactivity model

Every mutation — regardless of source — fires the internal notification bus, which updates all useKey and useKeyJSON refs:

cache.set('key', value)           ← local write

  ├─ WASM store updated
  ├─ notify all useKey('key') subscribers → Ref.value updated → Vue re-renders
  ├─ WebSocket send → server → fan-out to other clients
  │     └─ WASM store updated → notify → Ref.value updated → Vue re-renders
  └─ BroadcastChannel post → other tabs
        └─ WASM store updated → notify → Ref.value updated → Vue re-renders

The onMutation listener registered inside each composable is removed by onUnmounted, so components that unmount leave no dangling listeners.


TypeScript

All composables are fully typed. useKeyJSON<T> infers the generic from the type argument:

ts
const user = useKeyJSON<User>('user:42')   // Ref<User | null>
const tags = useKeyJSON<string[]>('tags')  // Ref<string[] | null>

useRecached() returns the full Cache type with all methods typed.

Released under the MIT License.