Use a Realtime provider to enable instant updates in your application. This provider handles subscriptions to events (like record creation, updates, or deletions) and publishing custom events.
The Realtime interface defines the contract:
interface Realtime {
// Subscribe to updates
subscribe: (props: SubscribeProps) => UnsubscribeFn
// Unsubscribe from updates (optional)
unsubscribe?: (props: UnsubscribeProps) => void
// Publish a custom event (optional)
publish?: (event: RealtimeEvent) => void
}
There are two main ways to handle realtime events: Auto and Manual.
| Mode | How It Works | When to Use |
|---|---|---|
| Auto | Framework automatically invalidates queries and refetches data | Most common - reduces boilerplate code |
| Manual | You handle events in callbacks with full control | Complex logic - conditional updates, multiple actions |
In auto mode, the framework automatically invalidates related queries when an event is received. For example, if a "post" is updated on the server, any active useGetList or useGetOne queries for that post will be refetched automatically.
Scenario: Real-time collaborative editor where multiple users edit the same document.
<script setup lang="ts">
import { useGetOne } from '@ginjou/vue'
// Auto mode (default)
const { data: post } = useGetOne({
resource: 'posts',
id: '123',
})
// When another user updates this post on server:
// 1. Event is received
// 2. Query cache is invalidated
// 3. useGetOne automatically refetches
// 4. UI updates with latest data
</script>
<template>
<div>
<h1>{{ post?.title }}</h1>
<p>Last updated: {{ post?.updatedAt }}</p>
</div>
</template>
In manual mode, you handle the event yourself in a callback. This provides total control over how the UI responds to changes.
Scenario: Live chat application where you need to insert new messages at the top of the list, not refetch all messages.
<script setup lang="ts">
import { useSubscribe } from '@ginjou/vue'
import { ref } from 'vue'
const messages = ref([])
useSubscribe({
channel: 'chat/room-123',
actions: ['created'],
mode: 'manual',
callback: (event) => {
// Control exactly what happens
if (event.action === 'created') {
// Insert new message at the top
messages.value.unshift(event.payload)
}
},
})
</script>
<template>
<div class="messages">
<div v-for="msg in messages" :key="msg.id">
{{ msg.content }}
</div>
</div>
</template>
Realtime events automatically integrate with data queries in auto mode:
useGetOne: Refetches when a single record is updateduseGetList: Refetches when records are created, updated, or deleteduseGetMany: Refetches when any of the specified records changeuseGetInfiniteList: Refetches affected pages when records changeIn manual mode, you explicitly handle the event and decide whether to refetch, update local state, or perform other actions.
Use useSubscribe to listen for events on a specific channel or resource.
<script setup lang="ts">
import { useSubscribe } from '@ginjou/vue'
useSubscribe({
channel: 'resources/posts',
actions: ['created', 'updated', 'deleted'],
callback: (event) => {
console.log('Realtime event received:', event)
// event.action: 'created' | 'updated' | 'deleted'
// event.payload: The changed record data
},
})
</script>
Example: Subscribe to Specific Resource Changes
<script setup lang="ts">
import { useSubscribe } from '@ginjou/vue'
import { ref } from 'vue'
const status = ref('idle')
useSubscribe({
channel: 'resources/orders',
actions: ['updated'],
mode: 'manual',
callback: (event) => {
if (event.payload.status === 'completed') {
status.value = 'Order completed!'
}
},
})
</script>
<template>
<p>{{ status }}</p>
</template>
Use usePublish to send custom events to other users or components. Publishing is automatically called by mutation operations in auto mode, but you can also publish custom events manually.
| Operation | Auto-Publishes | Event Type |
|---|---|---|
useCreateOne / useCreateMany | Yes | 'created' |
useUpdateOne / useUpdateMany | Yes | 'updated' |
useDeleteOne / useDeleteMany | Yes | 'deleted' |
Manual Publishing:
<script setup lang="ts">
import { usePublish } from '@ginjou/vue'
const publish = usePublish()
function handleInteraction() {
// Publish custom event
publish({
channel: 'collaboration/doc-123',
type: 'cursor_move',
payload: { userId: '456', x: 100, y: 200 },
})
}
</script>
<template>
<button @click="handleInteraction">
Move cursor
</button>
</template>
Example: Collaborative Presence
<script setup lang="ts">
import { usePublish, useSubscribe } from '@ginjou/vue'
import { onUnmounted, ref } from 'vue'
const userId = '123'
const activeCursors = ref([])
const publish = usePublish()
// Publish cursor position every 100ms
let publishInterval = setInterval(() => {
publish({
channel: 'collaboration/doc-456',
type: 'cursor_position',
payload: { userId, x: 100, y: 200 },
})
}, 100)
// Subscribe to other users' cursors
useSubscribe({
channel: 'collaboration/doc-456',
actions: [],
mode: 'manual',
callback: (event) => {
if (event.type === 'cursor_position' && event.payload.userId !== userId) {
activeCursors.value = [
...activeCursors.value.filter(c => c.userId !== event.payload.userId),
event.payload,
]
}
},
})
onUnmounted(() => {
publishInterval
&& clearInterval(publishInterval)
publishInterval = undefined
})
</script>
<template>
<div class="cursors">
<div
v-for="cursor in activeCursors" :key="cursor.userId"
:style="{ left: `${cursor.x}px`, top: `${cursor.y}px` }"
>
👆
</div>
</div>
</template>