npm package: @neuroomnet/cms-client
Description: This is a client library for the Neuroomnet CMS. It is used to interact with the CMS API.
Latest version: 1.9.1

# @neuroomnet/cms-client

This is a client library for the Neuroomnet CMS. It is used to interact with the CMS API.

## Features

- Create, read, update, and remove documents
- Check if a document exists
- Count and find one or more documents
- Create, read, update, and remove the schema
- Create and download files
- Use a cache to work offline
- Use a custom storage to store and retrieve data
- WebSocket connections for server command handling

## Installation

```sh
npm install @neuroomnet/cms-client
```

## Usage

### Create a client

Create a client instance using the `CmsClient` class. The client instance is used to interact with the CMS.

```typescript
import { CmsClient } from '@neuroomnet/cms-client'

const client = new CmsClient({
    baseURL: 'https://cms.neuroom.net',
    auth: {
        username: 'username',
        password: 'password',
    },
})
```

#### With WebSocket Support

You can enable real-time WebSocket connections by providing WebSocket options:

```typescript
import { CmsClient } from '@neuroomnet/cms-client'

const client = new CmsClient({
    baseURL: 'https://cms.neuroom.net',
    auth: {
        username: 'username',
        password: 'password',
    },
    websocket: {
        exhibit: 'your-exhibit-id',
        secret: 'your-secret-key',
        autoConnect: true, // Auto-connect on client creation
        autoReconnect: true, // Auto-reconnect on connection loss
        maxReconnectAttempts: 5, // Maximum reconnection attempts
        reconnectDelay: 1000, // Initial reconnection delay (ms)
        maxReconnectDelay: 30000, // Maximum reconnection delay (ms)
    },
})
```

### Create a collection

To interact with documents, you need to create a collection instance using the `collection` method on the client. Supply the collection function with a type that represents the schema of the documents in the collection to get type safety.

```typescript
import { CmsString, CmsNumber } from '@neuroomnet/cms-client'

type Cat = {
    name: CmsString
    age: CmsNumber
    breed?: CmsString
}

const Cat = client.collection<Cat>('neuroomnet', 'cats')
```

### Infering the type from the schema

You can use the `InferType` utility type to infer the object type from the schema. This is useful when you need to define the schema anyway.

```typescript
import { CmsSchemaObject, InferType } from '@neuroomnet/cms-client'

const CatSchema = {
    fields: {
        name: { type: 'string' },
        age: { type: 'number' },
        breed: { type: 'string', optional: true },
    },
} satisfies CmsSchemaObject

type Cat = InferType<typeof CatSchema>

const Cat = client.collection<Cat>('neuroomnet', 'cats')
```

### Interact with documents

You can create, read, update, and remove documents using the `create`, `read`, `update`, and `remove` methods. You can also check if a document exists using the `exists` method.

```typescript
const { _id } = await Cat.create({
    name: 'Whiskers',
    age: 3,
    breed: 'Siamese',
})

const whiskers = await Cat.read(_id) // { _id: '...', _cms: { ... }, name: 'Whiskers', age: 3, breed: 'Siamese' }

await Cat.update(_id, { breed: 'Tabby' }) // { ..., breed: 'Tabby' }

await Cat.remove(_id) // { ... }

await Cat.exists(_id) // false
```

### Query documents

You can count and find one or more documents using the `count`, `find`, and `findOne` methods. You can also filter, sort, and paginate the results.

```typescript
const catsTotal = await Cat.count()
const tabbiesTotal = await Cat.count({ filter: { breed: 'Tabby' } })

const allCats = await Cat.find()
const tabbies = await Cat.find({ filter: { breed: 'Tabby' } })
const adultCats = await Cat.find({ filter: { age: { $gte: 2 } } })

const whiskers = await Cat.findOne({ filter: { name: 'Whiskers' } })
const oldestCat = await Cat.findOne({ sort: { age: 'descending' } })

const top10Cats = await Cat.find({
    filter: { age: { $lte: 2 } },
    sort: { name: 'ascending' },
    pageSize: 10,
})
```

### Interact with the schema

You can create, read, and update the schema using the `create`, `read`, and `update` methods. The schema is used to define the structure of the documents in the collection.

```typescript
const schema = await Cat.schema.read() // { ..., fields: { name: { type: 'string' }, age: { type: 'number' }, breed: { type: 'string', optional: true } } }

await Cat.schema.update({
    ...schema,
    fields: {
        ...schema.fields,
        image: { type: 'file', mimeType: ['image/jpeg', 'image/png'], optional: true },
    },
})
```

### Interact with files

When you have a file field in your schema, you can upload and download files using the `create` and `download` methods.

```typescript
const whiskers3rdBirthday = fs.readFileSync('whiskers-3rd-birthday.jpg')

const whiskers = await Cat.create(
    {
        name: 'Whiskers',
        age: 3,
        breed: 'Siamese',
        image: {
            name: 'whiskers 3rd birthday',
            path: 'whiskers-3rd-birthday.jpg',
            mime: 'image/jpeg',
            size: whiskers3rdBirthday.length,
        },
    },
    {
        'whiskers-3rd-birthday.jpg': whiskers3rdBirthday,
    }
)

const whiskersImage = await Cat.download(whiskers.image)

console.log(whiskersImage) // <Buffer ...>
console.log(whiskers3rdBirthday.equals(whiskersImage)) // true

const token = await client.getToken()

const whiskersImageUrl = Cat.getFileUrl(whiskers.image, token)

console.log(whiskersImageUrl) // https://cms.neuroom.net/media/neuroomnet/cats/whiskers-3rd-birthday.jpg?token=...
```

### WebSocket Connections

The CMS client supports WebSocket connections for handling server-triggered commands. This is useful for scenarios like live preview updates.

#### Basic WebSocket Usage

```typescript
// Connect to WebSocket server
client.connect()

// Listen for all preview commands
client.on('preview', (message) => {
    console.log('Received preview command:', message.command)
    console.log('Command arguments:', message.args)
})

// Listen for specific commands
client.on('refresh', (message) => {
    console.log('Refresh command received')
    // Handle refresh logic
    window.location.reload()
})

client.on('navigate', (message) => {
    const { url } = message.args
    console.log('Navigate to:', url)
    // Handle navigation logic
    window.location.href = url as string
})

// Remove event listeners
const handler = (message) => console.log('Command received:', message)
client.on('preview', handler)
client.off('preview', handler) // Remove specific handler
client.off('preview') // Remove all handlers for 'preview'

// Disconnect when done
client.disconnect()
```

#### Manual Connection Management

If you prefer manual control over WebSocket connections:

```typescript
const client = new CmsClient({
    baseURL: 'https://cms.neuroom.net',
    auth: { username: 'username', password: 'password' },
    websocket: {
        exhibit: 'your-exhibit-id',
        secret: 'your-secret-key',
        autoConnect: false, // Disable auto-connect
        autoReconnect: false, // Disable auto-reconnect
    },
})

// Connect manually when needed
client.connect()

// Or connect with different credentials
client.connect({
    exhibit: 'different-exhibit',
    secret: 'different-secret',
})
```

#### WebSocket Message Format

The WebSocket client handles incoming messages with the following format:

```typescript
// Incoming command messages
{
    type: 'preview',
    command: 'refresh',
    args: {
        // Express req.query parameters
        param1: 'value1',
        param2: ['value2', 'value3']
    }
}
```

Authentication is handled automatically using the configured exhibit and secret:

```typescript
// Outgoing authentication (sent automatically)
{
    type: 'authorize',
    exhibit: 'your-exhibit-id',
    secret: 'your-secret-key'
}
```

#### WebSocket Configuration Options

| Option                 | Type      | Default | Description                                         |
| ---------------------- | --------- | ------- | --------------------------------------------------- |
| `exhibit`              | `string`  | -       | Required. The exhibit identifier for authentication |
| `secret`               | `string`  | -       | Required. The secret key for authentication         |
| `autoConnect`          | `boolean` | `false` | Auto-connect when client is created                 |
| `autoReconnect`        | `boolean` | `true`  | Auto-reconnect on connection loss                   |
| `maxReconnectAttempts` | `number`  | `5`     | Maximum number of reconnection attempts             |
| `reconnectDelay`       | `number`  | `1000`  | Initial reconnection delay in milliseconds          |
| `maxReconnectDelay`    | `number`  | `30000` | Maximum reconnection delay in milliseconds          |

### Use a cache

This library uses [axios-cache-interceptor](https://axios-cache-interceptor.js.org/) to optionally cache requests. The second argument of the `CmsClient` constructor is an options object for the cache. You can use the `buildMemoryStorage`, `buildWebStorage`, or `buildFsStorage` function to create a cache instance.

> **Note:** The `buildFsStorage` function is only available in the Node.js environment. Import it from `@neuroomnet/cms-client/node`.

```typescript
import { CmsClient } from '@neuroomnet/cms-client'
import { buildFsStorage } from '@neuroomnet/cms-client/node'

const client = new CmsClient(
    {
        baseURL: 'https://cms.neuroom.net',
        auth: {
            username: 'username',
            password: 'password',
        },
    },
    {
        storage: buildFsStorage({ directory: 'cache' }),
    }
)
```

### Use a custom storage

Use the `buildStorage` function to create a storage instance that stores and retrieves data from a custom source.

```typescript
import { CmsClient, buildStorage } from '@neuroomnet/cms-client'

const myCustomStorage = buildStorage({
    async find(key) {
        // ...
    },
    async remove(key) {
        // ...
    },
    async set(key, value) {
        // ...
    },
})

const client = new CmsClient(
    {
        baseURL: 'https://cms.neuroom.net',
        auth: {
            username: 'username',
            password: 'password',
        },
    },
    {
        storage: myCustomStorage,
    }
)
```