# @nexabase/sdk

> **Official JavaScript/TypeScript SDK for NexaBase** - Build apps faster with auto-generated APIs

[![npm version](https://img.shields.io/npm/v/@nexabase/sdk.svg?style=flat-square)](https://www.npmjs.com/package/@nexabase/sdk)
[![npm downloads](https://img.shields.io/npm/dm/@nexabase/sdk.svg?style=flat-square)](https://www.npmjs.com/package/@nexabase/sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.0.0-green.svg?style=flat-square)](https://nodejs.org/)

---

## 📖 Quick Links

- **[Full Documentation](./docs)** - Complete guides and API reference
- **[NexaBase Platform](https://fabricadealgoritmos.site/)** - Learn about NexaBase BaaS
- **[Examples](./examples)** - Runnable code examples
- **[API Reference](./docs/API.md)** - Complete API documentation
- **[GitHub Issues](https://github.com/fmirpe/nexabase-sdk/issues)** - Report bugs or request features

---

## ✨ What is NexaBase?

**NexaBase** is a powerful Backend as a Service (BaaS) platform that automatically generates REST APIs from your data schemas. Think of it as **Supabase + Firebase** with auto-generated endpoints.

The `@nexabase/sdk` is the official JavaScript/TypeScript client that makes it easy to interact with your NexaBase backend from any JavaScript environment.

### Key Benefits

- 🚀 **Zero Backend Code** - Define your schema, get instant REST APIs
- 🔌 **Type-Safe** - Full TypeScript support with IntelliSense
- ⚡ **Real-time Ready** - WebSocket subscriptions out of the box
- 🔐 **Secure by Default** - JWT, API keys, OAuth support
- 📦 **All-in-One** - Auth, Storage, Functions, Webhooks included

---

## 📦 Installation

### npm

```bash
npm install @nexabase/sdk
```

### yarn

```bash
yarn add @nexabase/sdk
```

### pnpm

```bash
pnpm add @nexabase/sdk
```

### CDN (for browsers)

```html
<script src="https://cdn.jsdelivr.net/npm/@nexabase/sdk@latest/dist/index.min.js"></script>
```

---

## 🚀 Quick Start

### 1️⃣ Initialize the Client

```typescript
import { createApiClient } from '@nexabase/sdk';

const nexabase = createApiClient(
  'https://your-tenant.nexabase.app',
  'your-api-key'  // Get from NexaBase dashboard
);
```

### 2️⃣ Authenticate (Optional)

```typescript
// Sign in with email/password (API Key required)
const { access_token, user } = await nexabase.signInApi(
  'user@example.com',
  'your-password'
);

console.log(`Welcome, ${user.name}!`);
```

### 3️⃣ Start Using APIs

```typescript
// List all collections
const collections = await nexabase.listCollections();
console.log('Collections:', collections);

// Query documents with filters
const users = await nexabase.listDocuments('users', {
  filter: { status: 'active' },
  sort: '-created_at',
  page: 1,
  per_page: 20,
});

// Create a document
const newUser = await nexabase.createDocument('users', {
  name: 'John Doe',
  email: 'john@example.com',
  role: 'user',
});

// Update a document
await nexabase.updateDocument('users', newUser.id, {
  last_login: new Date().toISOString(),
});

// Delete a document
await nexabase.deleteDocument('users', userId);

// Complex Query with Logical Groups (NEW)
const { data } = await nexabase
  .from('products')
  .where('status', 'active')
  .whereGroup(q => {
    q.where('price', '>', 100).where('category', 'electronics')
  }, 'OR')
  .get();

// Create document with file upload (NEW)
const file = document.querySelector('input[type="file"]').files[0];
await nexabase.createDocumentWithFile('products', { name: 'New Item' }, file, 'image');

```

---

## 🎯 Core Features

### 🔐 Authentication

Multiple authentication methods supported:

```typescript
import { 
  createApiClient,      // API Key auth
  createTokenClient,    // Bearer token auth
  createAuthenticatedApiClient  // Auto login
} from '@nexabase/sdk';

// API Key (server-to-server)
const client = createApiClient(
  'https://api.nexabase.app',
  'your-api-key'
);

// Bearer Token (user-authenticated)
const client = createTokenClient(
  'https://api.nexabase.app',
  'jwt-token-here'
);

// Auto login (API Key + user credentials)
const client = await createAuthenticatedApiClient(
  'https://api.nexabase.app',
  'api-key',
  'user@example.com',
  'password'
);

// Subdomain client
const client = createSubdomainClient(
  'mytenant',      // Your tenant subdomain
  'api-key'
);
// Connects to: https://mytenant.nexabase.app
```

---

### 📚 Collections & Documents

Full CRUD operations with advanced querying:

```typescript
// Create collection
await nexabase.createCollection({
  name: 'products',
  schema: {
    fields: {
      name: { type: 'string', required: true, maxLength: 255 },
      price: { type: 'number', required: true, min: 0 },
      description: { type: 'text' },
      in_stock: { type: 'boolean', default: true },
    },
    timestamps: true,
  },
});

// List collections
const collections = await nexabase.listCollections();

// Get collection by name
const collection = await nexabase.getCollection('products');

// Update collection
await nexabase.updateCollection('products', {
  description: 'Product catalog',
  is_active: true,
});

// Delete collection
await nexabase.deleteCollection('products');
```

#### Document Operations

```typescript
// Create document
const product = await nexabase.createDocument('products', {
  name: 'iPhone 15',
  price: 999.99,
  in_stock: true,
});

// Get document
const doc = await nexabase.getDocument('products', productId);

// Update document
await nexabase.updateDocument('products', productId, {
  price: 899.99,
});

// Delete document
await nexabase.deleteDocument('products', productId);
```

---

### 🔍 Fluent Query Builder

Advanced querying with chainable syntax:

```typescript
import { NexaQuery } from '@nexabase/sdk';

// Basic query
const { data } = await nexabase
  .from('products')
  .select('id,name,price')
  .where('price', '>', 50)
  .where('in_stock', true)
  .sort('-created_at')
  .limit(10)
  .get();

// Advanced filters
const { data } = await nexabase
  .from('products')
  .where('category', 'in', ['electronics', 'books'])
  .where('price', 'between', [100, 500])
  .where('name', 'like', '%iphone%')
  .logical('OR')
  .get();

// Complex Logical Groups (v2.17.0+)
// Generates: (status = 'active') AND (price > 100 OR stock < 10)
const { data: complex } = await nexabase
  .from('products')
  .where('status', 'active')
  .whereGroup(q => {
    q.where('price', '>', 100).where('stock', '<', 10)
  }, 'OR')
  .get();


// Aggregations
const { data } = await nexabase
  .from('orders')
  .groupBy('status')
  .count('*', 'total')
  .sum('amount', 'total_amount')
  .avg('amount', 'avg_amount')
  .get();

// Date grouping
const { data } = await nexabase
  .from('orders')
  .groupBy('year(created_at)', 'month(created_at)')
  .count('*', 'total')
  .get();

// Search
const { data } = await nexabase
  .from('products')
  .search('iphone')
  .get();

// First result
const first = await nexabase
  .from('users')
  .where('email', 'test@example.com')
  .first();

// Date extractions and groups (NEW)
const { data: stats } = await nexabase
  .from('orders')
  .month('created_at', 'mes')
  .year('created_at', 'anio')
  .count('*', 'total')
  .groupBy(['mes', 'anio'])
  .get();
```

---

### 🚀 Fluent Query Builder (v2.17.0+) - INSERT, UPDATE, DELETE

The new Query Builder allows write operations with a fluent API:

```typescript
import { NexaQueryBuilder } from '@nexabase/sdk';

// ========== INSERT ==========

// Simple insert
const user = await nexabase
  .query('users')
  .set({ name: 'John', email: 'john@example.com' })
  .insertGetId();

// Direct insert
const product = await nexabase
  .query('products')
  .insertGetId({ name: 'iPhone', price: 999 });

// Bulk insert
const users = await nexabase
  .query('users')
  .insertBulk([
    { name: 'User 1', email: 'user1@example.com' },
    { name: 'User 2', email: 'user2@example.com' },
  ]);

// ========== UPDATE ==========

// Update with condition (updates all matching)
const modified = await nexabase
  .query('products')
  .where('category', 'electronics')
  .update({ price: 899, active: true })
  .updateGetModified();

// Update first matching
const updated = await nexabase
  .query('products')
  .where('name', 'like', '%iphone%')
  .update({ stock: 0 })
  .updateFirst();

// Update with idempotency
await nexabase
  .query('orders')
  .where('id', orderId)
  .withIdempotency('update-order-123')
  .update({ status: 'completed' })
  .updateFirst();

// ========== DELETE ==========

// Delete with condition
const deletedCount = await nexabase
  .query('temp_data')
  .where('created_at', '<', thirtyDaysAgo)
  .deleteGetCount();

// Delete first matching
const deleted = await nexabase
  .query('users')
  .where('email', 'temp@example.com')
  .deleteFirst();

// Delete by ID directly
await nexabase
  .query('users')
  .deleteById(userId);

// ========== SELECT shortcuts ==========

// Find by ID
const user = await nexabase.query('users').find(userId);

// Check existence
const exists = await nexabase.query('users').where('email', email).exists();

// Count records
const count = await nexabase.query('products').where('active', true).count();

// Select with filters
const { data } = await nexabase.query('users').select('id', 'name', 'email');

// ========== Combine with existing Query ==========

// Convert to NexaQuery for get/first
const results = await nexabase
  .query('products')
  .where('price', '>', 100)
  .toQuery() // Converts to NexaQuery
  .limit(20)
  .get();
```

#### Query Builder Methods

| Method | Description |
|--------|-------------|
| `set(data)` | Set data for insert/update |
| `insert(data)` | Alias of set for insert |
| `insertGetId(data?)` | Insert and return the document |
| `insertBulk(docs[])` | Insert multiple documents |
| `update(data)` | Alias of set for update |
| `updateGetModified()` | Update all matching, return count |
| `updateFirst()` | Update only the first one |
| `deleteGetCount()` | Delete all matching, return count |
| `deleteFirst()` | Delete only the first one |
| `deleteById(id)` | Delete by ID directly |
| `find(id)` | Find document by ID |
| `count()` | Count matching documents |
| `exists()` | Check if any document exists |
| `select(...fields)` | Select specific fields |
| `withIdempotency(key)` | Add idempotency key |
| `toQuery()` | Convert to NexaQuery for chaining |

#### Query Methods

| Method | Description | Example |
|--------|-------------|---------|
| `select()` | Select fields | `.select('id,name,email')` |
| `fields()` | Alias for select | `.fields('id,name')` |
| `where()` | Add filter | `.where('status', 'active')` |
| `where()` | Operator filter | `.where('price', '>', 100)` |
| `whereGroup()` | Nested group | `.whereGroup(q => q.where(...), 'OR')` |
| `logical()` | AND/OR logic | `.logical('OR')` |

| `sort()` | Sort results | `.sort('-created_at')` |
| `limit()` | Limit results | `.limit(20)` |
| `page()` | Pagination | `.page(2)` |
| `include()` | Include metadata | `.include('schema')` |
| `groupBy()` | Group by field | `.groupBy('status')` |
| `count()` | Count records | `.count('*', 'total')` |
| `sum()` | Sum values | `.sum('amount', 'total')` |
| `avg()` | Average | `.avg('price', 'avg_price')` |
| `min()` | Minimum value | `.min('price')` |
| `max()` | Maximum value | `.max('price')` |
| `month()` | Extract month | `.month('created_at')` |
| `year()` | Extract year | `.year('created_at')` |
| `day()` | Extract day | `.day('created_at')` |
| `search()` | Text search | `.search('keyword')` |
| `get()` | Execute query | `.get()` |
| `first()` | Get first result | `.first()` |

---

### 👥 User Management

Complete user CRUD operations:

```typescript
import { NexaUsers } from '@nexabase/sdk';

// List users
const users = await nexabase.users.list({
  page: 1,
  limit: 20,
  search: 'john', // Search by name/email
});

// Get user
const user = await nexabase.users.get(userId);

// Create user
const newUser = await nexabase.users.create({
  email: 'newuser@example.com',
  password: 'SecurePassword123!',
  first_name: 'New',
  last_name: 'User',
  role: 'user', // 'user' | 'admin' | 'superadmin'
  status: 'active',
});

// Update user
await nexabase.users.update(userId, {
  first_name: 'Updated',
  role: 'admin',
  is_active: true,
});

// Resend invite
await nexabase.users.resendInvite(userId);

// Delete user
await nexabase.users.delete(userId);
```

---

### ☁️ Cloud Functions

Invoke serverless functions:

```typescript
import { NexaFunctions } from '@nexabase/sdk';

// Invoke function (POST)
const result = await nexabase.functions.invoke('calculate-totals', {
  cartId: 'cart_123',
});

// Invoke with options
const result = await nexabase.functions.invoke('send-email', {
  to: 'user@example.com',
  subject: 'Welcome!',
  body: 'Hello!',
}, {
  method: 'POST',
  timeout: 10000,
  headers: {
    'X-Custom-Header': 'value',
  },
});

// Invoke with GET
const result = await nexabase.functions.invoke('get-stats', {}, {
  method: 'GET',
});

// List functions
const functions = await nexabase.functions.list();
```

---

### 📁 Storage

File upload and management:

```typescript
import { NexaStorage } from '@nexabase/sdk';

// Upload a file
const file = await nexabase.storage.upload(
  'avatars',           // Bucket name
  fileBlob,            // File object/Blob
  'user-avatar.png'    // Optional path
);

// Download a file
const blob = await nexabase.storage.download(fileId);

// Get signed URL (temporary access)
const url = await nexabase.storage.getSignedUrl(fileId, {
  expiresIn: 3600, // 1 hour
});

// List files in bucket
const files = await nexabase.storage.listFiles('avatars', {
  prefix: 'users/',
});

// Delete a file
await nexabase.storage.delete(fileId);
```

---

### 🪝 Webhooks

Create and manage webhooks:

```typescript
import { NexaWebhooks } from '@nexabase/sdk';

// List webhooks
const webhooks = await nexabase.webhooks.list();

// Create webhook
const webhook = await nexabase.webhooks.create({
  name: 'Order Created',
  url: 'https://your-api.com/webhooks/orders',
  events: ['document:created'],
  is_active: true,
});

// Get webhook
const webhook = await nexabase.webhooks.get(webhookId);

// Update webhook
await nexabase.webhooks.update(webhookId, {
  name: 'Updated Name',
  is_active: false,
});

// Delete webhook
await nexabase.webhooks.delete(webhookId);
```

---

### 🔄 Real-time Subscriptions

Listen to data changes in real-time:

```typescript
import { NexaBaseRealtime } from '@nexabase/sdk';

const realtime = new NexaBaseRealtime({
  baseURL: 'https://your-tenant.nexabase.app',
  apiKey: 'your-api-key',
  token: 'jwt-token', // Optional
  reconnect: true,
  maxReconnectAttempts: 5,
  reconnectInterval: 5000,
  heartbeatInterval: 30000,
});

// Connect
await realtime.connect();

// Subscribe to collection events
const subscriptionId = realtime.subscribe(
  'products',
  (message) => {
    console.log('Product changed:', message);
  },
  {
    events: ['*'], // ['insert', 'update', 'delete'] or ['*']
  }
);

// Subscribe with filters
realtime.subscribe(
  'orders',
  (message) => {
    console.log('New admin order:', message);
  },
  {
    events: ['insert'],
    filters: { role: 'admin' },
  }
);

// Broadcast message
realtime.broadcast('products', 'product:updated', {
  productId: '123',
  action: 'restock',
});

// Unsubscribe
realtime.unsubscribe(subscriptionId);

// Disconnect
realtime.disconnect();
```

---

## 🎯 TypeScript Support

The SDK is built with TypeScript and provides full type definitions:

```typescript
import {
  NexaBase,
  NexaBaseConfig,
  Document,
  Collection,
  StandardResponse,
  DocumentQueryOptions,
  User,
  CreateUser,
} from '@nexabase/sdk';

// Fully typed configuration
const config: NexaBaseConfig = {
  baseURL: 'https://api.nexabase.app',
  apiKey: 'your-key',
  timeout: 30000,
  debug: false,
};

// Type-safe queries
const options: DocumentQueryOptions = {
  filter: { status: 'active' },
  sort: '-created_at',
  page: 1,
  per_page: 20,
  fields: 'id,name,email',
};

// Typed response
const response: StandardResponse<Document[]> = await nexabase.listDocuments(
  'users',
  options
);

// Access typed data
response.data.forEach((doc: Document) => {
  console.log(doc.id, doc.created_at);
});
```

---

## 🏭 Factory Functions

Pre-configured clients for common scenarios:

```typescript
import {
  createApiClient,
  createTokenClient,
  createAuthenticatedApiClient,
  createSubdomainClient,
  createDevelopmentClient,
  createTestClient,
} from '@nexabase/sdk';

// API Key client (recommended)
const client = createApiClient(
  'https://api.nexabase.app',
  'your-api-key'
);

// Bearer token client
const client = createTokenClient(
  'https://api.nexabase.app',
  'jwt-token'
);

// Auto-login client
const client = await createAuthenticatedApiClient(
  'https://api.nexabase.app',
  'api-key',
  'user@example.com',
  'password'
);

// Subdomain client
const client = createSubdomainClient(
  'mytenant',
  'api-key',
  'nexabase.app'
);

// Development client
const client = createDevelopmentClient(
  'http://localhost:3000',
  'dev-key',
  {
    debug: true,
    timeout: 30000,
  }
);

// Test client
const client = createTestClient(
  'http://localhost:3000',
  'test-key',
  {
    debug: true,
    mockMode: true,
  }
);
```

---

## 🚨 Error Handling

Comprehensive error handling:

```typescript
try {
  const doc = await nexabase.getDocument('users', 'invalid-id');
} catch (error) {
  if (error.statusCode === 404) {
    console.error('Document not found');
  } else if (error.statusCode === 401) {
    console.error('Unauthorized - check your API key');
  } else if (error.statusCode === 429) {
    console.error('Rate limit exceeded');
  } else {
    console.error('Error:', error.message, error.details);
  }
}
```

---

## ⚙️ Configuration Options

### Client Configuration

```typescript
const client = new NexaBase({
  // Required
  baseURL: 'https://your-tenant.nexabase.app',
  apiKey: 'your-api-key',
  
  // Optional
  token: 'jwt-token',              // Bearer token
  timeout: 30000,                   // Request timeout (ms)
  debug: false,                     // Enable debug logging
  customHeaders: {                  // Custom headers
    'X-Custom-Header': 'value',
  },
});
```

### Realtime Configuration

```typescript
const realtime = new NexaBaseRealtime({
  baseURL: 'https://your-tenant.nexabase.app',
  apiKey: 'your-api-key',
  token: 'jwt-token',
  reconnect: true,
  maxReconnectAttempts: 5,
  reconnectInterval: 5000,
  heartbeatInterval: 30000,
});
```

---

## 🌍 Browser Support

| Browser | Version | Support |
|---------|---------|---------|
| Chrome | Last 2 versions | ✅ |
| Firefox | Last 2 versions | ✅ |
| Safari | Last 2 versions | ✅ |
| Edge | Last 2 versions | ✅ |
| Node.js | >= 18.0.0 | ✅ |

---

## 📚 Complete Documentation

This README provides a quick overview. For complete documentation:

### Guides

- **[Getting Started](./docs/guides/getting-started.md)** - Complete setup guide
- **[Authentication](./docs/guides/authentication.md)** - All auth methods
- **[Collections & Documents](./docs/guides/collections-and-documents.md)** - Working with data
- **[Query Builder](./docs/API.md#query-builder)** - Fluent queries and write operations
- **[Aggregations & Reports](./docs/guides/aggregations-and-reports.md)** - Analytics and reporting
- **[Cloud Functions](./docs/guides/cloud-functions.md)** - Serverless functions
- **[Storage](./docs/guides/storage.md)** - File management
- **[Webhooks](./docs/guides/webhooks.md)** - Webhook setup
- **[User Management](./docs/guides/users.md)** - User CRUD
- **[Real-time](./docs/API.md#realtime-subscriptions)** - WebSocket subscriptions
- **[Error Handling](./docs/guides/error-handling.md)** - Best practices
- **[Idempotency](./docs/guides/idempotency.md)** - Safe retries with idempotency keys

### API Reference

- **[Complete API Reference](./docs/API.md)** - All methods documented
- **[TypeScript Types](./docs/typescript-reference.md)** - Type definitions

---

## 🤝 Contributing

We welcome contributions! Here's how you can help:

### Ways to Contribute

- 🐛 Report bugs
- 💡 Suggest features
- 📝 Improve documentation
- 🔧 Submit pull requests
- 💬 Help others in discussions

### Development Setup

```bash
# Fork and clone
git clone https://github.com/fmirpe/nexabase-sdk.git
cd nexabase-sdk/javascript

# Install dependencies
npm install

# Start development
npm run dev

# Run tests
npm test

# Build
npm run build
```

See [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines.

---

## 📄 License

MIT License - see [LICENSE](./LICENSE) file for details.

---

## 📞 Support

- 📧 **Email:** support@fabricadealgoritmos.site
- 🐛 **Issues:** [GitHub Issues](https://github.com/fmirpe/nexabase-sdk/issues)
- 💬 **Discussions:** [GitHub Discussions](https://github.com/fmirpe/nexabase-sdk/discussions)
- 📖 **Docs:** [Full Documentation](./docs)
- 🌐 **Website:** [NexaBase Platform](https://fabricadealgoritmos.site/)

---

<div align="center">
  <strong>NexaBase SDK - Build Apps Faster</strong>
  <br>
  <sub>Backend as a Service Platform</sub>
  <br><br>
  Made with ❤️ by Fabrica de Algoritmos
</div>
