@asaidimu/remote-store
TypeScript icon, indicating that this package has built-in type declarations

1.0.6 • Public • Published

@asaidimu/remote-store

A lightweight, type-safe remote data store for React applications with React Query integration.

Features

  • 🔒 Type-safe data operations
  • 🔄 Built-in React Query integration
  • 🎣 Simple hooks-based API
  • 🔌 Extensible adapter system
  • 📦 Zero config setup
  • 🎯 Focused mutations API

Installation

npm install @asaidimu/remote-store @tanstack/react-query
# or
yarn add @asaidimu/remote-store @tanstack/react-query
# or
bun add @asaidimu/remote-store @tanstack/react-query
# or
pnpm add @asaidimu/remote-store @tanstack/react-query

Quick Start

  1. Create your store adapter:
// adapters/rest.adapter.ts
import { BaseStore, RemoteStoreRecord } from '@asaidimu/remote-store';

export class RestAdapter<T extends RemoteStoreRecord> implements BaseStore<T> {
  constructor(private baseUrl: string) {}

  async find({ filter }: { filter: string }) {
    const response = await fetch(`${this.baseUrl}?filter=${filter}`);
    return response.json();
  }

  async read({ id }: { id: string }) {
    const response = await fetch(`${this.baseUrl}/${id}`);
    return response.json();
  }

  async list({ filter = '', page = 1, pageSize = 10 }) {
    const response = await fetch(
      `${this.baseUrl}?filter=${filter}&page=${page}&pageSize=${pageSize}`
    );
    return response.json();
  }

  async create({ data }: { data: Omit<T, 'id'> }) {
    const response = await fetch(this.baseUrl, {
      method: 'POST',
      body: JSON.stringify(data),
    });
    return response.json();
  }

  async update({ id, data }: { id: string; data: Partial<T> }) {
    const response = await fetch(`${this.baseUrl}/${id}`, {
      method: 'PATCH',
      body: JSON.stringify(data),
    });
    return response.json();
  }

  async delete({ id }: { id: string }) {
    await fetch(`${this.baseUrl}/${id}`, { method: 'DELETE' });
  }

  async upload({ id, field, file }: { id: string; field: string; file: File }) {
    const formData = new FormData();
    formData.append(field, file);
    
    const response = await fetch(`${this.baseUrl}/${id}/upload`, {
      method: 'POST',
      body: formData,
    });
    return response.json();
  }
}
  1. Create your store:
// stores/user.store.ts
import { useRemoteStore } from '@asaidimu/remote-store';
import { RestAdapter } from '../adapters/rest.adapter';
import { useQueryClient } from '@tanstack/react-query';

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

export function useUserStore() {
  const queryClient = useQueryClient();
  const adapter = new RestAdapter<User>('https://api.example.com/users');
  
  return useRemoteStore({
    store: adapter,
    queryClient,
  })('users');
}
  1. Use in your components:
// components/UserList.tsx
import { useUserStore } from '../stores/user.store';

export function UserList() {
  const store = useUserStore();
  
  // Setup mutations
  const createUser = store.create({
    onSuccess: (user) => console.log('Created:', user),
    onError: (error) => console.error('Failed:', error)
  });
  
  const updateUser = store.update({
    onSuccess: (user) => console.log('Updated:', user)
  });
  
  // Query data
  const { data: users, isLoading } = store.list({
    pageSize: 20
  });
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <div>
      {users?.map(user => (
        <div key={user.id}>
          {user.name}
          <button onClick={() => updateUser(user.id, { name: 'New Name' })}>
            Update
          </button>
        </div>
      ))}
      
      <button onClick={() => createUser({ 
        name: 'New User',
        email: 'new@example.com'
      })}>
        Add User
      </button>
    </div>
  );
}

API Reference

useRemoteStore

Main hook for creating a store instance.

function useRemoteStore<R extends RemoteStoreRecord>({
  store: BaseStore<R>,
  queryClient: QueryClient
}): (collection: string) => Store<R>

Store Methods

Queries

  • find({ filter, options? }) - Find a single record by filter
  • read({ id, options? }) - Read a single record by ID
  • list({ filter?, page?, pageSize?, options? }) - List multiple records

Mutations

  • create(callbacks?) => (data) => void - Create a new record
  • update(callbacks?) => (id, data) => void - Update an existing record
  • delete(callbacks?) => (id) => void - Delete a record
  • upload(callbacks?) => (id, field, file) => void - Upload a file

Types

RemoteStoreRecord

interface RemoteStoreRecord {
  readonly id: string;
  [key: string]: unknown;
}

BaseStore

interface BaseStore<T extends RemoteStoreRecord> {
  readonly find: (props: { filter: string; options?: QueryOptions }) => Promise<T>;
  readonly read: (props: { id: string; options?: QueryOptions }) => Promise<T>;
  readonly list: (props: ListOptions) => Promise<T[]>;
  readonly update: (props: { id: string; data: Partial<T> }) => Promise<T>;
  readonly delete: (props: { id: string }) => Promise<void>;
  readonly create: (props: { data: Omit<T, 'id'> }) => Promise<T>;
  readonly upload: (props: { id: string; field: string; file: File }) => Promise<T>;
}

Creating Adapters

You can create adapters for any data source by implementing the BaseStore interface. Here are some examples:

Firebase Adapter

import { BaseStore, RemoteStoreRecord } from '@asaidimu/remote-store';
import { collection, doc, getDoc, getDocs, query, where } from 'firebase/firestore';

export class FirebaseAdapter<T extends RemoteStoreRecord> implements BaseStore<T> {
  constructor(
    private db: Firestore,
    private collectionName: string
  ) {}

  async find({ filter }) {
    const q = query(
      collection(this.db, this.collectionName),
      where('field', '==', filter)
    );
    const snapshot = await getDocs(q);
    return snapshot.docs[0].data() as T;
  }

  async read({ id }) {
    const docRef = doc(this.db, this.collectionName, id);
    const snapshot = await getDoc(docRef);
    return snapshot.data() as T;
  }

  // ... implement other methods
}

Supabase Adapter

import { BaseStore, RemoteStoreRecord } from '@asaidimu/remote-store';
import { SupabaseClient } from '@supabase/supabase-js';

export class SupabaseAdapter<T extends RemoteStoreRecord> implements BaseStore<T> {
  constructor(
    private supabase: SupabaseClient,
    private tableName: string
  ) {}

  async find({ filter }) {
    const { data, error } = await this.supabase
      .from(this.tableName)
      .select()
      .match(filter)
      .single();
      
    if (error) throw error;
    return data;
  }

  async create({ data }) {
    const { data: created, error } = await this.supabase
      .from(this.tableName)
      .insert(data)
      .single();
      
    if (error) throw error;
    return created;
  }

  // ... implement other methods
}

Contributing

We welcome contributions! Please see our contributing guide for details.

License

MIT

Package Sidebar

Install

npm i @asaidimu/remote-store

Weekly Downloads

1

Version

1.0.6

License

MIT

Unpacked Size

14.3 kB

Total Files

5

Last publish

Collaborators

  • asaidimu