npm package: @m4rctr3y/reutjs
Description: TypeScript/JavaScript client library for Reut backend API. Provides a type-safe, fluent API similar to Supabase's JavaScript client.
Latest version: 1.0.0

# @reut/reutjs

TypeScript/JavaScript client library for Reut backend API. Provides a type-safe, fluent API similar to Supabase's JavaScript client but tailored for the Reut API structure.

## Features

- 🎯 **Type Safety**: Full TypeScript support with generics
- 🔗 **Fluent API**: Chainable query builder methods
- 🔄 **Relationship Loading**: Easy eager loading with `.with()` and relationship aliasing
- 📊 **Efficient Counts**: Get counts without loading data using `.withCount()` and `.count()`
- 🔍 **Advanced Filtering**: Support for relationship filters and operators
- 📄 **Pagination**: Built-in pagination support
- 🌐 **Framework Agnostic**: Works in Node.js, React, Vue, Angular, etc.
- 🚀 **Axios-based**: Reliable HTTP client with interceptors support

## Installation

```bash
npm install @m4rctr3y/reutjs axios
```

or

```bash
yarn add @m4rctr3y/reutjs axios
```

or

```bash
pnpm add @m4rctr3y/reutjs axios
```

## Quick Start

```typescript
import { ReutClient } from '@m4rctr3y/reutjs';

// Initialize client
const client = new ReutClient({
  url: 'http://localhost:9000'
});

// Define your types
interface Post {
  id: number;
  title: string;
  content: string;
  user_id: number;
  created_at: string;
  updated_at: string;
}

// Query data with relationship aliasing
const posts = await client.from<Post>('posts')
  .select(['id', 'title', 'content'])
  .where('user_id', 1)
  .with([['user', 'user_id']]) // Load user relationship with alias
  .withCount([['comments', 'post_id']]) // Get comment count (efficient!)
  .page(1)
  .limit(10)
  .all();

console.log(posts.results);
// Each post will have: { id, title, content, user: {...}, comments_count: 5 }
// Note: user_id is removed from results when using alias

// Get total count (efficient COUNT query)
const totalPosts = await client.from<Post>('posts').count();
console.log(`Total posts: ${totalPosts}`);
```

## API Reference

### ReutClient

The main client class for interacting with the Reut API.

#### Constructor

```typescript
new ReutClient(config: ReutClientConfig)
```

**Config Options:**
- `url` (string, required): Base URL of the Reut API server
- `token` (string, optional): Authentication token
- `axiosConfig` (AxiosRequestConfig, optional): Additional axios configuration

**Example:**
```typescript
const client = new ReutClient({
  url: 'http://localhost:9000',
  token: 'your-auth-token',
  axiosConfig: {
    timeout: 5000
  }
});
```

#### Methods

##### `from<T>(tableName: string): Table<T>`

Get a table instance for querying.

```typescript
const postsTable = client.from<Post>('posts');
```

##### `setToken(token: string): void`

Set authentication token.

```typescript
client.setToken('new-token');
```

##### `removeToken(): void`

Remove authentication token.

```typescript
client.removeToken();
```

### Table

Represents a database table and provides CRUD operations.

#### Query Methods (Chainable)

All query methods return the Table instance for chaining.

##### `select(columns: string[]): Table<T>`

Select specific columns.

```typescript
.select(['title', 'user_id'])
```

##### `where(column: string, operatorOrValue: WhereOperator | WhereValue, value?: WhereValue): Table<T>`

Add a where condition.

```typescript
// Simple equality
.where('user_id', 1)

// With operator
.where('age', 'gt', 18)
.where('name', 'like', 'John')
```

##### `whereIn(column: string, values: (string | number)[]): Table<T>`

Filter where column value is in array.

```typescript
.whereIn('user_id', [1, 2, 3])
```

##### `whereNotIn(column: string, values: (string | number)[]): Table<T>`

Filter where column value is not in array.

```typescript
.whereNotIn('status', ['deleted', 'archived'])
```

##### `whereLike(column: string, value: string): Table<T>`

Filter with LIKE operator (contains).

```typescript
.whereLike('title', 'test')
```

##### `whereGt(column: string, value: number): Table<T>`

Filter where column is greater than value.

```typescript
.whereGt('age', 18)
```

##### `whereGte(column: string, value: number): Table<T>`

Filter where column is greater than or equal to value.

```typescript
.whereGte('price', 100)
```

##### `whereLt(column: string, value: number): Table<T>`

Filter where column is less than value.

```typescript
.whereLt('age', 65)
```

##### `whereLte(column: string, value: number): Table<T>`

Filter where column is less than or equal to value.

```typescript
.whereLte('price', 1000)
```

##### `whereNe(column: string, value: WhereValue): Table<T>`

Filter where column is not equal to value.

```typescript
.whereNe('status', 'deleted')
```

##### `whereIsNull(column: string): Table<T>`

Filter where column is null.

```typescript
.whereIsNull('deleted_at')
```

##### `whereIsNotNull(column: string): Table<T>`

Filter where column is not null.

```typescript
.whereIsNotNull('email')
```

##### `with(relationships: WithRelationships): Table<T>`

Eager load relationships. Supports both old format (backward compatible) and new format with aliases.

**Old Format (Backward Compatible):**
```typescript
.with(['user_id', 'comments'])
// Results: { user_id: {...}, comments: [...] }
```

**New Format with Aliases (Recommended):**
```typescript
.with([['user', 'user_id'], ['category', 'category_id']])
// Results: { user: {...}, category: {...} }
// Note: user_id and category_id are removed from results
```

**Benefits of Aliases:**
- Cleaner code: `post.user.name` instead of `post.user_id.name`
- Foreign key columns are automatically removed from results
- More readable and maintainable code

##### `join(joinConfig: JoinConfig): Table<T>`

Add a manual join.

```typescript
.join({
  table: 'Users',
  localColumn: 'user_id',
  refColumn: 'id',
  type: 'INNER',
  alias: 'user'
})
```

##### `page(page: number): Table<T>`

Set page number for pagination.

```typescript
.page(1)
```

##### `limit(limit: number): Table<T>`

Set limit for pagination.

```typescript
.limit(10)
```

##### `offset(offset: number): Table<T>`

Set offset for pagination.

```typescript
.offset(20)
```

##### `orderBy(column: string, direction: 'asc' | 'desc' = 'asc'): Table<T>`

Order results by column.

```typescript
.orderBy('created_at', 'desc')
```

##### `withCount(relationships: RelationshipAlias[]): Table<T>`

Get count of related records (efficient COUNT query, doesn't load data).

```typescript
.withCount([['comments', 'post_id']])
// Returns: { comments_count: 5 } (no comments array loaded)
```

**Example:**
```typescript
const posts = await client.from<Post>('posts')
  .select(['id', 'title'])
  .withCount([['comments', 'post_id']])
  .all();

// Each post will have: { id, title, comments_count: 5 }
// Comments data is NOT loaded, only the count
```

##### `count(): Promise<number>`

Get total count of records matching the query (efficient COUNT query, no data).

```typescript
// Get total count
const total = await client.from<Post>('posts').count();
// Returns: 10 (just the number)

// Get count with filters
const count = await client.from<Post>('posts')
  .where('user_id', 1)
  .count();
// Returns: 5 (just the number)
```

**Benefits:**
- Efficient: Only executes `SELECT COUNT(*)`, doesn't load data
- Fast: Perfect for pagination, statistics, and dashboards
- Works with all query builder methods (where, joins, etc.)

#### CRUD Methods

##### `all(options?: QueryOptions): Promise<PaginatedResponse<T>>`

Get all records with optional query options.

```typescript
const result = await client.from<Post>('posts')
  .select(['title'])
  .where('user_id', 1)
  .page(1)
  .limit(10)
  .all();

console.log(result.results);      // Array of posts
console.log(result.totalPages);   // Total pages
console.log(result.page);         // Current page
console.log(result.limit);        // Items per page
console.log(result.totalItems);   // Total items
```

##### `find(id: string | number, options?: { with?: string[] }): Promise<SingleResponse<T>>`

Find a single record by ID.

```typescript
const post = await client.from<Post>('posts')
  .find(1, { with: ['user_id'] });
```

##### `add(data: Partial<T>): Promise<SingleResponse<T>>`

Create a new record.

```typescript
const newPost = await client.from<Post>('posts')
  .add({
    title: 'New Post',
    content: 'Post content',
    user_id: 1
  });
```

##### `update(id: string | number, data: Partial<T>): Promise<SingleResponse<T>>`

Update a record by ID.

```typescript
const updatedPost = await client.from<Post>('posts')
  .update(1, {
    title: 'Updated Title'
  });
```

##### `delete(id: string | number): Promise<void>`

Delete a record by ID.

```typescript
await client.from<Post>('posts').delete(1);
```

## Examples

### Basic Query

```typescript
const posts = await client.from<Post>('posts')
  .page(1)
  .limit(10)
  .all();
```

### Select Specific Columns

```typescript
const posts = await client.from<Post>('posts')
  .select(['title', 'user_id'])
  .all();
```

### Eager Load Relationships

**Old Format (Backward Compatible):**
```typescript
const posts = await client.from<Post>('posts')
  .with(['user_id'])
  .all();
// Results: { user_id: {...} }
```

**New Format with Aliases (Recommended):**
```typescript
const posts = await client.from<Post>('posts')
  .select(['id', 'title', 'content'])
  .with([['user', 'user_id']])
  .all();
// Results: { id, title, content, user: {...} }
// Note: user_id is removed from results
```

### Filter by Column

```typescript
const posts = await client.from<Post>('posts')
  .where('user_id', 1)
  .all();
```

### Filter by Relationship

```typescript
const posts = await client.from<Post>('posts')
  .where('user.name', 'John Doe')
  .all();
```

### Complex Query

```typescript
const posts = await client.from<Post>('posts')
  .select(['id', 'title', 'content'])
  .where('user_id', 1)
  .whereGt('created_at', '2024-01-01')
  .with([['user', 'user_id']]) // Load user with alias
  .withCount([['comments', 'post_id']]) // Get comment count
  .page(1)
  .limit(10)
  .orderBy('created_at', 'desc')
  .all();
```

### Get Relationship Counts (Efficient)

```typescript
// Get posts with comment counts (doesn't load comment data)
const posts = await client.from<Post>('posts')
  .select(['id', 'title'])
  .withCount([['comments', 'post_id']])
  .all();

// Each post: { id, title, comments_count: 5 }
```

### Get Total Count

```typescript
// Get total number of posts
const total = await client.from<Post>('posts').count();
console.log(`Total posts: ${total}`);

// Get count with filters
const filteredCount = await client.from<Post>('posts')
  .where('user_id', 1)
  .where('status', 'published')
  .count();
console.log(`Published posts by user 1: ${filteredCount}`);
```

### Create Record

```typescript
const newPost = await client.from<Post>('posts')
  .add({
    title: 'New Post',
    content: 'Content here',
    user_id: 1
  });
```

### Update Record

```typescript
const updated = await client.from<Post>('posts')
  .update(1, {
    title: 'Updated Title'
  });
```

### Delete Record

```typescript
await client.from<Post>('posts').delete(1);
```

## Type Definitions

### PaginatedResponse<T>

```typescript
interface PaginatedResponse<T> {
  results: T[];
  totalPages: number;
  page: number;
  limit: number;
  totalItems: number;
}
```

### SingleResponse<T>

```typescript
type SingleResponse<T> = T;
```

### WhereOperator

```typescript
type WhereOperator = 
  | 'eq'   // equals
  | 'ne'   // not equals
  | 'gt'   // greater than
  | 'gte'  // greater than or equal
  | 'lt'   // less than
  | 'lte'  // less than or equal
  | 'like' // like (contains)
  | 'in'   // in array
  | 'nin'  // not in array
  | 'is'   // is null
  | 'isn'; // is not null
```

### RelationshipAlias

```typescript
type RelationshipAlias = [string, string]; // [alias, fkColumn]

// Example:
[['user', 'user_id'], ['category', 'category_id']]
```

### WithRelationships

```typescript
type WithRelationships = string[] | RelationshipAlias[];

// Old format (backward compatible):
string[] // e.g., ['user_id', 'comments']

// New format (recommended):
RelationshipAlias[] // e.g., [['user', 'user_id'], ['comments', 'post_id']]
```

## Recent Updates

### v0.1.0 - Latest Features

#### ✨ Relationship Aliasing
- Use cleaner aliases for relationships: `with([['user', 'user_id']])`
- Foreign key columns are automatically removed from results
- More readable code: `post.user.name` instead of `post.user_id.name`
- Backward compatible with old `with(['user_id'])` format

#### 📊 Efficient Count Queries
- **`withCount()`**: Get relationship counts without loading data
  ```typescript
  .withCount([['comments', 'post_id']])
  // Returns: { comments_count: 5 } (no comments array)
  ```
- **`.count()`**: Get total count of records matching query
  ```typescript
  const total = await client.from('posts').count();
  // Returns: 10 (just the number, no data loaded)
  ```

#### 🎯 Benefits
- **Performance**: COUNT queries are much faster than loading all data
- **Memory**: Reduces memory usage by not loading unnecessary data
- **Readability**: Cleaner code with relationship aliases
- **Backward Compatible**: Old code continues to work

## Error Handling

The library throws errors for failed requests. Always wrap API calls in try-catch blocks:

```typescript
try {
  const posts = await client.from<Post>('posts').all();
} catch (error) {
  console.error('Error fetching posts:', error.message);
}
```

## Migration Guide

### Upgrading to Relationship Aliases

**Before:**
```typescript
const posts = await client.from<Post>('posts')
  .select(['id', 'title', 'user_id'])
  .with(['user_id'])
  .all();
// Results: { id, title, user_id: {...} }
```

**After (Recommended):**
```typescript
const posts = await client.from<Post>('posts')
  .select(['id', 'title']) // user_id removed from select
  .with([['user', 'user_id']])
  .all();
// Results: { id, title, user: {...} }
// Note: user_id is automatically removed
```

**Old code still works!** The old format is fully backward compatible.

## License

MIT

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.