My App

Examples

Practical examples using the Unchange SDK

Basic Setup

import { Unchange } from 'unchange-sdk';

// Initialize the client
const client = new Unchange({
  apiKey: process.env.UNCHANGE_API_KEY!,
  projectId: process.env.UNCHANGE_PROJECT_ID!,
});

Display Latest Changelog to User

Show the latest changelog to a user and track whether they've seen it:

async function showLatestChangelog(userId: string) {
  try {
    const changelog = await client.changelogs.getLatestForUser(userId);
    
    if (!changelog.seen) {
      // Display the changelog to the user
      console.log(`New Update: ${changelog.title}`);
      console.log(changelog.content);
      
      // Mark as seen after user acknowledges
      await client.changelogs.acknowledge(changelog._id, userId);
      console.log('Changelog marked as seen');
    } else {
      console.log('User has already seen the latest changelog');
    }
  } catch (error) {
    console.error('Error displaying changelog:', error);
  }
}

// Usage
await showLatestChangelog('user-123');

List All Changelogs with Formatting

Fetch and display all changelogs with proper date formatting:

async function displayAllChangelogs() {
  try {
    const changelogs = await client.changelogs.list();
    
    changelogs.forEach(changelog => {
      const date = new Date(changelog._creationTime);
      console.log(`
📢 ${changelog.title}
📅 ${date.toLocaleDateString()}
${changelog.content}
${'─'.repeat(50)}
      `);
    });
  } catch (error) {
    console.error('Error fetching changelogs:', error);
  }
}

await displayAllChangelogs();

User Onboarding Flow

Create a user when they sign up and show them the latest changelog:

async function onboardUser(userId: string, username: string) {
  try {
    // Create the user in the system
    const response = await client.users.create(userId);
    console.log(`✓ ${response.message}`);
    
    // Get the latest changelog
    const latest = await client.changelogs.getLatest();
    
    if (latest.length > 0) {
      console.log(`
Welcome, ${username}! 🎉
Here's what's new:

${latest[0].title}
${latest[0].content}
      `);
    }
  } catch (error) {
    console.error('Error during onboarding:', error);
  }
}

await onboardUser('user-456', 'Alice');

React Component Example

Integrate the SDK into a React component using React Query:

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Unchange, type ChangelogWithSeenStatus } from 'unchange-sdk';

const client = new Unchange({
  apiKey: process.env.NEXT_PUBLIC_UNCHANGE_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_UNCHANGE_PROJECT_ID!,
});

export function ChangelogNotification({ userId }: { userId: string }) {
  const queryClient = useQueryClient();

  // Fetch latest changelog for user
  const { data: changelog, isLoading } = useQuery({
    queryKey: ['changelog', 'latest', userId],
    queryFn: () => client.changelogs.getLatestForUser(userId),
  });

  // Acknowledge changelog mutation
  const acknowledgeMutation = useMutation({
    mutationFn: (changelogId: string) =>
      client.changelogs.acknowledge(changelogId, userId),
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['changelog', 'latest', userId] });
    },
  });

  const handleAcknowledge = () => {
    if (!changelog) return;
    acknowledgeMutation.mutate(changelog._id);
  };

  if (isLoading) return <div>Loading...</div>;
  if (!changelog || changelog.seen) return null;

  return (
    <div className="notification">
      <h3>{changelog.title}</h3>
      <p>{changelog.content}</p>
      <button
        onClick={handleAcknowledge}
        disabled={acknowledgeMutation.isPending}
      >
        {acknowledgeMutation.isPending ? 'Marking...' : 'Mark as Read'}
      </button>
    </div>
  );
}

Setup React Query Provider

Don't forget to wrap your app with QueryClientProvider:

// app/providers.tsx
'use client';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';

export function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000, // 1 minute
      },
    },
  }));

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

Advanced React Query Patterns

Fetch All Changelogs

import { useQuery } from '@tanstack/react-query';

function ChangelogList() {
  const { data: changelogs, isLoading, error } = useQuery({
    queryKey: ['changelogs'],
    queryFn: () => client.changelogs.list(),
  });

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

  return (
    <div>
      {changelogs?.map(changelog => (
        <article key={changelog._id}>
          <h2>{changelog.title}</h2>
          <p>{changelog.content}</p>
          <time>{new Date(changelog._creationTime).toLocaleDateString()}</time>
        </article>
      ))}
    </div>
  );
}

Create User Mutation

import { useMutation } from '@tanstack/react-query';

function UserOnboarding({ userId }: { userId: string }) {
  const createUserMutation = useMutation({
    mutationFn: (userId: string) => client.users.create(userId),
    onSuccess: (data) => {
      console.log(data.message);
    },
    onError: (error) => {
      console.error('Failed to create user:', error);
    },
  });

  return (
    <button
      onClick={() => createUserMutation.mutate(userId)}
      disabled={createUserMutation.isPending}
    >
      {createUserMutation.isPending ? 'Creating...' : 'Create Account'}
    </button>
  );
}

Custom Hook Pattern

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Custom hook for changelog operations
function useChangelog(userId: string) {
  const queryClient = useQueryClient();

  const latest = useQuery({
    queryKey: ['changelog', 'latest', userId],
    queryFn: () => client.changelogs.getLatestForUser(userId),
    enabled: !!userId,
  });

  const acknowledge = useMutation({
    mutationFn: (changelogId: string) =>
      client.changelogs.acknowledge(changelogId, userId),
    onSuccess: () => {
      queryClient.invalidateQueries({ 
        queryKey: ['changelog', 'latest', userId] 
      });
    },
  });

  return {
    changelog: latest.data,
    isLoading: latest.isLoading,
    isError: latest.isError,
    acknowledge: acknowledge.mutate,
    isAcknowledging: acknowledge.isPending,
  };
}

// Usage
function MyComponent({ userId }: { userId: string }) {
  const { changelog, isLoading, acknowledge, isAcknowledging } = useChangelog(userId);

  if (isLoading) return <div>Loading...</div>;
  if (!changelog || changelog.seen) return null;

  return (
    <div>
      <h3>{changelog.title}</h3>
      <p>{changelog.content}</p>
      <button onClick={() => acknowledge(changelog._id)} disabled={isAcknowledging}>
        Mark as Read
      </button>
    </div>
  );
}

Next.js API Route Example

Create a server-side API route to fetch changelogs:

// app/api/changelogs/route.ts
import { NextResponse } from 'next/server';
import { Unchange } from 'unchange-sdk';

const client = new Unchange({
  apiKey: process.env.UNCHANGE_API_KEY!,
  projectId: process.env.UNCHANGE_PROJECT_ID!,
});

export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url);
    const userId = searchParams.get('userId');

    if (userId) {
      const changelog = await client.changelogs.getLatestForUser(userId);
      return NextResponse.json(changelog);
    } else {
      const changelogs = await client.changelogs.list();
      return NextResponse.json(changelogs);
    }
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch changelogs' },
      { status: 500 }
    );
  }
}

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { changelogId, userId } = body;

    await client.changelogs.acknowledge(changelogId, userId);
    return NextResponse.json({ success: true });
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to acknowledge changelog' },
      { status: 500 }
    );
  }
}

Cleanup on User Deletion

Delete a user when they close their account:

async function handleAccountDeletion(userId: string) {
  try {
    // Delete user and their changelog acknowledgments
    await client.users.delete(userId);
    console.log(`User ${userId} successfully deleted`);
  } catch (error) {
    console.error('Error deleting user:', error);
    throw error;
  }
}

await handleAccountDeletion('user-789');

Batch Operations

Process multiple users efficiently:

async function acknowledgeForMultipleUsers(
  changelogId: string,
  userIds: string[]
) {
  const results = await Promise.allSettled(
    userIds.map(userId => 
      client.changelogs.acknowledge(changelogId, userId)
    )
  );

  const successful = results.filter(r => r.status === 'fulfilled').length;
  const failed = results.filter(r => r.status === 'rejected').length;

  console.log(`✓ ${successful} users acknowledged`);
  if (failed > 0) {
    console.log(`✗ ${failed} failed`);
  }
}

await acknowledgeForMultipleUsers('changelog-1', [
  'user-1', 'user-2', 'user-3'
]);

Error Handling Pattern

Robust error handling with retry logic:

async function fetchWithRetry<T>(
  operation: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  let lastError: Error;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;
      console.log(`Attempt ${i + 1} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }

  throw lastError!;
}

// Usage
const changelogs = await fetchWithRetry(() => 
  client.changelogs.list()
);

On this page