npm package: @standardbackend/core
Description: TypeScript SDK for Standard Backend authentication platform
Latest version: 0.1.2

# @standardbackend/core

TypeScript SDK for the [Standard Backend](https://standardbackend.com) authentication platform. ESM only, zero runtime dependencies.

## Install

```bash
npm install @standardbackend/core
```

## Quick start

### Client SDK (browser / frontend)

Use `StandardAuth` for end-user authentication flows. Requires a publishable API key (`pk_*`).

```typescript
import { StandardAuth } from '@standardbackend/core'

const auth = new StandardAuth({
  publishableKey: 'pk_live_...',
  baseUrl: 'https://api.yourapp.com',
})

// Sign up
const { user, tokens } = await auth.signUp({
  email: 'jane@example.com',
  password: 'securepassword',
  passwordConfirm: 'securepassword',
  firstName: 'Jane',
  lastName: 'Doe',
})

// Sign in
const tokens = await auth.signIn({
  email: 'jane@example.com',
  password: 'securepassword',
})

// Get current user
const user = await auth.getUser()

// Sign out
await auth.signOut()
```

### Admin SDK (server-side)

Use `StandardAuthAdmin` for server-side user management. Requires a secret API key (`sk_*`). Never expose this key in client-side code.

```typescript
import { StandardAuthAdmin } from '@standardbackend/core'

const admin = new StandardAuthAdmin({
  secretKey: 'sk_live_...',
  baseUrl: 'https://api.yourapp.com',
})

// List users
const page = await admin.users.list({ pageSize: 25 })
console.log(page.results) // User[]

// Create a user
const user = await admin.users.create({
  email: 'new@example.com',
  password: 'securepassword',
  passwordConfirm: 'securepassword',
})

// Ban a user
await admin.users.ban(user.id)
```

## Client SDK

### Configuration

```typescript
const auth = new StandardAuth({
  // Required
  publishableKey: 'pk_live_...',
  baseUrl: 'https://api.yourapp.com',

  // Optional
  tokenStorage: 'memory',     // 'memory' | 'localStorage' | custom adapter
  autoRefresh: true,           // auto-refresh JWT before expiry (default: true)
  timeout: 30_000,             // request timeout in ms (default: 30000)
  retries: 3,                  // retry on 5xx / network errors (default: no retries)
  onTokenChange: (tokens) => { /* sync with app state */ },
  onError: (error) => { /* global error handler */ },
})
```

### Authentication

```typescript
// Sign up — returns user and tokens
const { user, tokens } = await auth.signUp({
  email: 'jane@example.com',
  password: 'securepassword',
  passwordConfirm: 'securepassword',
  firstName: 'Jane',       // optional
  lastName: 'Doe',         // optional
})

// Sign in — returns tokens
const tokens = await auth.signIn({
  email: 'jane@example.com',
  password: 'securepassword',
})

// Sign out — revokes refresh token
await auth.signOut()

// Manually refresh the access token
const newTokens = await auth.refreshToken()
```

### User profile

```typescript
// Get current user
const user = await auth.getUser()

// Update profile
const updated = await auth.updateUser({
  firstName: 'Jane',
  lastName: 'Smith',
  profile: {
    bio: 'Hello!',
    phoneNumber: '+1234567890',
  },
})
```

### Password reset

```typescript
// Request a reset email
await auth.requestPasswordReset({ email: 'jane@example.com' })

// Confirm with uid/token from the email link
await auth.confirmPasswordReset({
  uid: 'MQ',
  token: 'abc123-def456',
  password: 'newpassword',
  passwordConfirm: 'newpassword',
})
```

### Sessions

```typescript
// List active sessions
const sessions = await auth.sessions.list()

// Revoke a session
await auth.sessions.revoke(sessionId)
```

### Token storage

Built-in adapters:

- `'memory'` (default) — tokens stored in-memory, lost on page refresh
- `'localStorage'` — tokens persisted in `localStorage`

Custom adapter:

```typescript
import type { TokenStorage, Tokens } from '@standardbackend/core'

const cookieStorage: TokenStorage = {
  get(): Tokens | null {
    // read from cookies
  },
  set(tokens: Tokens): void {
    // write to cookies
  },
  clear(): void {
    // delete cookies
  },
}

const auth = new StandardAuth({
  publishableKey: 'pk_live_...',
  baseUrl: 'https://api.yourapp.com',
  tokenStorage: cookieStorage,
})
```

### Token lifecycle

When `autoRefresh` is enabled (the default), the SDK:

1. Schedules a refresh at ~80% of the access token's lifetime
2. On 401 responses, attempts one token refresh and retries the original request
3. Coalesces concurrent refresh attempts into a single request
4. Calls `onTokenChange(null)` when tokens are cleared (refresh failure, sign out)

## Admin SDK

### Configuration

```typescript
const admin = new StandardAuthAdmin({
  // Required
  secretKey: 'sk_live_...',
  baseUrl: 'https://api.yourapp.com',

  // Optional
  timeout: 30_000,       // request timeout in ms (default: 30000)
  retries: 3,            // retry on 5xx / network errors (default: no retries)
  onError: (error) => { /* global error handler */ },
})
```

### Users

```typescript
// List with pagination and filters
const page = await admin.users.list({
  pageSize: 25,
  search: 'jane',
  isActive: true,
  emailVerified: true,
  ordering: '-date_joined',
})

// CRUD
const user = await admin.users.create({ email, password, passwordConfirm })
const user = await admin.users.get(id)
const user = await admin.users.update(id, { firstName: 'Jane' })
await admin.users.delete(id)

// Actions
await admin.users.ban(id)
await admin.users.unban(id)
await admin.users.verifyEmail(id)
await admin.users.resetPassword(id, { password, passwordConfirm })

// Sessions
const sessions = await admin.users.listSessions(id)
await admin.users.revokeSessions(id)
await admin.users.deleteSessions(id)
```

### Pagination

`admin.users.list()` returns a `Page<User>` with cursor-based pagination.

```typescript
const page = await admin.users.list({ pageSize: 10 })

page.results   // User[] — current page
page.hasMore   // boolean — more pages available

// Navigate pages
const next = await page.next()     // Page<User> | null
const prev = await page.previous() // Page<User> | null
```

`Page` implements `AsyncIterable`, so you can iterate over all users across pages:

```typescript
for await (const user of admin.users.list()) {
  console.log(user.email)
}
```

### Bulk operations

```typescript
// Bulk create (atomic — all or nothing)
const result = await admin.users.bulkCreate({
  users: [
    { email: 'a@test.com', password: 'pass1', passwordConfirm: 'pass1' },
    { email: 'b@test.com', password: 'pass2', passwordConfirm: 'pass2' },
  ],
})
// { createdCount: 2, users: [...] }

// Bulk create (partial — individual success/failure)
const result = await admin.users.bulkCreate({
  users: [...],
  partial: true,
})
// { total: 5, succeeded: 4, failed: 1, results: [...] }

// Bulk update
await admin.users.bulkUpdate({
  updates: [
    { id: 1, firstName: 'Updated' },
    { id: 2, isActive: false },
  ],
})

// Bulk delete
await admin.users.bulkDelete({ ids: [1, 2, 3] })
```

### Import / Export

```typescript
// Import users from CSV
const file = new Blob([csvData], { type: 'text/csv' })
const { jobId } = await admin.users.import(file)

// Check import status
const status = await admin.users.importStatus(jobId)

// Export users (returns raw Response)
const response = await admin.users.export({ isActive: true })
const csv = await response.text()
```

### Project settings

```typescript
// Get project settings
const settings = await admin.project.get()

// Update settings
await admin.project.update({ name: 'My App' })

// List API keys
const keys = await admin.project.listApiKeys()

// Rotate API keys
const { apiKeyPublic, secretKey } = await admin.project.rotateKeys()
```

## Error handling

All API errors extend `StandardAuthError` with `status`, `code`, and an optional `details` object containing per-field validation errors.

```typescript
import {
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  ValidationError,
  RateLimitError,
  ServerError,
  NetworkError,
} from '@standardbackend/core'

try {
  await auth.signIn({ email, password })
} catch (error) {
  if (error instanceof ValidationError) {
    // error.status  — 400 or 422
    // error.details — { email: ['Already exists'], password: ['Too short'] }
  }
  if (error instanceof AuthenticationError) {
    // error.status — 401
    // error.code   — 'invalid_credentials' | 'token_expired' | ...
  }
  if (error instanceof RateLimitError) {
    // error.retryAfter — seconds until retry is allowed
  }
  if (error instanceof NetworkError) {
    // No response received (timeout, DNS failure, etc.)
  }
}
```

| Error class | Status | When |
|---|---|---|
| `AuthenticationError` | 401 | Invalid credentials, expired/invalid token |
| `AuthorizationError` | 403 | Insufficient permissions |
| `NotFoundError` | 404 | Resource not found |
| `ValidationError` | 400/422 | Invalid request data |
| `RateLimitError` | 429 | Too many requests |
| `ServerError` | 5xx | Server-side failure |
| `NetworkError` | — | No response (timeout, offline) |

### Error codes

Use `ErrorCode` constants for reliable code matching:

```typescript
import { ErrorCode } from '@standardbackend/core'

if (error.code === ErrorCode.TOKEN_EXPIRED) {
  // handle expired token
}
```

Available codes: `INVALID_CREDENTIALS`, `TOKEN_EXPIRED`, `TOKEN_INVALID`, `API_KEY_INVALID`, `API_KEY_REVOKED`, `API_KEY_EXPIRED`, `PROJECT_INACTIVE`, `ENVIRONMENT_INACTIVE`, `PERMISSION_DENIED`, `NOT_FOUND`, `VALIDATION_ERROR`, `RATE_LIMITED`, `SERVER_ERROR`, `NETWORK_ERROR`.

## Retry

When `retries` is set, the SDK automatically retries failed requests with exponential backoff and jitter:

- **Retried:** 5xx errors, network errors, 429 rate limits
- **Not retried:** 400, 401, 403, 404 (client errors are not transient)
- **429 behavior:** Uses the server's `Retry-After` header as the delay
- **Backoff formula:** `min(baseDelay * 2^attempt + jitter, maxDelay)`

```typescript
const auth = new StandardAuth({
  publishableKey: 'pk_live_...',
  baseUrl: 'https://api.yourapp.com',
  retries: 3, // up to 3 retries (4 total attempts)
})
```

## TypeScript

All types are exported for use in your application:

```typescript
import type {
  AuthUser,
  User,
  Profile,
  Session,
  Tokens,
  Page,
  StandardAuthConfig,
  StandardAuthAdminConfig,
} from '@standardbackend/core'
```

The SDK uses camelCase for all properties. The API's snake_case responses are transformed automatically.

## Requirements

- Node.js 18+ or any environment with `fetch` (browsers, Deno, Bun, Cloudflare Workers)
- ESM only (`"type": "module"`)

## License

MIT