npm package: @kodes.agency/gbp-client
Description: TypeScript client library for Google Business Profile APIs with discovery-driven code generation
Author: Kodes Agency
Latest version: 0.1.0

# @kodes.agency/gbp-client

A TypeScript client library for Google Business Profile APIs with OAuth 2.0 authentication, discovery-driven API methods, automatic retry, and comprehensive error handling.

## Features

- 🔐 **OAuth 2.0 Authentication** - Service account and OAuth2 client credentials support
- 🔍 **Discovery-driven APIs** - Dynamic API methods based on Google's discovery documents
- 🔄 **Automatic Retry** - Exponential backoff with configurable retry policies
- ✅ **Request/Response Validation** - Zod schemas for type-safe API calls
- 📝 **Configurable Logging** - Built-in logger with adjustable log levels
- 🎯 **Convenience APIs** - High-level methods for common operations

## Installation

```bash
npm install @kodes.agency/gbp-client
# or
pnpm add @kodes.agency/gbp-client
```

## Prerequisites

- Node.js 20.0.0 or higher
- Google Cloud project with Business Profile API enabled
- Service account credentials or OAuth2 client credentials

## Quick Start

### 1. Set up credentials

Create a `.env` file in your project root:

```env
GOOGLE_CLIENT_EMAIL=your-service-account@project.iam.gserviceaccount.com
GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
```

### 2. Initialize the client

```typescript
import { createClient, isOk, isErr } from '@kodes.agency/gbp-client';

const result = await createClient({
  credentials: {
    type: 'service_account',
    clientEmail: process.env.GOOGLE_CLIENT_EMAIL!,
    privateKey: process.env.GOOGLE_PRIVATE_KEY!,
  },
})();

if (isOk(result)) {
  const client = result.right;

  // List your accounts
  const accounts = await client.api.accounts.list();

  if (isOk(accounts)) {
    console.log('Accounts:', accounts.right.accounts);
  }
} else {
  console.error('Failed to initialize client:', result.left.message);
}
```

### 3. Use the convenience APIs

```typescript
// List locations for an account
const locations = await client.api.locations.list('accounts/{accountId}');

// List reviews for a location
const reviews = await client.api.reviews.list('accounts/{accountId}/locations/{locationId}');

// Reply to a review
const reply = await client.api.reviews.reply(
  'accounts/{accountId}/locations/{locationId}/reviews/{reviewId}',
  'Thank you for your feedback!'
);
```

## Authentication

The library supports two authentication methods:

### Service Account (Recommended for server-side applications)

```typescript
import { createClient, isOk } from '@kodes.agency/gbp-client';

const result = await createClient({
  credentials: {
    type: 'service_account',
    clientEmail: process.env.GOOGLE_CLIENT_EMAIL!,
    privateKey: process.env.GOOGLE_PRIVATE_KEY!,
  },
})();
```

**Setup**:
1. Create a service account in Google Cloud Console
2. Download the JSON key file
3. Enable domain-wide delegation if accessing user data
4. Grant the service account access to your Business Profile locations

### OAuth2 Client Credentials

```typescript
import { createClient, isOk } from '@kodes.agency/gbp-client';

const result = await createClient({
  credentials: {
    type: 'oauth2_client',
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
  },
})();
```

**Setup**:
1. Create OAuth2 credentials in Google Cloud Console
2. Obtain a refresh token through the OAuth2 authorization flow
3. The library automatically refreshes access tokens

## Configuration

```typescript
import { createClient } from '@kodes.agency/gbp-client';

const result = await createClient({
  credentials: {
    type: 'service_account',
    clientEmail: process.env.GOOGLE_CLIENT_EMAIL!,
    privateKey: process.env.GOOGLE_PRIVATE_KEY!,
  },
  // Optional configuration
  scopes: ['https://www.googleapis.com/auth/business.manage'],
  timeout: 30000, // 30 seconds
})();
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `credentials` | `Credentials` | *required* | Authentication credentials |
| `scopes` | `string[]` | `['https://www.googleapis.com/auth/business.manage']` | OAuth scopes |
| `timeout` | `number` | `30000` | Request timeout in milliseconds |

## API Overview

### Client Interface

The client provides access to all Business Profile APIs:

```typescript
interface Client {
  // Token management
  getTokenManager(): TokenManager;
  getAccessToken(): TaskEither<ApiError, string>;

  // Discovery-based API access
  loadDiscovery(apiName: string): TaskEither<ApiError, ParsedDiscoveryDocument>;
  getSchemaRegistry(): SchemaRegistry;
  checkVersion(apiName: string, minVersion?: string): TaskEither<ApiError, VersionInfo>;

  // Convenience APIs
  api: ConvenienceApis;

  // Lifecycle
  close(): Promise<void>;
}
```

### Convenience APIs

High-level methods for common operations:

#### Accounts API

```typescript
// List all accessible accounts
const accounts = await client.api.accounts.list();

// Get a specific account
const account = await client.api.accounts.get('accounts/{accountId}');
```

#### Locations API

```typescript
// List locations for an account
const locations = await client.api.locations.list('accounts/{accountId}', {
  pageSize: 50,
  filter: 'location.state=ACTIVE',
});

// Get a specific location
const location = await client.api.locations.get('accounts/{accountId}/locations/{locationId}');

// Update a location
const updated = await client.api.locations.update(
  'accounts/{accountId}/locations/{locationId}',
  { title: 'New Business Name' },
  'title' // updateMask
);
```

#### Reviews API

```typescript
// List reviews for a location
const reviews = await client.api.reviews.list('accounts/{accountId}/locations/{locationId}');

// Get a specific review
const review = await client.api.reviews.get('accounts/{accountId}/locations/{locationId}/reviews/{reviewId}');

// Reply to a review
const reply = await client.api.reviews.reply(
  'accounts/{accountId}/locations/{locationId}/reviews/{reviewId}',
  'Thank you for your feedback!'
);
```

## Error Handling

The library uses a `Result` type pattern for explicit error handling. All async operations return `TaskEither<ApiError, T>` from fp-ts.

### Using isOk/isErr

```typescript
import { createClient, isOk, isErr } from '@kodes.agency/gbp-client';

const result = await createClient({ credentials })();

if (isOk(result)) {
  const client = result.right;
  // Success - use the client
} else {
  const error = result.left;
  console.error(`Error [${error.code}]: ${error.message}`);

  if (error.recovery) {
    console.log(`Suggested action: ${error.recovery.action}`);
  }
}
```

### Using fp-ts pipe

```typescript
import { pipe } from 'fp-ts/function';
import * as TE from 'fp-ts/TaskEither';
import { createClient, isOk } from '@kodes.agency/gbp-client';

const program = pipe(
  createClient({ credentials }),
  TE.chain((client) => client.api.accounts.list()),
  TE.map((response) => response.accounts),
  TE.mapLeft((error) => `Failed: ${error.message}`)
);

const result = await program();
```

### Error Codes Reference

| Code | Description | Recovery Action |
|------|-------------|-----------------|
| `AUTHENTICATION_FAILED` | Failed to authenticate with Google | Verify credentials are correct and have required scopes |
| `AUTHORIZATION_FAILED` | Authenticated but not authorized for resource | Check account permissions and API access |
| `INVALID_CREDENTIALS` | Credentials format is invalid | Verify credentials JSON structure matches expected format |
| `TOKEN_EXPIRED` | Access token has expired | Token refresh is automatic; if persistent, check system clock |
| `DISCOVERY_FAILED` | Failed to fetch API discovery document | Check network connectivity; API may be temporarily unavailable |
| `VALIDATION_ERROR` | Request parameters failed validation | Check parameter types and required fields |
| `RATE_LIMITED` | API rate limit exceeded | Implement exponential backoff; reduce request frequency |
| `SERVER_ERROR` | Server returned 5xx error | Retry with backoff; check Google Cloud status |
| `NETWORK_ERROR` | Network request failed | Check internet connectivity and DNS resolution |
| `TIMEOUT` | Request exceeded timeout limit | Increase timeout in config or reduce payload size |
| `PARSE_ERROR` | Failed to parse API response | Response may be malformed; check API version compatibility |
| `RETRY_EXHAUSTED` | All retry attempts failed | Check underlying error; may need manual intervention |
| `UNKNOWN_ERROR` | Unexpected error occurred | Check error details for more information |

## Examples

For more detailed examples, see [USAGE.md](./USAGE.md).

- [List Accounts](./USAGE.md#list-accounts)
- [List Locations with Pagination](./USAGE.md#list-locations-with-pagination)
- [Get/Update Location](./USAGE.md#get-and-update-location)
- [List Reviews](./USAGE.md#list-reviews)
- [Reply to Reviews](./USAGE.md#reply-to-review)
- [Error Handling Patterns](./USAGE.md#error-handling-patterns)

## License

MIT