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

natural-path-sdk

v1.0.3

Published

React TypeScript SDK for The Natural Path Spa Management System

Readme

natural-path-sdk

React TypeScript SDK for The Natural Path Spa Management System

npm version TypeScript React Query

A fully-typed React SDK that provides hooks-based abstractions for the Natural Path Spa backend API. Built with React Query for optimal caching and state management.

Registry: natural-path-sdk on npm — latest 1.0.0. Install with npm install natural-path-sdk (peer: @tanstack/react-query). The monorepo frontend/ app uses "natural-path-sdk": "^1.0.0" from the registry so it matches what external consumers install.

Features

  • Fully Typed - Complete TypeScript support with all API types
  • React Query Integration - Automatic caching, refetching, and optimistic updates
  • Auth Management - JWT handling with automatic token refresh
  • Real-time Updates - WebSocket support for live availability updates
  • Complete Booking Flow - Multi-step booking with slot locking
  • Admin Dashboard - Stats, analytics, and user management hooks

Publishing (maintainers)

The repo includes .github/workflows/publish-sdk.yml.

  1. npm (recommended for frontend consumers)
    Cost: Publishing public packages is free; you only need a free npmjs.com account. Paid npm plans are for private packages and org features you may not need.
    Token: GitHub → repo Settings → Secrets → Actions → add NPM_TOKEN. Create the token at npm → Access Tokens (sign in → Generate New Token). Use type Automation (classic) or a fine-grained token with permission to publish the natural-path-sdk package. The workflow uses it as NODE_AUTH_TOKEN during npm publish — you do not paste the token into the repo; only into GitHub Secrets.
    Scope: This package is published as natural-path-sdk (unscoped), so you do not need an npm org or @scope — only an account that is allowed to publish that name (first publish wins the name). Bump version in backend/sdk/package.json, merge, then either:

    • Local (token only in your shell): export NODE_AUTH_TOKEN=… then run ../scripts/publish-sdk-to-npm.sh from the monorepo (script lives under backend/scripts/).
    • Actions → Publish SDK → Run workflow → choose npm, or
    • Push a git tag sdk/v1.0.1 (must match the version you intend to publish).
  2. GitHub Packages
    Not configured for this package name (GPR works best with scoped @org/pkg). Use npm only unless you later rename to a scoped package.

Local SDK development (monorepo): To test unpublished SDK changes in frontend/, use either npm link (cd backend/sdk && npm link then cd frontend && npm link natural-path-sdk) or temporarily set "natural-path-sdk": "file:../backend/sdk" and add a Vite alias to ../backend/sdk/src/index.ts — revert before shipping so the app tracks the published package.

Installation

npm install natural-path-sdk @tanstack/react-query
# or
yarn add natural-path-sdk @tanstack/react-query
# or
pnpm add natural-path-sdk @tanstack/react-query

Quick Start

1. Wrap your app with the provider

import { NaturalPathProvider } from 'natural-path-sdk';

function App() {
  return (
    <NaturalPathProvider 
      baseUrl="https://api.thenaturalpath.com"
      onAuthError={(error) => {
        console.error('Auth error:', error);
        // Redirect to login page
      }}
    >
      <YourApp />
    </NaturalPathProvider>
  );
}

2. Use hooks in your components

import { useServices, useAuth, useBookingFlow } from 'natural-path-sdk';

function ServicesPage() {
  const { data: services, isLoading } = useServices();

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

  return (
    <div>
      {services?.map(service => (
        <ServiceCard key={service.service_id} service={service} />
      ))}
    </div>
  );
}

Authentication

Login

import { useAuth } from 'natural-path-sdk';

function LoginPage() {
  const { login, isLoading, error } = useAuth();

  const handleSubmit = async (email: string, password: string) => {
    try {
      await login({ email, password });
      // Redirect to dashboard
    } catch (err) {
      // Handle error
    }
  };

  return (
    <form onSubmit={...}>
      {/* Your form fields */}
    </form>
  );
}

Register

const { register } = useAuth();

await register({
  email: '[email protected]',
  password: 'securepassword',
  first_name: 'John',
  last_name: 'Doe',
  phone: '+1234567890'
});

Check Auth State

const { user, isAuthenticated, logout } = useAuth();

if (isAuthenticated) {
  return (
    <div>
      Welcome, {user?.first_name}!
      <button onClick={logout}>Logout</button>
    </div>
  );
}

Services

List Services

import { useServices, useFeaturedServices } from 'natural-path-sdk';

// All services
const { data: services } = useServices();

// Featured services only
const { data: featured } = useFeaturedServices();

// By category
const { data: massages } = useServices('massage');

Get Single Service

import { useService } from 'natural-path-sdk';

const { data: service } = useService('service-id');

Practitioners

List Practitioners

import { 
  usePractitioners, 
  useFeaturedPractitioners,
  usePractitionersByService 
} from 'natural-path-sdk';

// All practitioners
const { data: practitioners } = usePractitioners();

// Featured only
const { data: featured } = useFeaturedPractitioners();

// By service
const { data: massageTherapists } = usePractitionersByService('massage-service-id');

Get Availability

import { useAvailability } from 'natural-path-sdk';

const { data: slots, isLoading } = useAvailability(
  'practitioner-id',
  '2026-03-25'  // YYYY-MM-DD format
);

// Slots automatically refresh every 30 seconds

Real-time Availability (WebSocket)

import { useRealtimeAvailability } from 'natural-path-sdk';

function AvailabilityCalendar({ practitionerId, date }) {
  const { slots, isConnected, lastUpdate } = useRealtimeAvailability(
    practitionerId,
    date
  );

  return (
    <div>
      {isConnected && <span>Live updates enabled</span>}
      {slots.map(slot => (
        <TimeSlot 
          key={slot.slot_id} 
          slot={slot}
          disabled={slot.status !== 'available'}
        />
      ))}
    </div>
  );
}

Booking Flow

The SDK provides a complete multi-step booking flow:

Step-by-Step Approach

import { useCreateBooking, useLockSlot, useConfirmBooking } from 'natural-path-sdk';

function BookingPage() {
  const createBooking = useCreateBooking();
  const lockSlot = useLockSlot();
  const confirmBooking = useConfirmBooking();

  // Step 1: Initiate booking
  const handleSelectSlot = async () => {
    const booking = await createBooking.mutateAsync({
      service_id: 'service-id',
      practitioner_id: 'practitioner-id',
      slot: {
        date: '2026-03-25',
        start_time: '10:00',
        end_time: '11:00'
      },
      notes: 'First time customer'
    });
    
    // Step 2: Lock the slot (5 min hold)
    await lockSlot.mutateAsync(booking.booking_id);
    
    // Step 3: Confirm and pay
    const confirmed = await confirmBooking.mutateAsync({
      booking_id: booking.booking_id,
      payment_method: 'card'
    });
  };
}

Combined Flow Hook (Recommended)

import { useBookingFlow } from 'natural-path-sdk';

function BookingWizard() {
  const {
    initiateBooking,
    lockSlot,
    confirmBooking,
    currentStep,
    bookingId,
    booking,
    isLoading,
    error,
    reset
  } = useBookingFlow();

  const handleBook = async () => {
    // Step 1: Initiate
    await initiateBooking({
      service_id: selectedService,
      practitioner_id: selectedPractitioner,
      slot: selectedSlot
    });

    // Step 2: Lock slot
    await lockSlot();

    // Step 3: Confirm with payment
    await confirmBooking('card');
  };

  return (
    <div>
      <p>Current step: {currentStep}</p>
      {isLoading && <Spinner />}
      {error && <ErrorMessage error={error} />}
      <button onClick={handleBook}>Complete Booking</button>
    </div>
  );
}

Cancel Booking

import { useCancelBooking } from 'natural-path-sdk';

const { mutate: cancelBooking } = useCancelBooking();

cancelBooking({
  booking_id: 'booking-id',
  reason: 'Schedule conflict'
});

User Profile

import { useProfile, useUpdateProfile, useUserBookings } from 'natural-path-sdk';

function ProfilePage() {
  const { data: profile } = useProfile();
  const { mutate: updateProfile } = useUpdateProfile();
  const { data: myBookings } = useUserBookings();

  const handleUpdate = () => {
    updateProfile({
      phone: '+1234567890',
      first_name: 'Updated Name'
    });
  };

  return (
    <div>
      <h1>{profile?.first_name} {profile?.last_name}</h1>
      <h2>My Bookings</h2>
      {myBookings?.map(booking => (
        <BookingCard key={booking.booking_id} booking={booking} />
      ))}
    </div>
  );
}

Notifications

import { useNotifications, useMarkNotificationRead } from 'natural-path-sdk';

function NotificationsPanel() {
  const { data: notifications } = useNotifications();
  const { mutate: markAsRead } = useMarkNotificationRead();

  return (
    <div>
      {notifications?.map(notif => (
        <div 
          key={notif.notification_id}
          onClick={() => markAsRead(notif.notification_id)}
        >
          <h4>{notif.title}</h4>
          <p>{notif.message}</p>
        </div>
      ))}
    </div>
  );
}

Real-time Notifications (WebSocket)

import { useRealtimeNotifications } from 'natural-path-sdk';

function App() {
  const { isConnected } = useRealtimeNotifications(
    userId,
    (notification) => {
      // Show toast notification
      toast.info(notification.title);
    }
  );
}

Admin Dashboard

Dashboard Stats

import { useAdminStats } from 'natural-path-sdk';

function AdminDashboard() {
  const { data: stats } = useAdminStats();

  return (
    <div>
      <StatCard title="Total Customers" value={stats?.total_customers} />
      <StatCard title="Today's Revenue" value={stats?.revenue_today} />
      <StatCard title="Bookings Today" value={stats?.bookings_today} />
    </div>
  );
}

Booking Analytics

import { useBookingAnalytics } from 'natural-path-sdk';

const { data: weeklyAnalytics } = useBookingAnalytics('week');
const { data: monthlyAnalytics } = useBookingAnalytics('month');

User Management

import { useUsers, useUpdateUserRole, useUpdateUserStatus } from 'natural-path-sdk';

function UserManagement() {
  const { data: users } = useUsers();
  const { mutate: updateRole } = useUpdateUserRole();
  const { mutate: updateStatus } = useUpdateUserStatus();

  const makeAdmin = (userId: string) => {
    updateRole({ userId, role: 'admin' });
  };

  const deactivateUser = (userId: string) => {
    updateStatus({ userId, isActive: false });
  };
}

Utilities

import { 
  formatDate, 
  formatCurrency, 
  formatDuration,
  getWeekRange 
} from 'natural-path-sdk';

formatDate(new Date());        // '2026-03-25'
formatCurrency(150);           // '$150.00'
formatDuration(90);            // '1 hr 30 min'
getWeekRange();                // { start: '2026-03-23', end: '2026-03-29' }

Advanced Usage

Custom Query Client

import { NaturalPathProvider, QueryClient } from 'natural-path-sdk';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 10 * 60 * 1000, // 10 minutes
    },
  },
});

<NaturalPathProvider baseUrl="..." queryClient={queryClient}>
  <App />
</NaturalPathProvider>

Custom Token Storage

import { NaturalPathProvider } from 'natural-path-sdk';

const secureStorage = {
  getAccessToken: () => secureStore.get('access_token'),
  getRefreshToken: () => secureStore.get('refresh_token'),
  setTokens: (access, refresh) => {
    secureStore.set('access_token', access);
    secureStore.set('refresh_token', refresh);
  },
  clearTokens: () => {
    secureStore.delete('access_token');
    secureStore.delete('refresh_token');
  },
};

<NaturalPathProvider baseUrl="..." tokenStorage={secureStorage}>
  <App />
</NaturalPathProvider>

Direct API Access

import { servicesApi, getApiClient } from 'natural-path-sdk';

// Use pre-built API methods
const services = await servicesApi.getAll();

// Or use the axios client directly
const client = getApiClient();
const response = await client.get('/api/custom-endpoint');

Manual Cache Invalidation

import { useQueryClient, queryKeys } from 'natural-path-sdk';

function RefreshButton() {
  const queryClient = useQueryClient();

  const refresh = () => {
    queryClient.invalidateQueries({ queryKey: queryKeys.services.all });
  };

  return <button onClick={refresh}>Refresh Services</button>;
}

TypeScript Support

All types are exported:

import type { 
  Service, 
  Practitioner, 
  Booking, 
  User,
  AvailabilitySlot 
} from 'natural-path-sdk';

interface ServiceCardProps {
  service: Service;
}

API Reference

Hooks

| Hook | Description | |------|-------------| | useAuth() | Authentication state and methods | | useServices() | Fetch all services | | useFeaturedServices() | Fetch featured services | | useService(id) | Fetch single service | | usePractitioners() | Fetch all practitioners | | usePractitioner(id) | Fetch single practitioner | | useAvailability(id, date) | Fetch availability slots | | useBookingFlow() | Complete booking flow | | useUserBookings() | User's bookings | | useProfile() | User profile | | useAdminStats() | Admin dashboard stats | | useRealtimeAvailability(id, date) | WebSocket availability |

License

MIT