npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vaiftech/react

v1.0.8

Published

React hooks for VAIF - Authentication, Data, Realtime, Storage, and more

Readme

@vaiftech/react

React hooks for VAIF Studio - a Backend-as-a-Service platform.

Installation

npm install @vaiftech/react @vaiftech/client
# or
pnpm add @vaiftech/react @vaiftech/client
# or
yarn add @vaiftech/react @vaiftech/client

Quick Start

import { VaifProvider } from '@vaiftech/react';
import { createVaifClient } from '@vaiftech/client';

const client = createVaifClient({
  baseUrl: 'https://api.myproject.vaif.io',
  apiKey: 'vaif_pk_xxx',
});

function App() {
  return (
    <VaifProvider client={client}>
      <MyComponent />
    </VaifProvider>
  );
}

Hooks Overview

| Category | Hooks | |----------|-------| | Auth | useAuth, useUser, useToken, usePasswordReset, useEmailVerification, useMagicLink, useOAuth, useMFA | | Query | useQuery, useQueryById, useQueryFirst, usePaginatedQuery, useInfiniteQuery, useCount | | Mutation | useMutation, useCreate, useUpdate, useDelete, useUpsert, useBatchCreate, useBatchUpdate, useBatchDelete, useOptimisticMutation | | Realtime | useSubscription, useChannel, usePresence, useRealtimeConnection, useBroadcast | | Storage | useUpload, useDownload, useFile, useFiles, useDropzone, usePublicUrl | | Functions | useFunction, useRpc, useFunctionList, useBatchInvoke, useScheduledFunction | | MongoDB | useMongoFind, useMongoFindOne, useMongoAggregate, useMongoInsertOne, useMongoInsertMany, useMongoUpdateOne, useMongoUpdateMany, useMongoDeleteOne, useMongoDeleteMany, useMongoInfiniteFind, useMongoCount, useMongoDistinct, useMongoCollection | | Observability | useMetrics, useAuditLogs, useIncidents, useSystemHealth, useRealtimeStats, useErrorTracking |

Authentication

import { useAuth, useUser, useSession, useMFA, useOAuth } from '@vaiftech/react';

function AuthComponent() {
  const { user, isLoading, signIn, signUp, signOut } = useAuth();

  if (isLoading) return <div>Loading...</div>;

  if (!user) {
    return (
      <button onClick={() => signIn({ email, password })}>
        Sign In
      </button>
    );
  }

  return (
    <div>
      <p>Welcome, {user.email}</p>
      <button onClick={signOut}>Sign Out</button>
    </div>
  );
}

// Just the user
function Profile() {
  const { user, isLoading } = useUser();
  return user ? <p>{user.email}</p> : null;
}

// OAuth login
function OAuthLogin() {
  const { signInWithProvider, isLoading } = useOAuth();

  return (
    <div>
      <button onClick={() => signInWithProvider('google')}>
        Sign in with Google
      </button>
      <button onClick={() => signInWithProvider('github')}>
        Sign in with GitHub
      </button>
    </div>
  );
}

// MFA setup
function MFASetup() {
  const { enroll, verify, isEnrolling, qrCode } = useMFA();

  const handleSetup = async () => {
    await enroll({ type: 'totp' });
  };

  return (
    <div>
      {qrCode && <img src={qrCode} alt="Scan with authenticator" />}
      <button onClick={handleSetup}>Enable MFA</button>
    </div>
  );
}

Standalone Auth Provider

For auth-only applications, use the standalone auth provider from @vaiftech/auth:

import { AuthProvider, useAuth, useSession } from '@vaiftech/react';

function App() {
  return (
    <AuthProvider config={{ url: 'https://api.vaif.studio', apiKey: 'your-key' }}>
      <YourApp />
    </AuthProvider>
  );
}

function LoginPage() {
  const { signInWithPassword, signInWithOAuth, isLoading } = useAuth();

  return (
    <div>
      <form onSubmit={(e) => {
        e.preventDefault();
        signInWithPassword({ email, password });
      }}>
        <input type="email" />
        <input type="password" />
        <button type="submit">Sign In</button>
      </form>
      <button onClick={() => signInWithOAuth({ provider: 'google' })}>
        Google
      </button>
    </div>
  );
}

Data Fetching

import { useQuery, useQueryById, usePaginatedQuery, useInfiniteQuery } from '@vaiftech/react';

interface Post {
  id: string;
  title: string;
  content: string;
}

// Basic query
function PostList() {
  const { data: posts, isLoading, error, refetch } = useQuery<Post>('posts', {
    filters: [{ field: 'published', operator: 'eq', value: true }],
    orderBy: [{ field: 'createdAt', direction: 'desc' }],
    limit: 10,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {posts?.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// Query by ID
function PostDetail({ postId }: { postId: string }) {
  const { data: post, isLoading } = useQueryById<Post>('posts', postId);

  if (isLoading) return <div>Loading...</div>;
  return <h1>{post?.title}</h1>;
}

// Paginated query
function PaginatedPosts() {
  const {
    data,
    page,
    totalPages,
    nextPage,
    prevPage,
    isLoading
  } = usePaginatedQuery<Post>('posts', {
    pageSize: 20,
  });

  return (
    <div>
      {data?.map(post => <div key={post.id}>{post.title}</div>)}
      <button onClick={prevPage} disabled={page === 1}>Prev</button>
      <span>{page} / {totalPages}</span>
      <button onClick={nextPage} disabled={page === totalPages}>Next</button>
    </div>
  );
}

// Infinite scroll
function InfinitePosts() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery<Post>('posts', { limit: 20 });

  return (
    <div>
      {data?.map(post => <div key={post.id}>{post.title}</div>)}
      {hasNextPage && (
        <button onClick={fetchNextPage} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Loading...' : 'Load More'}
        </button>
      )}
    </div>
  );
}

Mutations

import { useCreate, useUpdate, useDelete, useMutation, useOptimisticMutation } from '@vaiftech/react';

// Create
function CreatePost() {
  const { create, isCreating, error } = useCreate<Post>('posts');

  const handleSubmit = async (data: Omit<Post, 'id'>) => {
    const newPost = await create(data);
    console.log('Created:', newPost);
  };

  return (
    <button onClick={() => handleSubmit({ title: 'New', content: '...' })} disabled={isCreating}>
      {isCreating ? 'Creating...' : 'Create Post'}
    </button>
  );
}

// Update
function UpdatePost({ postId }: { postId: string }) {
  const { update, isUpdating } = useUpdate<Post>('posts');

  return (
    <button
      onClick={() => update(postId, { title: 'Updated Title' })}
      disabled={isUpdating}
    >
      Update
    </button>
  );
}

// Delete
function DeletePost({ postId }: { postId: string }) {
  const { remove, isDeleting } = useDelete('posts');

  return (
    <button onClick={() => remove(postId)} disabled={isDeleting}>
      Delete
    </button>
  );
}

// Full mutation hook
function PostActions() {
  const { create, update, remove, isLoading } = useMutation<Post>('posts');

  // Use create, update, remove as needed
}

// Optimistic updates
function OptimisticLike({ postId }: { postId: string }) {
  const { mutate } = useOptimisticMutation<Post>('posts', {
    optimisticUpdate: (cache, postId) => {
      return { ...cache[postId], likes: cache[postId].likes + 1 };
    },
    rollbackOnError: true,
  });

  return <button onClick={() => mutate(postId, { likes: '+1' })}>Like</button>;
}

Realtime

import { useSubscription, useChannel, usePresence, useBroadcast } from '@vaiftech/react';

// Subscribe to table changes
function MessageListener() {
  useSubscription<Message>('messages', {
    event: 'INSERT',
    onInsert: (message) => console.log('New message:', message),
    onUpdate: (message) => console.log('Updated:', message),
    onDelete: (message) => console.log('Deleted:', message.id),
  });

  return null;
}

// Channel communication
function ChatRoom({ roomId }: { roomId: string }) {
  const channel = useChannel(`room-${roomId}`);
  const [messages, setMessages] = useState<Message[]>([]);

  useEffect(() => {
    channel.on('message', (msg) => {
      setMessages(prev => [...prev, msg]);
    });
    return () => channel.off('message');
  }, [channel]);

  const sendMessage = (text: string) => {
    channel.send('message', { text, timestamp: Date.now() });
  };

  return <div>{/* Chat UI */}</div>;
}

// Presence tracking
function OnlineUsers({ roomId }: { roomId: string }) {
  const { users, track, leave } = usePresence(`room-${roomId}`);

  useEffect(() => {
    track({ status: 'online', name: currentUser.name });
    return () => leave();
  }, []);

  return (
    <div>
      <h4>Online ({users.length})</h4>
      {users.map(u => <span key={u.id}>{u.name}</span>)}
    </div>
  );
}

// Broadcast events
function TypingIndicator({ roomId }: { roomId: string }) {
  const { send, subscribe } = useBroadcast(`room-${roomId}`);
  const [typing, setTyping] = useState<string[]>([]);

  useEffect(() => {
    subscribe('typing', (event) => {
      setTyping(prev => [...prev, event.userId]);
      setTimeout(() => {
        setTyping(prev => prev.filter(id => id !== event.userId));
      }, 3000);
    });
  }, []);

  const onType = () => send('typing', { userId: currentUser.id });

  return <div>{typing.length > 0 && `${typing.join(', ')} typing...`}</div>;
}

Storage

import { useUpload, useDownload, useFile, useFiles, useDropzone, usePublicUrl } from '@vaiftech/react';

// File upload with progress
function FileUpload() {
  const { upload, progress, isUploading, error } = useUpload();

  const handleUpload = async (file: File) => {
    const result = await upload(file, `uploads/${file.name}`);
    console.log('Uploaded:', result?.url);
  };

  return (
    <div>
      <input type="file" onChange={(e) => handleUpload(e.target.files![0])} />
      {isUploading && <progress value={progress} max={100} />}
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

// Drag and drop zone
function DropzoneUpload() {
  const { getRootProps, getInputProps, isDragActive, files } = useDropzone({
    accept: { 'image/*': ['.png', '.jpg', '.gif'] },
    maxFiles: 5,
    onDrop: async (files) => {
      // Handle uploaded files
    },
  });

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      {isDragActive ? 'Drop files here' : 'Drag files or click to upload'}
    </div>
  );
}

// Display files
function FileDisplay({ path }: { path: string }) {
  const url = usePublicUrl(path);
  return url ? <img src={url} alt="" /> : null;
}

// List files
function FileList({ prefix }: { prefix: string }) {
  const { files, isLoading, refetch } = useFiles({ prefix });

  return (
    <ul>
      {files?.map(file => (
        <li key={file.path}>{file.name} ({file.size} bytes)</li>
      ))}
    </ul>
  );
}

Edge Functions

import { useFunction, useRpc, useBatchInvoke } from '@vaiftech/react';

// Invoke function
function SendEmail() {
  const { invoke, isInvoking, error, result } = useFunction('send-email');

  const handleSend = async () => {
    await invoke({
      to: '[email protected]',
      subject: 'Hello',
    });
  };

  return (
    <button onClick={handleSend} disabled={isInvoking}>
      {isInvoking ? 'Sending...' : 'Send Email'}
    </button>
  );
}

// RPC-style calls
function DataProcessor() {
  const { call, isLoading, data } = useRpc<ProcessResult>('process-data');

  return (
    <button onClick={() => call({ input: rawData })}>
      Process
    </button>
  );
}

// Batch invocations
function BatchProcessor() {
  const { invoke, isInvoking, results } = useBatchInvoke();

  const processAll = async () => {
    await invoke([
      { name: 'resize-image', payload: { url: 'img1.jpg' } },
      { name: 'resize-image', payload: { url: 'img2.jpg' } },
      { name: 'resize-image', payload: { url: 'img3.jpg' } },
    ]);
  };

  return <button onClick={processAll}>Process All</button>;
}

MongoDB Hooks

import {
  useMongoFind,
  useMongoFindOne,
  useMongoInsertOne,
  useMongoUpdateOne,
  useMongoDeleteOne,
  useMongoAggregate,
  useMongoCollection,
  useMongoInfiniteFind,
} from '@vaiftech/react';

// Find documents
function UserList() {
  const { data: users, isLoading, error, refetch } = useMongoFind<User>('users', {
    filter: { status: 'active' },
    sort: { createdAt: -1 },
    limit: 20,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {users?.map(user => <li key={user._id}>{user.name}</li>)}
    </ul>
  );
}

// Find single document
function UserProfile({ id }: { id: string }) {
  const { data: user, isLoading } = useMongoFindOne<User>('users', { _id: id });
  return user ? <div>{user.name}</div> : null;
}

// Insert document
function CreateUser() {
  const { insertOne, isInserting } = useMongoInsertOne<User>('users');

  const handleCreate = async () => {
    const result = await insertOne({
      name: 'New User',
      email: '[email protected]',
      createdAt: new Date(),
    });
    console.log('Created:', result.insertedId);
  };

  return (
    <button onClick={handleCreate} disabled={isInserting}>
      Create User
    </button>
  );
}

// Update document
function UpdateUser({ id }: { id: string }) {
  const { updateOne, isUpdating } = useMongoUpdateOne('users');

  return (
    <button
      onClick={() => updateOne({ _id: id }, { $set: { updatedAt: new Date() } })}
      disabled={isUpdating}
    >
      Update
    </button>
  );
}

// Delete document
function DeleteUser({ id }: { id: string }) {
  const { deleteOne, isDeleting } = useMongoDeleteOne('users');
  return (
    <button onClick={() => deleteOne({ _id: id })} disabled={isDeleting}>
      Delete
    </button>
  );
}

// Aggregation pipeline
function UserStats() {
  const { data: stats } = useMongoAggregate<{ _id: string; count: number }>('users', [
    { $match: { status: 'active' } },
    { $group: { _id: '$country', count: { $sum: 1 } } },
    { $sort: { count: -1 } },
  ]);

  return (
    <ul>
      {stats?.map(stat => (
        <li key={stat._id}>{stat._id}: {stat.count}</li>
      ))}
    </ul>
  );
}

// Infinite scroll with MongoDB
function InfiniteUserList() {
  const { data, fetchNextPage, hasNextPage } = useMongoInfiniteFind<User>('users', {
    filter: { status: 'active' },
    sort: { createdAt: -1 },
    limit: 20,
  });

  return (
    <div>
      {data?.map(user => <div key={user._id}>{user.name}</div>)}
      {hasNextPage && <button onClick={fetchNextPage}>Load More</button>}
    </div>
  );
}

// Full collection access
function AdvancedOps() {
  const collection = useMongoCollection<User>('users');

  const handleBulk = async () => {
    const count = await collection.count({ status: 'active' });
    const countries = await collection.distinct('country');
    await collection.bulkWrite([
      { insertOne: { document: { name: 'Bulk User' } } },
    ]);
  };

  return <button onClick={handleBulk}>Run Operations</button>;
}

Observability Hooks

import {
  useMetrics,
  useAuditLogs,
  useIncidents,
  useSystemHealth,
  useRealtimeStats,
  useErrorTracking,
} from '@vaiftech/react';

// Metrics dashboard
function MetricsDashboard() {
  const { metrics, isLoading, timeRange, setTimeRange, refresh } = useMetrics({
    sources: ['api', 'database', 'storage'],
    interval: '1h',
  });

  return (
    <div>
      <select onChange={(e) => setTimeRange(e.target.value)} value={timeRange}>
        <option value="1h">Last Hour</option>
        <option value="24h">Last 24 Hours</option>
        <option value="7d">Last 7 Days</option>
      </select>
      {metrics?.map(m => (
        <div key={m.name}>{m.name}: {m.value}</div>
      ))}
    </div>
  );
}

// Audit logs
function AuditLogViewer() {
  const { logs, hasMore, loadMore, setFilters } = useAuditLogs({ limit: 50 });

  return (
    <div>
      <select onChange={(e) => setFilters({ action: e.target.value })}>
        <option value="">All Actions</option>
        <option value="create">Create</option>
        <option value="update">Update</option>
        <option value="delete">Delete</option>
      </select>
      <ul>
        {logs?.map(log => (
          <li key={log.id}>{log.timestamp}: {log.action} by {log.userId}</li>
        ))}
      </ul>
      {hasMore && <button onClick={loadMore}>Load More</button>}
    </div>
  );
}

// Incident management
function IncidentList() {
  const { incidents, activeCount, acknowledge, resolve } = useIncidents();

  return (
    <div>
      <h3>Active Incidents: {activeCount}</h3>
      {incidents?.map(incident => (
        <div key={incident.id}>
          <span>{incident.title} - {incident.severity}</span>
          {incident.status === 'open' && (
            <button onClick={() => acknowledge(incident.id)}>Ack</button>
          )}
          {incident.status === 'acknowledged' && (
            <button onClick={() => resolve(incident.id)}>Resolve</button>
          )}
        </div>
      ))}
    </div>
  );
}

// System health
function SystemHealth() {
  const { isHealthy, services, lastCheck } = useSystemHealth({ pollInterval: 30000 });

  return (
    <div>
      <div>Status: {isHealthy ? 'Healthy' : 'Degraded'}</div>
      <div>Last Check: {lastCheck?.toLocaleString()}</div>
      {services?.map(svc => (
        <div key={svc.name}>{svc.name}: {svc.status} ({svc.latency}ms)</div>
      ))}
    </div>
  );
}

// Realtime stats
function RealtimeStats() {
  const { connections, requestsPerSecond, activeUsers } = useRealtimeStats();

  return (
    <div>
      <div>Connections: {connections}</div>
      <div>Requests/sec: {requestsPerSecond}</div>
      <div>Active Users: {activeUsers}</div>
    </div>
  );
}

// Error tracking
function ErrorTracker() {
  const { errorRate, topErrors, trackError } = useErrorTracking({ timeRange: '24h' });

  return (
    <div>
      <div>Error Rate: {errorRate}%</div>
      {topErrors?.map(err => (
        <div key={err.message}>{err.message} ({err.count})</div>
      ))}
    </div>
  );
}

TypeScript Support

All hooks support TypeScript generics for full type safety:

interface User {
  id: string;
  email: string;
  name: string;
}

const { data } = useQuery<User>('users');
// data is User[] | undefined

const { data: user } = useMongoFindOne<User>('users', { email: '[email protected]' });
// user is User | null | undefined

Related Packages

License

MIT