UNPKG

@nomyx/gun-sync

Version:

A lightweight, powerful library for synchronizing application state in real-time using Gun.js.

355 lines (267 loc) 7.83 kB
# @nomyx/gun-sync A lightweight, powerful library for synchronizing application state in real-time using Gun.js with React hooks and TypeScript support. ## Features - 🔄 **Real-time state synchronization** using Gun.js peer-to-peer database - ⚛️ **React hooks** for seamless integration with React applications - 🔐 **User authentication** with built-in auth lifecycle management - 👥 **Presence tracking** to monitor active users - 📝 **Collection management** with CRUD operations - 🎯 **TypeScript support** with full type definitions - 🌐 **Context provider** for easy Gun instance sharing - ⚡ **Lightweight** with minimal dependencies ## Installation ```bash npm install @nomyx/gun-sync gun ``` ## Quick Start ### 1. Setup with Context Provider ```tsx import React from 'react'; import { GunProvider } from '@nomyx/gun-sync'; import App from './App'; function Root() { return ( <GunProvider peers={['https://gun-manhattan.herokuapp.com/gun']}> <App /> </GunProvider> ); } ``` ### 2. Use Hooks in Components ```tsx import React from 'react'; import { useGunState, useGunUser } from '@nomyx/gun-sync'; function MyComponent() { // Synchronized state const [message, setMessage] = useGunState('chat', 'currentMessage', ''); // User authentication const [userState, userActions] = useGunUser(); return ( <div> <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Type a message..." /> {userState.isLoggedIn ? ( <button onClick={userActions.signOut}>Sign Out</button> ) : ( <button onClick={() => userActions.signIn('username', 'password')}> Sign In </button> )} </div> ); } ``` ## API Reference ### Core Functions #### `createSync(config: SyncConfig): IGunInstance` Creates and initializes a Gun instance with the specified peer configuration. ```typescript import { createSync } from '@nomyx/gun-sync'; const gun = createSync({ peers: ['https://gun-manhattan.herokuapp.com/gun'] }); ``` ### Context #### `<GunProvider peers={string[]}>` Provides a Gun instance to all descendant components. ```tsx <GunProvider peers={['wss://gun-us.herokuapp.com/gun']}> <App /> </GunProvider> ``` #### `useGun(): IGunInstance` Accesses the Gun instance from context. ```typescript const gun = useGun(); ``` ### State Management Hooks #### `useGunState<T>(scope: string, key: string, initialValue: T, gunInstance?: IGunInstance)` Synchronizes a piece of state with Gun.js, similar to `useState`. ```typescript const [count, setCount] = useGunState('app', 'counter', 0); // Update state setCount(count + 1); // Or with function setCount(prev => prev + 1); ``` #### `useGunValue<T>(scope: string, key: string, initialValue: T, gunInstance?: IGunInstance)` Read-only access to synchronized values. ```typescript const currentUser = useGunValue('app', 'currentUser', null); ``` #### `useGunSet<T>(scope: string, gunInstance?: IGunInstance)` Manages a synchronized collection of items. ```typescript const [items, { addItem, updateItem, removeItem }] = useGunSet<Todo>('todos'); // Add new item (returns generated ID) const id = addItem({ text: 'Learn Gun.js', completed: false }); // Update item updateItem(id, { completed: true }); // Remove item removeItem(id); ``` ### Authentication #### `useGunUser(gunInstance?: IGunInstance)` Manages user authentication lifecycle. ```typescript const [userState, userActions] = useGunUser(); // User state userState.isLoggedIn; // boolean userState.isLoading; // boolean userState.user; // user object or null userState.error; // error message or undefined // User actions await userActions.signUp('alice', 'password123'); await userActions.signIn('alice', 'password123'); userActions.signOut(); ``` ### Presence Tracking #### `useGunPresence(scope: string, gunInstance?: IGunInstance)` Tracks active users within a scope. ```typescript const activeUsers = useGunPresence('chat-room-1'); // Returns array of PresenceInfo objects activeUsers.forEach(user => { console.log(`User ${user.id} last seen: ${new Date(user.lastSeen)}`); }); ``` ## Advanced Usage ### Without Context Provider You can use hooks with a specific Gun instance: ```typescript import { createSync, useGunState } from '@nomyx/gun-sync'; function MyComponent() { const gun = createSync({ peers: ['https://gun-manhattan.herokuapp.com/gun'] }); const [data, setData] = useGunState('app', 'data', null, gun); return <div>{data}</div>; } ``` ### Type Safety All hooks support TypeScript generics for type safety: ```typescript interface User { id: string; name: string; email: string; } const [users, userActions] = useGunSet<User>('users'); const [currentUser, setCurrentUser] = useGunState<User | null>('app', 'currentUser', null); ``` ## Examples ### Real-time Chat ```tsx import React, { useState } from 'react'; import { useGunSet, useGunUser } from '@nomyx/gun-sync'; interface Message { text: string; author: string; timestamp: number; } function Chat() { const [messages, { addItem }] = useGunSet<Message>('chat-messages'); const [userState] = useGunUser(); const [newMessage, setNewMessage] = useState(''); const sendMessage = () => { if (newMessage.trim() && userState.user) { addItem({ text: newMessage, author: userState.user.alias, timestamp: Date.now() }); setNewMessage(''); } }; return ( <div> <div> {messages .sort((a, b) => a.timestamp - b.timestamp) .map(msg => ( <div key={msg.id}> <strong>{msg.author}:</strong> {msg.text} </div> )) } </div> {userState.isLoggedIn && ( <div> <input value={newMessage} onChange={(e) => setNewMessage(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && sendMessage()} /> <button onClick={sendMessage}>Send</button> </div> )} </div> ); } ``` ### Collaborative Todo List ```tsx import React from 'react'; import { useGunSet } from '@nomyx/gun-sync'; interface Todo { text: string; completed: boolean; createdAt: number; } function TodoList() { const [todos, { addItem, updateItem, removeItem }] = useGunSet<Todo>('todos'); const addTodo = (text: string) => { addItem({ text, completed: false, createdAt: Date.now() }); }; const toggleTodo = (id: string) => { const todo = todos.find(t => t.id === id); if (todo) { updateItem(id, { completed: !todo.completed }); } }; return ( <div> <input placeholder="Add todo..." onKeyPress={(e) => { if (e.key === 'Enter') { addTodo(e.currentTarget.value); e.currentTarget.value = ''; } }} /> <ul> {todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> <button onClick={() => removeItem(todo.id)}>Delete</button> </li> ))} </ul> </div> ); } ``` ## Peer Dependencies - React >= 16.8.0 - Gun.js ^0.2020.1239 ## License ISC ## Contributing Issues and pull requests are welcome! Please check out the [GitHub repository](https://github.com/nomyx/gun-sync). ## Support For questions and support, please visit the [Gun.js community chat](http://chat.gun.eco).