Vue integration
The SDK is framework-agnostic. For Vue 3, wrap it in a composable that manages connection lifecycle and exposes reactive state.
Minimal composable
Section titled “Minimal composable”import { ref, shallowRef, onScopeDispose } from 'vue'import { createClient, type SportEvent, type SportEventId } from 'realtimeodds'
export function useRealtimeOdds(apiKey: string) { const events = shallowRef<ReadonlyMap<SportEventId, SportEvent>>(new Map()) const connected = ref(false)
const client = createClient({ url: 'wss://api.realtimeodds.xyz', apiKey })
const refresh = () => { events.value = new Map(client.snapshot().sportEvents) }
client.on('connected', () => { connected.value = true; refresh() }) client.on('disconnected', () => { connected.value = false }) client.on('sportEvent:added', refresh) client.on('sportEvent:updated', refresh) client.on('sportEvent:removed', refresh)
client.connect().catch(err => console.error('connect failed:', err))
onScopeDispose(() => { void client.disconnect() })
return { events, connected }}Used in a component:
<script setup lang="ts">import { useRealtimeOdds } from './useRealtimeOdds'
const { events, connected } = useRealtimeOdds(import.meta.env.VITE_REALTIMEODDS_API_KEY)</script>
<template> <p v-if="!connected">Connecting…</p> <ul v-else> <li v-for="ev of events.values()" :key="ev.id"> [{{ ev.bookmaker }}] {{ ev.name }} — {{ ev.markets.size }} markets </li> </ul></template>shallowRef vs ref
Section titled “shallowRef vs ref”Use shallowRef for the events map. ref would deep-track every nested entity Vue’s reactivity system encounters, which is expensive for thousands of objects you never mutate. shallowRef only tracks reassignments — exactly what you want when each update produces a fresh Map.
For more granular reactivity (per-row updates), see the per-event pattern below.
Per-event subscription
Section titled “Per-event subscription”For dashboards rendering hundreds of rows, prefer per-row subscriptions to avoid re-rendering the whole list on every odds tick:
import { ref, watch, onScopeDispose } from 'vue'import type { Client, SelectionId } from 'realtimeodds'
export function useSelectionPrice(client: Client, selectionId: SelectionId) { const price = ref<number | undefined>()
const handler = (payload: { selectionId: SelectionId; quote: { price: number } }) => { if (payload.selectionId === selectionId) { price.value = payload.quote.price } }
client.on('odds:changed', handler) onScopeDispose(() => client.off('odds:changed', handler))
return { price }}Sharing the client app-wide
Section titled “Sharing the client app-wide”Provide a single client at the app root and inject it where needed:
import { createApp } from 'vue'import { createClient } from 'realtimeodds'
const client = createClient({ url: 'wss://api.realtimeodds.xyz', apiKey: import.meta.env.VITE_REALTIMEODDS_API_KEY})
const app = createApp(App)app.provide('realtimeOdds', client)
client.connect().then(() => app.mount('#app')).catch(console.error)Components read it via inject:
import { inject } from 'vue'import type { Client } from 'realtimeodds'
const client = inject<Client>('realtimeOdds')!Pinia stores
Section titled “Pinia stores”If you use Pinia, the same pattern applies — initialize the client in an action, store the snapshot in state via shallowRef / markRaw, and update it on events. Avoid letting Pinia deep-track entity instances; mark them raw or pass them through shallowRef.