npm package: @flxbl-dev/client
Description: Lightweight runtime SDK for FLXBL — zero dependencies, uses native fetch
Author: FLXBL
Latest version: 0.3.1

# @flxbl-dev/client

Lightweight, type-safe runtime SDK for [FLXBL](https://flxbl.dev). Zero dependencies — uses native `fetch`.

This package is the **production dependency** your app ships with. The generated typed client (from `@flxbl-dev/cli`) extends `FlxblClient` with schema-specific collections and full autocomplete.

## Install

```bash
npm install @flxbl-dev/client
```

## Quick Start

Use the generated client from `@flxbl-dev/cli` for full type safety:

```typescript
import { createFlxblClient } from './flxbl/_generated';

const db = createFlxblClient({
  instanceUrl: 'https://api.flxbl.dev',
  apiKey: process.env.FLXBL_API_KEY,
});

// Fully typed — autocomplete on fields, type-checked inputs
const users = await db.users.findMany({ where: { role: 'admin' }, limit: 10 });
const post = await db.posts.create({ title: 'Hello', content: '...', published: true });
const user = await db.users.findById('abc123');
await db.users.update('abc123', { role: 'user' });
await db.users.delete('abc123');
```

Or use the base client directly for untyped access:

```typescript
import { FlxblClient } from '@flxbl-dev/client';

const client = new FlxblClient({
  instanceUrl: 'https://api.flxbl.dev',
  apiKey: process.env.FLXBL_API_KEY,
});

// Raw request
const users = await client.request('POST', '/api/v1/dynamic/User/query', {
  where: { role: 'admin' },
  limit: 10,
});
```

## Configuration

```typescript
import { FlxblClient } from '@flxbl-dev/client';

const client = new FlxblClient({
  // Required
  instanceUrl: 'https://api.flxbl.dev',

  // Auth — provide one of:
  apiKey: 'flxbl_abc123...',     // API key (recommended for server-side)
  accessToken: 'eyJhbG...',     // JWT token (for browser/short-lived)

  // Optional
  fetch: customFetch,           // Custom fetch implementation
});

// You can also set the token dynamically
client.setAccessToken('eyJhbG...');
```

## Collection Methods

Each generated collection (e.g., `db.users`) provides:

| Method | Description |
|--------|-------------|
| `findMany(options?)` | Query with filters, sorting, pagination, traversals |
| `findById(id)` | Get a single record by ID |
| `create(data)` | Create a new record |
| `update(id, data)` | Partial update by ID |
| `delete(id)` | Delete by ID |

### Query Options

```typescript
const result = await db.users.findMany({
  where: { isActive: true, role: { $in: ['admin', 'editor'] } },
  select: ['id', 'email', 'role'],
  orderBy: 'createdAt',
  orderDirection: 'DESC',
  offset: 0,
  limit: 25,
  includeCount: true,
  traverse: [{ relationship: 'AUTHORED', direction: 'out', select: ['title'] }],
});

console.log(result.items);  // User[]
console.log(result.count);  // total count (if includeCount: true)
```

### Filter Operators

| Operator | Description |
|----------|-------------|
| `$eq` | Equals |
| `$neq` | Not equals |
| `$gt`, `$gte` | Greater than (or equal) |
| `$lt`, `$lte` | Less than (or equal) |
| `$in`, `$notIn` | Value in / not in array |
| `$isNull` | Is null check |
| `$contains` | String contains |
| `$startsWith`, `$endsWith` | String prefix/suffix |
| `$and`, `$or` | Logical operators |

## GraphQL

```typescript
const { data, errors } = await client.graphql(
  'tenant-id',
  `query { users { id email } }`,
  { /* variables */ },
);
```

## Relationships

```typescript
const rels = client.relationships('User', 'user-123');

// Create a relationship
await rels.create('AUTHORED', 'post-456', { role: 'primary' });

// List related entities
const { items, count } = await rels.list('AUTHORED', 'out');

// Delete a relationship
await rels.delete('AUTHORED', 'post-456');
```

## Error Handling

```typescript
import { FlxblApiError, FlxblAuthError, FlxblValidationError } from '@flxbl-dev/client';

try {
  await db.users.create({ email: 'bad' });
} catch (error) {
  if (error instanceof FlxblAuthError) {
    // 401 — re-authenticate
  } else if (error instanceof FlxblValidationError) {
    // 400 — check error.validationErrors
  } else if (error instanceof FlxblApiError) {
    // Generic API error — check error.statusCode
  }
}
```

## How It Works

Under the hood, the client maps collection methods to FLXBL's Dynamic REST API:

| Method | HTTP Request |
|--------|-------------|
| `db.users.findMany(opts)` | `POST /api/v1/dynamic/User/query` |
| `db.users.findById(id)` | `GET /api/v1/dynamic/User/:id` |
| `db.users.create(data)` | `POST /api/v1/dynamic/User` |
| `db.users.update(id, data)` | `PATCH /api/v1/dynamic/User/:id` |
| `db.users.delete(id)` | `DELETE /api/v1/dynamic/User/:id` |

## Requirements

- Node.js >= 18 (for native `fetch`)
- A FLXBL instance with an active schema

## License

MIT