npm package: @startsimpli/api
Description: Type-safe Django REST API client for StartSimpli apps
Latest version: 0.5.6

# @startsimpli/api

Type-safe Django REST API client for StartSimpli Next.js applications.

## Overview

This package provides a type-safe TypeScript client for the Django REST API backend (`start-simpli-api`). It handles authentication, error normalization, pagination, and provides endpoint wrappers for all Django models.

**IMPORTANT**: This is a Django REST API client - NO Prisma, NO database code. All data lives in the Django backend.

## Installation

```bash
npm install @startsimpli/api
```

## Usage

### Basic Setup

```typescript
import { createStartSimpliApi } from '@startsimpli/api';

const api = createStartSimpliApi({
  baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1',
  getToken: async () => {
    // Get token from your auth provider
    const session = await getServerSession();
    return session?.accessToken || null;
  },
});

// Use API endpoints
const contacts = await api.contacts.list({ tier: 1 });
const orgs = await api.organizations.getByStage('seed');
```

### Contacts API

```typescript
// List contacts with filters
const contacts = await api.contacts.list(
  { tier: 1, has_linkedin: true }, // filters
  { page: 1, page_size: 20 },      // pagination
  { ordering: '-created_at' }       // sorting
);

// Get single contact
const contact = await api.contacts.get('contact-uuid');

// Create contact with assertions
const newContact = await api.contacts.create({
  name: 'John Doe',
  email: 'john@example.com',
  title: 'Partner',
  write_tags: ['tier_1', 'focus:fintech'],
  write_metrics: { enrichment_score: 0.95 },
  write_profiles: { linkedin: 'https://linkedin.com/in/johndoe' },
});

// Update contact
const updated = await api.contacts.update('contact-uuid', {
  title: 'Managing Partner',
  write_tags: ['tier_1', 'vip'],
});

// Search contacts
const results = await api.contacts.search('john');

// Get contacts by firm
const firmContacts = await api.contacts.getByFirm('firm-uuid');
```

### Organizations API

```typescript
// List organizations with filters
const orgs = await api.organizations.list(
  {
    tier: 1,
    stage: 'seed',
    check_size_min_gte: 100000,
    check_size_max_lte: 1000000,
  },
  { page: 1, page_size: 20 },
  { ordering: '-aum' }
);

// Get single organization
const org = await api.organizations.get('org-uuid');

// Create organization with assertions
const newOrg = await api.organizations.create({
  name: 'Acme Ventures',
  domain: 'acmevc.com',
  location: 'San Francisco, CA',
  write_tags: ['tier_1', 'stage:seed', 'focus:fintech'],
  write_metrics: {
    check_size_min: 500000,
    check_size_max: 2000000,
    aum: 100000000,
  },
  write_profiles: {
    linkedin: 'https://linkedin.com/company/acme-ventures',
    website: 'https://acmevc.com',
  },
});

// Get by check size range
const firms = await api.organizations.getByCheckSizeRange(100000, 1000000);
```

### Entities API (Low-level)

```typescript
// Tags
const tags = await api.entities.listTags();

// Entity Tags
const entityTags = await api.entities.listEntityTags(
  { page: 1, page_size: 50 },
  { entity_id: 'some-uuid', category: 'focus' }
);

// Metrics
const metrics = await api.entities.listMetrics(
  undefined,
  { entity_id: 'some-uuid', type: 'financial' }
);

// Profiles
const profiles = await api.entities.listProfiles(
  undefined,
  { entity_id: 'some-uuid', type: 'professional' }
);
```

### Using Direct Client

For custom endpoints not covered by wrappers:

```typescript
import { createApiClient } from '@startsimpli/api';

const client = createApiClient({
  baseUrl: 'http://localhost:8000/api/v1',
  getToken: async () => getAccessToken(),
});

// Direct fetch calls
const data = await client.fetch.get('/custom-endpoint/', {
  params: { key: 'value' }
});

const created = await client.fetch.post('/custom-endpoint/', {
  name: 'test'
});
```

## Next.js API Routes Middleware

### With Auth

```typescript
import { withAuth } from '@startsimpli/api/middleware';

export const GET = withAuth(async (request, context) => {
  const { userId, token, isAuthenticated } = context;

  if (!isAuthenticated) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Use token to call Django API
  return NextResponse.json({ userId, token });
});
```

### With Validation

```typescript
import { withValidation } from '@startsimpli/api/middleware';
import { z } from 'zod';

const createContactSchema = z.object({
  name: z.string().min(1),
  email: z.string().email().optional(),
  tier: z.number().int().min(1).max(3).optional(),
});

export const POST = withValidation(
  async (request, { body }) => {
    // body is typed and validated
    const contact = await createContact(body);
    return NextResponse.json(contact);
  },
  { body: createContactSchema }
);
```

### With Error Handling

```typescript
import { withErrorHandling } from '@startsimpli/api/middleware';

export const GET = withErrorHandling(async (request) => {
  // Errors are automatically caught and formatted
  const data = await somethingThatMightThrow();
  return NextResponse.json(data);
});
```

### Composing Middleware

```typescript
import { withAuth, withErrorHandling, withValidation } from '@startsimpli/api/middleware';
import { z } from 'zod';

const schema = z.object({ name: z.string() });

export const POST = withErrorHandling(
  withAuth(
    withValidation(
      async (request, { body }, authContext) => {
        // Fully typed, validated, and authenticated
        return NextResponse.json({ success: true });
      },
      { body: schema }
    )
  )
);
```

## Error Handling

```typescript
import {
  isApiException,
  isValidationError,
  isAuthError,
  isNotFoundError,
} from '@startsimpli/api';

try {
  const contact = await api.contacts.get('invalid-id');
} catch (error) {
  if (isValidationError(error)) {
    console.log('Validation errors:', error.errors);
  } else if (isAuthError(error)) {
    console.log('Auth error:', error.status);
  } else if (isNotFoundError(error)) {
    console.log('Contact not found');
  } else if (isApiException(error)) {
    console.log('API error:', error.message);
  }
}
```

## TypeScript Types

All types are exported for use in your application:

```typescript
import type {
  Contact,
  Organization,
  Entity,
  Tag,
  Metric,
  Profile,
  Attribute,
  PaginatedResponse,
  ApiError,
  CreateContactRequest,
  UpdateContactRequest,
  ContactFilters,
} from '@startsimpli/api';
```

## Architecture

### Django Integration

This package is designed to work with the Django REST API:

- **Base URL**: `http://localhost:8000/api/v1/` (configurable)
- **Auth**: Bearer token from `@startsimpli/auth`
- **Pagination**: Django REST Framework format (`page`, `page_size`)
- **Filtering**: Django-filter query params (`field__gte=100`, `field__in=1,2,3`)

### Entity System

Django uses a generic Entity model with assertions (Tags, Metrics, Profiles, Attributes):

```typescript
// Entity with assertions
{
  id: 'uuid',
  entity_type: 'contact',

  // Canonical fields
  name: 'John Doe',
  email: 'john@example.com',

  // Computed from assertions
  tier: 1,  // from tags
  linkedin: 'https://...', // from profiles
  enrichment_score: 0.95, // from metrics

  // Full assertions
  tags: [{ category: 'quality', name: 'tier_1', ... }],
  metrics: [{ type: 'quality', subtype: 'enrichment_score', value: 0.95 }],
  profiles: [{ type: 'professional', subtype: 'linkedin', identifier: '...' }],
}
```

## Development

```bash
# Run tests
npm test

# Type check
npm run type-check
```

## License

MIT