# @kalisio/feathers-automerge-server

Package to set up an automerge sync server that synchronizes documents with a Feathers API.

## Usage

### As a sync server

In your Feathers application, add the following to your `app` file:

```ts
import { automergeServer } from '@kalisio/feathers-automerge-server'

//...
app.configure(services)
app.configure(
  automergeServer({
    directory: '../../data/automerge',
    serverId: 'test-server',
    syncServicePath: 'automerge',
    async authenticate(accessToken) {
      if (!accessToken) {
        return false
      }
      // Check with the local authentication service if the token is valid
      await app.service('authentication').create({
        strategy: 'jwt',
        accessToken
      })

      return true
    },
    async initializeDocument(servicePath, query) {
      if (servicePath === 'todos') {
        const { username } = query
        return app.service('todos').find({
          paginate: false,
          query: { username }
        })
      }

      return null
    },
    async getDocumentsForData(servicePath, data, documents) {
      if (servicePath === 'todos') {
        return documents.filter(doc => data.username === doc.query.username)
      }

      return []
    },
    async canAccess(query, user) {
      // Only allow access to documents where the query username matches the authenticated user
      return query.username === user?.username
    }
  })
)
```

### Server to server synchronization

```ts
import { automergeServer } from '@kalisio/feathers-automerge-server'

//...
app.configure(services)
app.configure(
  automergeServer({
    syncServerUrl: 'https://server.com',
    directory: '../../data/automerge',
    serverId: 'test-server',
    syncServicePath: 'automerge',
    async getAccessToken() {
      const response = await fetch('http://localhost:3030/authentication', {
        body: JSON.stringify({
          strategy: 'local',
          email: 'david@feathers.dev',
          password: 'test'
        }),
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        }
      })
      const { accessToken } = await response.json()
      return accessToken
    },
    async initializeDocument(servicePath, query) {
      if (servicePath === 'todos') {
        const { username } = query
        return app.service('todos').find({
          paginate: false,
          query: { username }
        })
      }

      return null
    },
    async getDocumentsForData(servicePath, data, documents) {
      if (servicePath === 'todos') {
        return documents.filter(doc => data.username === doc.query.username)
      }

      return []
    },
    async canAccess(query, user) {
      // Only allow access to documents where the query username matches the authenticated user
      return query.username === user?.username
    }
  })
)
```

## Options

The following options are available:

### Common Options

- `directory: string`: The directory where the automerge repository data will be stored. The root document ID will be automatically stored in a `automerge-server.json` file in this directory.
- `serverId: string`: A unique identifier for this server instance (used to track data source).
- `syncServicePath`: The service path where the automerge sync service will be mounted (e.g., 'automerge').
- `canAccess(query: Query, params: Params): Promise<boolean>`: An async function that controls access to documents based on the query and service call params. Called for all operations when a `provider` is present in params (external calls).
- `initializeDocument: (servicePath: string, query: Query, documents: SyncServiceInfo[]) => Promise<unknown[]>`: An async function that initializes document data for a given service path and query. Called when creating new documents.
- `getDocumentsForData: (servicePath: string, data: any, documents: SyncServiceInfo[]) => Promise<SyncServiceInfo[]>`: An async function that determines which documents should be updated when service data changes.
- `getInitialDocuments: (app: Application) => Promise<SyncServiceInfo>`: Return the documents to load when there are no local documents on this server. This is useful when running as a client (`syncServerUrl` is pointing to another server) to determine which documents should be synced with this instance.

### Server Options

- `syncServerWsPath?: string`: The websocket path for the local sync server
- `authenticate: (accessToken: string | null) => Promise<boolean>`: Authenticate an access token that was passed to the connection of the local sync server.

### Client Options

- `syncServerUrl?: string`: Connect to another remote sync server instead (for server to server synchronization)
- `getAccessToken?: () => Promise<string>`: Get an access token for the remote sync server.
