@meldkit/vue
v0.1.2
Published
Vue bindings for the MeldKit SDK
Readme
@meldkit/vue
Vue bindings for the MeldKit SDK: end-to-end encrypted real-time collaboration, as composables.
npm install @meldkit/vue yjsVue 3. The room is a reactive object, so a template reads meldkit.status
with no .value and nothing to subscribe to.
Connect
<script setup>
import { MeldKitProvider } from '@meldkit/vue';
</script>
<template>
<MeldKitProvider :room-key="KEY" room="doc-1" auth="/api/meldkit-auth">
<Board />
</MeldKitProvider>
</template>MeldKitProvider renders its slot and nothing else — no wrapper element to
style around.
auth points at your own endpoint. It is POSTed { room } and answers
{ token }, minted server-side with your project's secret key — a secret key
can't be in a browser. The provider fetches a token before it connects, and
again before every reconnect, so a session outlives the short-lived token it
started with.
The connection belongs to the provider: it closes when the component unmounts.
Props are read reactively, so a room that changes reconnects — and only if
what the transport is built from actually changed.
Read and write
<script setup>
import { useMeldKit, useSyncedList } from '@meldkit/vue';
const meldkit = useMeldKit();
const todos = useSyncedList('todos');
</script>
<template>
<label v-for="[id, todo] in todos.entries" :key="id">
<input
type="checkbox"
:checked="todo.done"
@change="todos.update(id, { done: !todo.done })"
/>
{{ todo.text }}
</label>
</template>| Helper | What it gives you |
| --------------------- | ------------------------------------------------------------- |
| useMeldKit() | the room: status, error, readOnly, doc, client |
| useSyncedMap(name) | a shared record — entries, get, set, update, remove |
| useSyncedList(name) | the same, ordered, plus push, insert, move |
| useSharedText(name) | a shared text field — see below |
| useOthers() | everyone else's presence, each with clientId folded in |
| useMyPresence() | your own presence, and a merging setter |
They read the connection from provide/inject, so — like inject itself — they
belong at the top of setup(), not inside a handler or a watcher. entries is
reactive: put it straight in the template.
update(id, patch) merges per field, so two people editing different fields
of the same item both win. set(id, value) replaces the whole entry. Use
useSyncedList whenever order is visible — an ordered CRDT is what stops two
concurrent inserts from landing in different orders on different screens.
Presence
<script setup>
const me = useMyPresence();
function onPointerMove(event) {
me.update({ pointer: { x: event.offsetX, y: event.offsetY } });
}
</script>update merges; awareness itself only replaces wholesale, which is the helper
every app was writing by hand. Set initial-presence on the provider and it is
published on connect and after every reconnect — awareness state is ephemeral,
so a client that announces itself once vanishes from everyone else's screen
after the first dropped connection and never comes back.
Shared text
<script setup>
const body = useSharedText('body');
</script>
<template>
<textarea :ref="body.bind"></textarea>
</template>That is the whole binding: no :value, no @input, no v-model. Don't add one
either — two owners of a single value is exactly the fight that loses
keystrokes.
bind is a template ref. Vue calls it with the element on mount and with null
on unmount, which is exactly the pair of events the binding needs, so there is
no directive to register and nothing to clean up by hand.
Two people typing in the same paragraph merge character by character, each local
edit goes back as the smallest change that explains it, and a remote edit moves
your caret with the text instead of dropping it at the end. body.text is the
current value, reactive, for anything that needs to render it.
Typing your shapes once
Augment one interface and every helper is typed:
// meldkit.config.ts — imported once, anywhere in your app
declare module '@meldkit/vue' {
interface MeldKitTypes {
Presence: Presence;
Collections: { todos: Todo };
}
}Then useMyPresence() is your presence and useSyncedList('todos') is a list of
Todo, with the collection name checked against the map — a typo is a compile
error instead of an empty list. Entirely optional; explicit type arguments keep
working either way.
Ready-made UI
@meldkit/vue-ui has the presence bar, the connection indicator, remote
carets and live cursors, against the same presence shape.
Errors
meldkit.error is a MeldKitError with a code. Two worth handling:
key-error— the socket is healthy and the document is unreadable. This is a wrong room key, and it is the one failure a status dot alone will not tell you about.config— your token endpoint answered 501, which is what it should do before it has a secret key. A first-run problem, not an auth failure;<SetupNotice />renders the four steps out of it.
