npm package: @forgedb/client
Description: ForgeDB JavaScript Client — Supabase-compatible interface for ForgeDB backends
Latest version: 0.1.0

# @forgedb/client

> ForgeDB JavaScript/TypeScript SDK — Supabase-compatible interface for apps built with BlueForge

---

## Installation

```bash
npm install @forgedb/client
# or
yarn add @forgedb/client
# or
pnpm add @forgedb/client
```

---

## Quick Start

```ts
import { createClient } from '@forgedb/client'

const forgedb = createClient({
  url: 'https://api.forgedb.cloud',        // ForgeDB server URL
  projectId: 'your-project-id',            // Project ID from ForgeDB console
  anonKey: 'your-anon-key',               // Anon key from ForgeDB console
})
```

---

## API Reference

### Auth

Mirrors `@supabase/supabase-js` Auth API exactly.

```ts
// Sign up
const { data, error } = await forgedb.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  display_name: 'John Doe',
})

// Sign in with password
const { data, error } = await forgedb.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password',
})

// Sign in with OAuth (Google / GitHub)
const { data } = await forgedb.auth.signInWithOAuth({ provider: 'google' })
window.location.href = data.url  // Redirect to Google

// Handle OAuth callback
const { data } = await forgedb.auth.handleOAuthCallback({
  provider: 'google',
  code: new URLSearchParams(window.location.search).get('code')!,
})

// Get current session
const { data: { session } } = await forgedb.auth.getSession()

// Get current user
const { data: { user } } = await forgedb.auth.getUser()

// Listen for auth changes
forgedb.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') console.log('User signed in:', session?.user)
  if (event === 'SIGNED_OUT') console.log('User signed out')
})

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

// Password reset
await forgedb.auth.resetPasswordForEmail('user@example.com')

// Refresh session
await forgedb.auth.refreshSession()
```

---

### Database (Query Builder)

Mirrors `@supabase/supabase-js` PostgREST query builder.

```ts
// SELECT
const { data, error } = await forgedb
  .from('posts')
  .select('id, title, content, author_id')
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .limit(10)

// SELECT with filters
const { data } = await forgedb
  .from('products')
  .select('*')
  .gte('price', 10)
  .lte('price', 100)
  .ilike('name', '%shirt%')
  .in('category', ['tops', 'outerwear'])

// SELECT single row
const { data: post, error } = await forgedb
  .from('posts')
  .select('*')
  .eq('id', postId)
  .single()

// INSERT
const { data, error } = await forgedb
  .from('posts')
  .insert({ title: 'Hello World', content: 'My first post', status: 'draft' })
  .select()
  .single()

// INSERT multiple rows
const { data } = await forgedb
  .from('tags')
  .insert([
    { name: 'typescript' },
    { name: 'forgedb' },
  ])
  .select()

// UPDATE
const { data } = await forgedb
  .from('posts')
  .update({ status: 'published', published_at: new Date().toISOString() })
  .eq('id', postId)
  .select()
  .single()

// DELETE
const { error } = await forgedb
  .from('posts')
  .delete()
  .eq('id', postId)

// UPSERT
const { data } = await forgedb
  .from('user_settings')
  .upsert({ user_id: userId, theme: 'dark' }, { onConflict: 'user_id' })
  .select()

// Pagination
const { data, count } = await forgedb
  .from('posts')
  .select('*', { count: 'exact' })
  .range(0, 19)   // rows 0-19 (first 20)
```

#### Supported Filter Operators

| Method | Operator | Example |
|--------|----------|---------|
| `.eq(col, val)` | `=` | `.eq('status', 'active')` |
| `.neq(col, val)` | `!=` | `.neq('role', 'admin')` |
| `.gt(col, val)` | `>` | `.gt('age', 18)` |
| `.gte(col, val)` | `>=` | `.gte('score', 90)` |
| `.lt(col, val)` | `<` | `.lt('price', 100)` |
| `.lte(col, val)` | `<=` | `.lte('stock', 5)` |
| `.like(col, pattern)` | `LIKE` | `.like('name', 'John%')` |
| `.ilike(col, pattern)` | `ILIKE` | `.ilike('email', '%@gmail%')` |
| `.is(col, null)` | `IS NULL` | `.is('deleted_at', null)` |
| `.in(col, [v1, v2])` | `IN (...)` | `.in('id', [1, 2, 3])` |

---

### Realtime

```ts
// Subscribe to all changes on a table
forgedb
  .channel('public:posts')
  .on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, (payload) => {
    console.log('Change type:', payload.eventType)  // INSERT | UPDATE | DELETE
    console.log('New data:', payload.new)
    console.log('Old data:', payload.old)
  })
  .subscribe((status) => {
    console.log('Realtime status:', status)
  })

// Subscribe to specific events
forgedb
  .channel('new-messages')
  .on('postgres_changes', { event: 'INSERT', table: 'messages' }, (payload) => {
    addMessage(payload.new)
  })
  .on('postgres_changes', { event: 'DELETE', table: 'messages' }, (payload) => {
    removeMessage(payload.old.id)
  })
  .subscribe()

// Broadcast (send custom events to channel subscribers)
const channel = forgedb.channel('room-1')
channel.send({ type: 'broadcast', event: 'cursor', payload: { x: 100, y: 200 } })

// Unsubscribe
await forgedb.removeChannel(channel)
await forgedb.removeAllChannels()
```

---

### Storage

```ts
// Upload file
const { data, error } = await forgedb.storage
  .from('avatars')
  .upload(`users/${userId}.jpg`, file, {
    contentType: 'image/jpeg',
    upsert: true,
  })

// Download file
const { data: blob } = await forgedb.storage
  .from('documents')
  .download('report.pdf')

// Get public URL
const { data: { publicUrl } } = forgedb.storage
  .from('avatars')
  .getPublicUrl(`users/${userId}.jpg`)

// List files
const { data: files } = await forgedb.storage
  .from('documents')
  .list('reports/', { limit: 50 })

// Delete files
await forgedb.storage.from('avatars').remove([`users/${userId}.jpg`])

// Create signed URL (temporary access)
const { data: { signedUrl } } = await forgedb.storage
  .from('private-docs')
  .createSignedUrl('contract.pdf', 3600)  // expires in 1 hour
```

---

### Edge Functions

```ts
// Invoke a function
const { data, error } = await forgedb.functions.invoke('send-email', {
  body: {
    to: 'user@example.com',
    subject: 'Welcome to our app!',
    template: 'welcome',
  },
})

// With custom headers
const { data } = await forgedb.functions.invoke('process-payment', {
  body: { amount: 9900, currency: 'USD' },
  headers: { 'Idempotency-Key': crypto.randomUUID() },
})
```

---

### RPC (Stored Procedures)

```ts
// Call a PostgreSQL function
const { data, error } = await forgedb.rpc('deduct_credit', {
  p_user_id: userId,
  p_amount: 5,
  p_action: 'llm_call',
})
```

---

## Migrating from Supabase

| Supabase | ForgeDB | Notes |
|---------|---------|-------|
| `createClient(url, anonKey)` | `createClient({ url, projectId, anonKey })` | Add `projectId` |
| `supabase.auth.signUp(...)` | `forgedb.auth.signUp(...)` | Identical |
| `supabase.auth.signInWithPassword(...)` | `forgedb.auth.signInWithPassword(...)` | Identical |
| `supabase.auth.signInWithOAuth(...)` | `forgedb.auth.signInWithOAuth(...)` | Identical |
| `supabase.from('table').select(...)` | `forgedb.from('table').select(...)` | Identical |
| `supabase.storage.from('bucket')...` | `forgedb.storage.from('bucket')...` | Identical |
| `supabase.functions.invoke(...)` | `forgedb.functions.invoke(...)` | Identical |
| `supabase.channel(...)` | `forgedb.channel(...)` | Identical |
| `supabase.rpc('func', params)` | `forgedb.rpc('func', params)` | Identical |

### RLS Policy Update Required

Replace `auth.uid()` with `current_setting('request.jwt.claim.sub', true)::uuid`:

```sql
-- Before (Supabase)
CREATE POLICY "Users can read own data"
  ON posts FOR SELECT
  USING (author_id = auth.uid());

-- After (ForgeDB)
CREATE POLICY "Users can read own data"
  ON posts FOR SELECT
  USING (author_id = current_setting('request.jwt.claim.sub', true)::uuid);
```

Or create a compatibility function:
```sql
CREATE OR REPLACE FUNCTION auth.uid() RETURNS uuid AS $$
  SELECT current_setting('request.jwt.claim.sub', true)::uuid
$$ LANGUAGE sql STABLE;
```

---

## Environment Variables

```env
VITE_FORGEDB_URL=https://api.forgedb.cloud
VITE_FORGEDB_PROJECT_ID=your-project-id
VITE_FORGEDB_ANON_KEY=your-anon-key
```

```ts
const forgedb = createClient({
  url: import.meta.env.VITE_FORGEDB_URL,
  projectId: import.meta.env.VITE_FORGEDB_PROJECT_ID,
  anonKey: import.meta.env.VITE_FORGEDB_ANON_KEY,
})
```

---

## License

MIT © ForgeDB