npm package: @reflog-io/client
Description: TypeScript client for Reflog with runtime protobuf type generation
Latest version: 0.1.2

# @reflog/client

TypeScript client for Reflog with runtime protobuf type generation. This client automatically generates type-safe methods from your custom protobuf definitions at runtime.

## Installation

```bash
npm install @reflog/client
```

Or if developing locally:

```bash
npm install
npm run build
```

## Running the Example

To run the example file, you need to use `ts-node` (included as a dev dependency):

```bash
npm install
npm run example
```

Or directly with ts-node:

```bash
npx ts-node example.ts
```

Make sure to set the required environment variables:

```bash
export REFLOG_HOST=localhost:50051
export REFLOG_CLIENT_ID=my-client-id
npm run example
```

**Note:** You cannot run `node example.ts` directly because it's a TypeScript file. You must use `ts-node` or compile it first with `npm run build` and then run the compiled JavaScript from the `dist` folder.

## Configuration

The client requires two environment variables:

- `REFLOG_HOST`: The gRPC server host (e.g., `localhost:50051`)
- `REFLOG_CLIENT_ID`: Your client identifier

Alternatively, you can pass these as configuration options when creating the client.

## Usage

### Basic Example

```typescript
import { createClient } from '@reflog/client';

async function main() {
  // Initialize the client (reads REFLOG_HOST and REFLOG_CLIENT_ID from env)
  const reflog = await createClient();

  // Insert a user
  await reflog.insert.user({
    entity_id: 'user-123',
    payload: {
      id: 'user-123',
      name: 'John Doe',
      email: 'john@example.com',
    },
  });

  // Update an article
  await reflog.update.article({
    entity_id: 'article-456',
    payload: {
      id: 'article-456',
      title: 'Updated Title',
      body: 'Updated body content',
    },
  });

  // Delete a user
  await reflog.delete.user({ entity_id: 'user-123' });

  // Close the connection when done (waits for pending background retries for 5 seconds)
  await reflog.close();
}

main().catch(console.error);
```

### With Custom Configuration

```typescript
import { createClient } from '@reflog/client';

const reflog = await createClient({
  host: 'localhost:50051',
  clientId: 'my-client-id',
  retry: {
    maxRetries: 5,
    initialBackoffMs: 200,
    maxBackoffMs: 3000,
  },
});
```

### Type Safety

The client automatically generates methods based on your protobuf definitions. For example, if your `custom.proto` defines:

```protobuf
message User {
  string id = 1;
  string name = 2;
  string email = 3;
}

message Article {
  string id = 1;
  string author_id = 2;
  string title = 3;
  string body = 4;
}
```

The client will automatically provide:

- `reflog.insert.user()`
- `reflog.insert.article()`
- `reflog.update.user()`
- `reflog.update.article()`
- `reflog.delete.user()`
- `reflog.delete.article()`

All methods are fully typed based on your protobuf schema.

## API

### `createClient(config?: Partial<ReflogConfig>): Promise<ReflogClient>`

Creates and initializes a new Reflog client. The client will automatically parse your `custom.proto` file and generate type-safe methods.

### `ReflogClient`

#### Methods

- `insert.<entityType>(options: IngestOptions): Promise<IngestResponse>` - Insert/create a new entity (uses background retries)
- `update.<entityType>(options: IngestOptions): Promise<IngestResponse>` - Update an existing entity (uses background retries)
- `delete.<entityType>(options: DeleteOptions): Promise<IngestResponse>` - Delete an entity (uses background retries)
- `close(waitForRetries?: boolean, timeoutMs?: number): Promise<void>` - Close the gRPC connection and optionally wait for pending background retries

#### Types

```typescript
interface IngestOptions {
  entity_id: string;
  payload: Record<string, any>; // Must match your protobuf message definition
  created_at?: number; // Optional Unix timestamp for create operations (milliseconds)
  updated_at?: number; // Optional Unix timestamp for update operations (milliseconds)
  deleted_at?: number; // Optional Unix timestamp for update operations (milliseconds)
}

interface DeleteOptions {
  entity_id: string;
  deleted_at?: number; // Optional Unix timestamp for delete operations (milliseconds)
}

interface IngestResponse {
  ok: boolean;
  message: string;
}
```

### Retries

The client automatically retries certain transient gRPC errors (such as `UNAVAILABLE` or `DEADLINE_EXCEEDED`) using an exponential backoff strategy with jitter.
You can configure retry behavior via the optional `retry` field on `ReflogConfig`:

```typescript
interface RetryOptions {
  maxRetries?: number;       // default: Infinity (retry forever)
  initialBackoffMs?: number; // default: 100
  maxBackoffMs?: number;     // default: 2000
}
```

Retries are applied to:

- `initialize()` when calling the `GetProtos` RPC
- All ingest operations (`insert.<entityType>`, `update.<entityType>`, `delete.<entityType>`)

## Requirements

- Node.js 16+
- TypeScript 4.5+ (for TypeScript projects)
- Access to the Reflog gRPC server

## Proto Files

The client includes the necessary proto files (`custom.proto`, `ingest.proto`, `options.proto`) in the package. These are automatically loaded at runtime to generate the type-safe API.

## Error Handling

### Background Retries

All ingest operations (`insert`, `update`, `delete`) use **background retries** for maximum reliability:

1. **First attempt**: The operation tries once immediately
2. **On failure**: If the first attempt fails with a retryable error (network issues, server unavailable, etc.), the promise resolves immediately with `{ ok: true, message: 'Request queued for background retry' }`
3. **Background retries**: The client continues retrying in the background until:
   - The operation succeeds, OR
   - Maximum retries are exhausted, OR
   - The client is closed

This ensures your application doesn't block waiting for the service to come back online, while still guaranteeing eventual delivery of your messages.

**Note**: The `initialize()` method uses synchronous retries (it must complete before the client is usable).

### Error Types

- **Non-retryable errors** (thrown immediately): Invalid payload, authentication failures, validation errors
- **Retryable errors** (background retries): Network issues, server unavailable, timeouts

```typescript
// This resolves immediately even if the service is down
// Retries continue in the background automatically
const result = await reflog.insert.user({
  entity_id: 'user-123',
  payload: { id: 'user-123', name: 'John' },
});
// result.ok === true (even if service was down - retries happening in background)

// When closing, wait for pending retries to complete
await reflog.close(); // Waits up to 5 seconds for background retries
// Or close immediately without waiting
reflog.close(false); // Closes immediately, background retries are cancelled
```