{"id":34674648,"url":"https://github.com/v0id-user/verani","last_synced_at":"2026-03-14T07:15:23.983Z","repository":{"id":326684912,"uuid":"1104426603","full_name":"v0id-user/verani","owner":"v0id-user","description":"A simple, focused realtime SDK for Cloudflare Actors with Socket.io-like semantics","archived":false,"fork":false,"pushed_at":"2025-12-17T04:43:15.000Z","size":661,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"canary","last_synced_at":"2025-12-17T13:07:01.788Z","etag":null,"topics":["actor","actors","bun","cloudflare","cloudflare-actors","cloudflare-durable-objects","cloudflare-workers","durable-objects","isc","npm","sdk","socket-io","sockets","web","websocket","websockets"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/verani","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/v0id-user.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-26T07:43:34.000Z","updated_at":"2025-12-17T04:43:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/v0id-user/verani","commit_stats":null,"previous_names":["v0id-user/verani"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/v0id-user/verani","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v0id-user%2Fverani","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v0id-user%2Fverani/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v0id-user%2Fverani/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v0id-user%2Fverani/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/v0id-user","download_url":"https://codeload.github.com/v0id-user/verani/tar.gz/refs/heads/canary","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v0id-user%2Fverani/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28007457,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actor","actors","bun","cloudflare","cloudflare-actors","cloudflare-durable-objects","cloudflare-workers","durable-objects","isc","npm","sdk","socket-io","sockets","web","websocket","websockets"],"created_at":"2025-12-24T20:01:04.109Z","updated_at":"2025-12-24T20:02:19.139Z","avatar_url":"https://github.com/v0id-user.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Verani\n\n\u003cdiv align=\"center\"\u003e\n\n[![MADE BY #V0ID](https://img.shields.io/badge/MADE%20BY%20%23V0ID-F3EEE1.svg?style=for-the-badge)](https://github.com/v0id-user)\n\nBuild realtime apps on Cloudflare with Socket.io-like simplicity\n\n[Getting Started](#quick-start) • [Documentation](./docs/) • [Examples](./examples/)\n\n\u003c/div\u003e\n\nVerani brings the familiar developer experience of Socket.io to Cloudflare's Durable Objects (Actors), with proper hibernation support and minimal overhead. Build realtime chat, presence systems, notifications, and more—all running on Cloudflare's edge.\n\n## Why Verani?\n\n- **Familiar API**: If you've used Socket.io, you already know how to use Verani\n- **Horizontally scalable**: Per-connection architecture - each user gets their own Durable Object\n- **Hibernation support**: Handles Cloudflare Actor hibernation automatically\n- **Type safe**: Built with TypeScript, full type safety throughout\n- **Simple mental model**: Connections, rooms, and broadcast semantics that just make sense\n- **Modern DX**: Automatic reconnection, error handling, and connection lifecycle management\n- **Edge-ready**: Built for Cloudflare Workers and Durable Objects\n- **Cost-efficient**: Idle connections hibernate and cost nothing\n\n## Quick Start\n\nGet a realtime chat app running in 5 minutes.\n\n### Step 1: Install\n\n```bash\nnpm install verani @cloudflare/actors\n```\n\nDon't have a Cloudflare Worker project? Create one:\n\n```bash\nnpm create cloudflare@latest my-verani-app\ncd my-verani-app\nnpm install verani @cloudflare/actors\n```\n\n### Step 2: Create Your Connection Handler\n\nCreate `src/actors/connection.ts`:\n\n```typescript\nimport { defineConnection, createConnectionHandler, createRoomHandler } from \"verani\";\n\n// Define connection handler (one WebSocket per user)\nconst userConnection = defineConnection({\n  name: \"UserConnection\",\n\n  extractMeta(req) {\n    const url = new URL(req.url);\n    const userId = url.searchParams.get(\"userId\") || crypto.randomUUID();\n    return {\n      userId,\n      clientId: crypto.randomUUID(),\n      channels: [\"default\"]\n    };\n  },\n\n  async onConnect(ctx) {\n    console.log(`User ${ctx.meta.userId} connected`);\n    // Join chat room (persisted across hibernation)\n    await ctx.actor.joinRoom(\"chat\");\n  },\n\n  async onDisconnect(ctx) {\n    console.log(`User ${ctx.meta.userId} disconnected`);\n    // Room leave is handled automatically\n  }\n});\n\n// Handle messages (socket.io-like API)\nuserConnection.on(\"chat.message\", async (ctx, data) =\u003e {\n  // Broadcast to everyone in the chat room\n  await ctx.emit.toRoom(\"chat\").emit(\"chat.message\", {\n    from: ctx.meta.userId,\n    text: data.text,\n    timestamp: Date.now()\n  });\n});\n\n// Export the connection handler\nexport const UserConnection = createConnectionHandler(userConnection);\n\n// Export the room coordinator\nexport const ChatRoom = createRoomHandler({ name: \"ChatRoom\" });\n```\n\n### Step 3: Wire Up Your Worker\n\nUpdate `src/index.ts`:\n\n```typescript\nimport { UserConnection, ChatRoom } from \"./actors/connection\";\n\n// Export Durable Object classes\nexport { UserConnection, ChatRoom };\n\n// Route WebSocket connections\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    const url = new URL(request.url);\n\n    if (url.pathname.startsWith(\"/ws\")) {\n      // Extract userId and route to user-specific DO\n      const userId = url.searchParams.get(\"userId\") || crypto.randomUUID();\n      const stub = UserConnection.get(userId);\n      return stub.fetch(request);\n    }\n\n    return new Response(\"Not Found\", { status: 404 });\n  }\n};\n```\n\n### Step 4: Configure Wrangler\n\nUpdate `wrangler.jsonc`:\n\n```jsonc\n{\n  \"name\": \"my-verani-app\",\n  \"main\": \"src/index.ts\",\n  \"compatibility_date\": \"2025-11-26\",\n\n  \"durable_objects\": {\n    \"bindings\": [\n      {\n        \"class_name\": \"UserConnection\",\n        \"name\": \"CONNECTION_DO\"\n      },\n      {\n        \"class_name\": \"ChatRoom\",\n        \"name\": \"ROOM_DO\"\n      }\n    ]\n  },\n\n  \"migrations\": [\n    {\n      \"new_sqlite_classes\": [\"UserConnection\", \"ChatRoom\"],\n      \"tag\": \"v1\"\n    }\n  ]\n}\n```\n\nImportant: Export names must match `class_name` in `wrangler.jsonc`.\n\n### Step 5: Build Your Client\n\n```typescript\nimport { VeraniClient } from \"verani/client\";\n\nconst client = new VeraniClient(\"ws://localhost:8787/ws?userId=alice\");\n\n// Listen for messages\nclient.on(\"chat.message\", (data) =\u003e {\n  console.log(`${data.from}: ${data.text}`);\n});\n\nclient.on(\"user.joined\", (data) =\u003e {\n  console.log(`User ${data.userId} joined!`);\n});\n\n// Send messages\nclient.emit(\"chat.message\", { text: \"Hello, world!\" });\n\n// Wait for connection (optional)\nawait client.waitForConnection();\n```\n\n### Step 6: Run It!\n\n```bash\n# Start the server\nnpm run dev\n# or\nwrangler dev\n\n# In another terminal, run your client\n# (or open multiple browser tabs with your client code)\n```\n\nThat's it! You now have a working realtime chat app.\n\nNeed more help? Check out the [Quick Start Guide](./docs/getting-started/quick-start.md) for detailed examples.\n\n## Documentation\n\n- [Live Documentation](https://verani-docs.cloudflare-c49.workers.dev/docs/getting-started/installation) - Installation and quick start guide\n- [Getting Started](./docs/getting-started/) - Installation and quick start guide\n- [API Reference](./docs/api/) - Complete server and client API documentation\n- [Guides](./docs/guides/) - Configuration, deployment, scaling, and RPC\n- [Examples](./docs/examples/) - Common usage patterns and code samples\n- [Concepts](./docs/concepts/) - Architecture, hibernation, and core concepts\n- [Security](./docs/security/) - Authentication, authorization, and best practices\n\n## Key Concepts\n\n- **ConnectionDO** = A Durable Object that owns ONE WebSocket per user\n- **RoomDO** = A Durable Object that coordinates room membership and broadcasts\n- **Emit** = Send messages (`ctx.emit.toRoom(\"chat\").emit(\"event\", data)`)\n- **Hibernation** = Handled automatically, state persisted and restored\n\n## Features\n\n### Server-Side\n- **Per-connection DOs**: Each user gets their own Durable Object (horizontally scalable)\n- **Socket.io-like API**: `connection.on()`, `ctx.emit.toRoom()`, familiar patterns\n- **Room coordination**: RoomDOs manage membership and broadcast via RPC\n- **Lifecycle hooks**: `onConnect`, `onDisconnect`, `onMessage` for full control\n- **RPC support**: DO-to-DO communication for message delivery\n- **Automatic hibernation**: State persisted and restored automatically\n- **Persistent state**: Built-in support for state that survives hibernation\n- **Type safety**: Full TypeScript support with type inference\n\n### Client-Side\n- **Automatic reconnection**: Exponential backoff with configurable retry logic\n- **Message queueing**: Messages queued when disconnected, sent on reconnect\n- **Keepalive**: Built-in ping/pong to detect dead connections\n- **Event-based API**: Familiar `on()`, `emit()`, `once()`, `off()` methods\n- **Connection state**: Track connection lifecycle (`connecting`, `connected`, `disconnected`)\n\n## Try the Examples\n\nSee Verani in action with working examples:\n\n```bash\ngit clone https://github.com/v0id-user/verani\ncd verani\nbun install \u0026\u0026 bun run dev\n```\n\nThen in another terminal, try:\n\n```bash\n# Chat room example\nbun run examples/clients/chat-client.ts\n\n# Presence tracking\nbun run examples/clients/presence-client.ts\n\n# Notifications feed\nbun run examples/clients/notifications-client.ts\n```\n\nSee the [Examples README](./examples/README.md) for more details.\n\n## Real-World Example\n\n[Vchats](https://github.com/v0id-user/vchats) - A complete chat application built with Verani\n\n\n## License\n\nISC\n\n## Contributing\n\nContributions welcome! Please read our [Contributing Guidelines](./CONTRIBUTING.md) first.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fv0id-user%2Fverani","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fv0id-user%2Fverani","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fv0id-user%2Fverani/lists"}