@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
Markdown
# @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).