# E-BaaS SDK

The official TypeScript SDK for E-BaaS (Enterprise Backend as a Service) - A comprehensive backend-as-a-service platform.

## Installation

```bash
npm install @e-techsolutions/e-baas-sdk@latest
# or
yarn add @e-techsolutions/e-baas-sdk@latest
```

## Quick Start

```typescript
import { createClient } from "@e-techsolutions/e-baas-sdk";

// Basic configuration (required)
const EBAAS_URL = process.env.EBAAS_API_URL; // required - https://your-api-e-baas-service
const EBAAS_KEY = process.env.EBAAS_API_KEY; // required - your API key
const WORKSPACE_ID = process.env.EBAAS_WORKSPACE_ID; // required - your workspace ID

export const ebaas = createClient(EBAAS_URL, EBAAS_KEY, {
  // Database configuration (required)
  database: {
    workspaceId: WORKSPACE_ID, // required
    autoDetectCustomDatabase: true, // optional (default: false)
  },
  // Other configurations are optional
});
```

### Complete Authentication Flow

```typescript
// 1. User login
const { user, session, error } = await ebaas.auth.signIn({
  email: "user@example.com",
  password: "secure-password",
});

if (error) {
  console.error("Login failed:", error);
  return;
}

// 2. Configure session in SDK (REQUIRED!)
ebaas.auth.setSession(session);

// 3. Persist session (optional)
localStorage.setItem("ebaas_session", JSON.stringify(session));
localStorage.setItem("ebaas_user", JSON.stringify(user));

// 4. Now you can use database operations normally
const query = ebaas.from("users").select("*");
const { data, error } = await query.execute();

if (error) {
  console.error("Error fetching users:", error);
} else {
  console.log("Users found:", data.length);
}
```

### Session Restoration

```typescript
// Restore session on app initialization
const restoreSession = () => {
  const savedSession = localStorage.getItem("ebaas_session");
  const savedUser = localStorage.getItem("ebaas_user");

  if (savedSession && savedUser) {
    const sessionData = JSON.parse(savedSession);

    // Check if not expired
    if (sessionData.expires_at > Date.now()) {
      // Restore session in SDK
      ebaas.auth.setSession(sessionData);
      console.log("✅ Session restored automatically");
    } else {
      // Clear expired session
      localStorage.removeItem("ebaas_session");
      localStorage.removeItem("ebaas_user");
    }
  }
};
```

### Environment Variables

Create a `.env` file in your project root:

```env
EBAAS_API_URL=https://your-api-e-baas-service
EBAAS_API_KEY=your-api-key-here
EBAAS_WORKSPACE_ID=your-workspace-id-here
```

**Note**: This works with any bundler (Webpack, Vite, Next.js, etc.) that supports `process.env`.

## Features

- **Authentication** - Email/password and OAuth providers (Google, GitHub, Facebook)
- **Database** - PostgreSQL with real-time subscriptions and RPC support
- **Custom Database** - Use your own PostgreSQL database with automatic detection
- **Storage** - File uploads with CDN integration and image transformations
- **Realtime** - WebSocket subscriptions for live data updates
- **Edge Functions** - Serverless functions with dual runtime (Node.js VM + Deno) and native SDK integration
- **TypeScript** - Full type safety and IntelliSense support

## Usage Examples

### Authentication

```typescript
// Sign up with email/password
const { user, session, error } = await ebaas.auth.signUp({
  email: "user@example.com",
  password: "secure-password",
  name: "John Doe",
});

// Sign in
const { user, session, error } = await ebaas.auth.signIn({
  email: "user@example.com",
  password: "secure-password",
});

// Get current user
const user = await ebaas.auth.getUser();

// Sign out
await ebaas.auth.signOut();
```

**⚠️ IMPORTANT - Session Configuration:**

After login, you **MUST** configure the session in the SDK for all subsequent operations to work correctly:

```typescript
// Complete authentication flow
const loginUser = async () => {
  // 1. Perform login
  const { user, session, error } = await ebaas.auth.signIn({
    email: "user@example.com",
    password: "secure-password",
  });

  if (error) {
    console.error("Login failed:", error);
    return;
  }

  // 2. Configure session in SDK (MANDATORY STEP!)
  ebaas.auth.setSession(session);

  // 3. Persist session in localStorage (optional)
  localStorage.setItem("ebaas_session", JSON.stringify(session));
  localStorage.setItem("ebaas_user", JSON.stringify(user));

  console.log("✅ Login and session configuration completed");
};

// Restore session on application initialization
const restoreSession = () => {
  const savedSession = localStorage.getItem("ebaas_session");
  const savedUser = localStorage.getItem("ebaas_user");

  if (savedSession && savedUser) {
    const sessionData = JSON.parse(savedSession);

    // Check if not expired
    if (sessionData.expires_at > Date.now()) {
      // Restore session in SDK
      ebaas.auth.setSession(sessionData);
      console.log("✅ Session restored automatically");
    } else {
      // Clear expired session
      localStorage.removeItem("ebaas_session");
      localStorage.removeItem("ebaas_user");
    }
  }
};
```

### Database Operations

```typescript
// Select data
const query = ebaas.from("users").select("*");
const { data, error } = await query.execute();

if (error) {
  console.error("Error fetching users:", error);
} else {
  console.log("Users found:", data.length);
}

// Select specific fields for better performance
const query = ebaas
  .from("users")
  .select("id, name, email")
  .eq("status", "active")
  .order("created_at", { ascending: false })
  .limit(10);
const { data, error } = await query.execute();

// Insert data
const query = ebaas.from("users").insert({
  name: "John Doe",
  email: "john@example.com",
});
const { data, error } = await query.execute();

if (error) {
  console.error("Error creating user:", error);
} else {
  console.log("User created successfully:", data);
}

// Update data
const query = ebaas.from("users").update({ name: "John Smith" }).eq("id", 123);
const { data, error } = await query.execute();

if (error) {
  console.error("Error updating user:", error);
} else {
  console.log("User updated successfully:", data);
}

// Delete data
const query = ebaas.from("users").delete().eq("id", 123);
const { data, error } = await query.execute();

if (error) {
  console.error("Error deleting user:", error);
} else {
  console.log("User deleted successfully");
}

// RPC (stored procedures)
const query = ebaas.rpc("get_user_stats", {
  user_id: 123,
});
const { data, error } = await query.execute();
```

### Custom Database

```typescript
// Configure with automatic custom database detection
const ebaas = createClient(EBAAS_URL, EBAAS_KEY, {
  database: {
    workspaceId: WORKSPACE_ID,
    autoDetectCustomDatabase: true, // Enable custom database auto-detection
  },
});
```

### Storage

```typescript
// Upload file
const { data, error } = await ebaas.storage.from("avatars").upload("user-123.jpg", file, {
  cacheControl: "3600",
  upsert: true,
});

// Download file
const { data, error } = await ebaas.storage.from("avatars").download("user-123.jpg");

// Get public URL
const { data } = ebaas.storage.from("avatars").getPublicUrl("user-123.jpg");

// Get signed URL (for private files)
const { data, error } = await ebaas.storage.from("private-docs").createSignedUrl("document.pdf", 60); // 60 seconds

// List files
const { data, error } = await ebaas.storage.from("avatars").list("", {
  limit: 100,
  offset: 0,
  sortBy: { column: "name", order: "asc" },
});
```

### Realtime Subscriptions

```typescript
// Subscribe to database changes
const channel = ebaas.channel("realtime:users");

channel.onPostgresChanges(
  {
    event: "*", // INSERT, UPDATE, DELETE, or *
    schema: "public",
    table: "users",
  },
  (payload) => {
    console.log("Change received!", payload);
  }
);

channel.subscribe();

// Broadcast messages
channel.send({
  type: "broadcast",
  event: "message",
  payload: { text: "Hello World!" },
});

// Presence tracking
channel.track({ user_id: 123, status: "online" });

// Unsubscribe
channel.unsubscribe();
```

### Edge Functions

#### Client-side Function Invocation

```typescript
// Invoke authenticated function (requires session)
const { data, error } = await ebaas.functions.invoke("hello-world", {
  body: { name: "John" },
  headers: { "Content-Type": "application/json" },
});

// Invoke public function (no authentication required)
const response = await fetch("https://your-api-domain.com/functions/v1/public/hello-world", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "John" }),
});
const data = await response.json();

// List functions
const { data, error } = await ebaas.functions.list();

// Get function details
const { data, error } = await ebaas.functions.get("hello-world");
```

#### Native SDK Integration in Edge Functions

The SDK is natively available within Edge Functions with global methods:

```typescript
// Edge Function with native SDK access
const users = await from("users").select("*").limit(10);
const bucket = bucket("uploads");
await channel("notifications").send("user_created", { user: userData });
return createResponse({ success: true, users });
```

**Available globals**: `from()`, `rpc()`, `bucket()`, `channel()`, `invoke()`, `ebaas.*`

## Advanced Configuration

```typescript
const ebaas = createClient(
  "https://your-api-domain.com", // required - API URL
  "your-api-key", // required - API Key
  {
    // Database configuration (required)
    database: {
      workspaceId: "your-workspace-id", // required - Workspace ID
      autoDetectCustomDatabase: true, // optional (default: false)
    },
    // Authentication configuration (optional)
    auth: {
      autoRefreshToken: true, // optional (default: true)
      persistSession: true, // optional (default: true)
      detectSessionInUrl: true, // optional (default: true)
    },
    // Realtime configuration (optional)
    realtime: {
      params: {
        eventsPerSecond: 10, // optional (default: 10)
      },
    },
    // Global configuration (optional)
    global: {
      headers: {
        "x-custom-header": "custom-value", // optional - custom headers
      },
    },
  }
);
```

## Error Handling

```typescript
import { EBaaSError } from "@e-techsolutions/e-baas-sdk";

try {
  const query = ebaas.from("users").select("*");
  const { data, error } = await query.execute();
} catch (error) {
  if (error instanceof EBaaSError) {
    console.error("E-BaaS Error:", error.message);
    console.error("Status:", error.status);
    console.error("Details:", error.details);
  }
}
```

## Secure DELETE Operations

### Security Validation

E-BaaS implements strict validation for DELETE and UPDATE operations, requiring **at least one filter** to prevent accidental mass deletions:

```typescript
// ✅ CORRECT: DELETE with specific filter
const query = ebaas.from("users").delete().eq("id", 123);
const { data, error } = await query.execute();

// ❌ ERROR: DELETE without filters (returns "Security violation")
const query = ebaas.from("users").delete();
const { data, error } = await query.execute(); // Fails with security error
```

### Secure DELETE Operations with Required Filters

Always use the SDK with proper filters for safe DELETE operations:

```typescript
// DELETE using SDK with required filter
const query = ebaas.from("users").delete().eq("id", userId);
const { data, error } = await query.execute();

if (error) {
  console.error("Error deleting user:", error);
} else {
  console.log("User deleted successfully");
}
```

## Refresh Token Management

### Automatic Token Refresh (Default)

The E-BaaS SDK automatically manages refresh tokens for workspace users, ensuring seamless authentication without manual intervention:

```typescript
const ebaas = createClient(EBAAS_URL, EBAAS_KEY, {
  database: {
    workspaceId: WORKSPACE_ID,
  },
  auth: {
    autoRefreshToken: true, // Default: true - automatic refresh enabled
    persistSession: true,   // Default: true - session persistence
  },
});
```

### How Automatic Refresh Works

1. **Login Phase**: When a user logs in, they receive both an access token and a refresh token
2. **Background Monitoring**: The SDK monitors token expiration automatically
3. **Seamless Refresh**: Before expiration, the SDK calls `/auth/v1/sdk/refresh` to get new tokens
4. **Token Rotation**: Old refresh tokens are invalidated for security (refresh token rotation)
5. **Continued Operation**: All database operations continue without interruption

### Manual Refresh Token Management

For advanced use cases, you can disable automatic refresh and manage tokens manually:

```typescript
const ebaas = createClient(EBAAS_URL, EBAAS_KEY, {
  database: {
    workspaceId: WORKSPACE_ID,
  },
  auth: {
    autoRefreshToken: false, // Disable automatic refresh
  },
});

// Manual refresh when needed
const refreshTokens = async () => {
  try {
    const query = ebaas.auth.refreshSession();
    const { data, error } = await query.execute();
    
    if (error) {
      console.error('Failed to refresh token:', error);
      // Redirect to login page
      return;
    }
    
    // Update session in SDK
    ebaas.auth.setSession(data.session);
    
    // Update localStorage
    localStorage.setItem('ebaas_session', JSON.stringify(data.session));
    
    console.log('✅ Tokens refreshed successfully');
  } catch (error) {
    console.error('Token refresh error:', error);
    // Handle refresh failure - redirect to login
  }
};
```

### React Hook for Token Management

Here's a custom React hook for handling token refresh and expiration:

```typescript
import { useEffect, useCallback } from 'react';
import { ebaas } from './ebaas-client';

export const useTokenRefresh = () => {
  const checkTokenExpiration = useCallback(async () => {
    const session = ebaas.auth.getSession();
    
    if (!session?.expires_at) return;
    
    const expiresAt = new Date(session.expires_at);
    const now = new Date();
    const timeToExpiry = expiresAt.getTime() - now.getTime();
    
    // Refresh if token expires in less than 5 minutes
    if (timeToExpiry < 5 * 60 * 1000) {
      try {
        const query = ebaas.auth.refreshSession();
        const { data, error } = await query.execute();
        
        if (error) {
          console.error('Token refresh failed:', error);
          // Redirect to login
          window.location.href = '/login';
          return;
        }
        
        // Update session
        ebaas.auth.setSession(data.session);
        localStorage.setItem('ebaas_session', JSON.stringify(data.session));
        
        console.log('✅ Token refreshed proactively');
      } catch (error) {
        console.error('Token refresh error:', error);
        // Handle error - redirect to login
        window.location.href = '/login';
      }
    }
  }, []);
  
  useEffect(() => {
    // Check immediately
    checkTokenExpiration();
    
    // Check every 2 minutes
    const interval = setInterval(checkTokenExpiration, 2 * 60 * 1000);
    
    return () => clearInterval(interval);
  }, [checkTokenExpiration]);
};

// Usage in your main App component
const App = () => {
  useTokenRefresh(); // Add this hook to your main App
  
  return (
    <div>
      {/* Your app content */}
    </div>
  );
};
```

### Token Refresh Error Handling

```typescript
const handleDatabaseOperation = async () => {
  try {
    const query = ebaas.from('users').select('*');
    const { data, error } = await query.execute();
    
    if (error) {
      // Check if error is due to token expiration
      if (error.status === 401 || error.message?.includes('token')) {
        console.log('Token expired, attempting refresh...');
        
        const { session, error } = await ebaas.auth.refreshSession();
        
        if (error) {
          // Refresh failed - redirect to login
          console.error('Token refresh failed:', error);
          window.location.href = '/login';
          return;
        }
        
        // Update session and retry operation
        ebaas.auth.setSession(session);
        const { data, error } = await query.execute();
        
        if (!error) {
          console.log('✅ Operation successful after token refresh');
          return data;
        }
      }
      
      console.error('Database operation failed:', error);
    }
    
    return data;
  } catch (error) {
    console.error('Unexpected error:', error);
  }
};
```

### Security Benefits

- **Token Rotation**: Each refresh generates new tokens and invalidates old ones
- **Short-lived Access Tokens**: Access tokens have shorter lifespans for security
- **Automatic Cleanup**: Expired and revoked tokens are automatically cleaned up
- **Session Tracking**: Each session is tracked with IP address and user agent
- **Workspace Isolation**: Refresh tokens are isolated per workspace for multi-tenancy

### Configuration Options

```typescript
const ebaas = createClient(EBAAS_URL, EBAAS_KEY, {
  auth: {
    autoRefreshToken: true,    // Enable/disable automatic refresh
    persistSession: true,      // Persist session in localStorage
    detectSessionInUrl: true,  // Detect session from URL params
  },
});
```

## TypeScript Support

The SDK is built with TypeScript and provides full type safety:

```typescript
interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

const query = ebaas.from<User>("users").select("*");
const { data, error } = await query.execute();

// data is now typed as User[] | null
```

## Edge Functions Runtime Support

Enhanced Edge Functions with dual runtime support (Node.js VM + Deno) and native SDK integration.

## License

MIT
