Skip to content

Instantly share code, notes, and snippets.

@daffl
Created December 2, 2025 06:30
Show Gist options
  • Select an option

  • Save daffl/d9ff6bcd7da5dc3b95b3e3f4563a289a to your computer and use it in GitHub Desktop.

Select an option

Save daffl/d9ff6bcd7da5dc3b95b3e3f4563a289a to your computer and use it in GitHub Desktop.
A local-first Feathers app
<script lang="ts">
import { feathers } from 'feathers'
import { Repo, type AnyDocumentId } from '@automerge/automerge-repo'
import { BrowserWebSocketClientAdapter } from '@automerge/automerge-repo-network-websocket'
import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb'
import type { SyncServiceDocument } from '@kalisio/feathers-automerge'
import { AutomergeService } from '@kalisio/feathers-automerge'
type Message = {
id: string
text: string
createdAt: number
}
// Initialize Automerge Repo and point to public testing sync server
const repo = new Repo({
network: [new BrowserWebSocketClientAdapter('wss://sync.automerge.org')],
storage: new IndexedDBStorageAdapter(),
})
// Load the Automerge document from the URL hash or create a new one and set the hash
async function getDocument() {
const hash = window.location.hash
if (hash) {
const url = hash.substring(1)
return repo.find<SyncServiceDocument>(url as AnyDocumentId)
}
const doc = repo.create<SyncServiceDocument>({
__meta: {},
data: {},
})
window.location.hash = doc.url
return doc
}
// Initialize Feathers 6 application
const app = feathers<{
messages: AutomergeService<Message, Omit<Message, 'id'>>
}>()
// Register the Automerge service
app.use('messages', new AutomergeService(await getDocument()))
let text = $state('')
let messages = $state(await app.service('messages').find())
// Render new messages on real-time events
app.service('messages').on('created', (message: Message) => {
messages.push(message)
})
// Set up the application
await app.setup()
function createMessage(event: Event) {
event.preventDefault()
app.service('messages').create({
text,
createdAt: Date.now(),
})
text = ''
}
</script>
<main>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://dove.feathersjs.com/feathers-chat.css" />
<div class="drawer drawer-mobile">
<input id="drawer-left" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col">
<div id="chat" class="h-full overflow-y-auto px-3">
{#each messages as message (message.id)}
<div class={`chat py-2 chat-start`}>
<div class="chat-image avatar">
<div class="w-10 rounded-full">
<img src="https://github.com/feathersdev.png" />
</div>
</div>
<div class="chat-header pb-1">
<time class="text-xs opacity-50">{new Date(message.createdAt).toLocaleString()}</time>
</div>
<div class="chat-bubble">
{message.text}
</div>
</div>
{/each}
<div id="message-end" />
</div>
<div class="form-control w-full py-2 px-3">
<form class="input-group overflow-hidden" id="send-message" on:submit={createMessage}>
<input
name="text"
type="text"
placeholder="Compose message"
class="input input-bordered w-full"
bind:value={text}
/>
<button type="submit" class="btn">Send</button>
</form>
</div>
</div>
</div>
</main>
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import wasm from 'vite-plugin-wasm'
// https://vite.dev/config/
export default defineConfig({
plugins: [
wasm(),
svelte({
compilerOptions: {
experimental: {
async: true,
},
},
}),
],
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment