npm package: @shiftengineering/folio
Description: A React component library for embedding and interacting with Folio documents, projects, and files in your application.
Latest version: 0.1.48

# @shiftengineering/folio

A React component library for embedding and interacting with Folio documents, projects, and files in your application.

## Installation

```bash
# Using npm
npm install @shiftengineering/folio

# Using yarn
yarn add @shiftengineering/folio

# Using pnpm
pnpm add @shiftengineering/folio
```

## Usage

This package exports three main features:

1. `FolioProvider` - Context provider for Folio connections
2. `FolioEmbed` - React component to embed Folio in an iframe
3. React hooks for interacting with Folio data:
   - `useFolioProjects` - Get projects data
   - `useFolioFiles` - Get files for a project
   - `useAddFolioProject` - Create new projects
   - `useAddFolioFiles` - Add files to a project
   - `useAddFolioDirectoriesWithFiles` - Add directories with files to a project
   - `useFolioUserMetadata` - Get and update user metadata

### Secure Token Handling

By default, the component uses a secure token passing mechanism via `postMessage` instead of passing the JWT token as a URL query parameter. This ensures your token is not visible in network logs or browser history.

The token is passed securely as follows:

1. The iframe loads without the token in the URL
2. When the iframe is ready, it requests the token from the parent via postMessage
3. The parent application responds with the token, which is then used for API requests

If you need backward compatibility with older versions, you can set the `passTokenInQueryParam` property to `true` on the `FolioProvider`:

```jsx
<FolioProvider
  host="http://your-folio-server.com"
  port={5174}
  token={token}
  passTokenInQueryParam={true} // Legacy mode: passes token in URL (less secure)
>
  <App />
</FolioProvider>
```

### Basic Setup

First, wrap your application with the `FolioProvider`:

```jsx
import { FolioProvider } from "@shiftengineering/folio";
import App from "./App";

// The token must be a valid JWT that the Folio backend is configured to accept
const token = "your-jwt-auth-token";

// Optional user metadata to personalize AI responses
const userMetadata = {
  role: "Sales Representative",
  industry: "Healthcare",
};

ReactDOM.render(
  <FolioProvider
    host="http://your-folio-server.com"
    port={5174}
    token={token}
    userMetadata={userMetadata}
  >
    <App />
  </FolioProvider>,
  document.getElementById("root"),
);
```

### Embedding Folio

Use the `FolioEmbed` component to embed Folio in your application:

```jsx
import { FolioEmbed } from "@shiftengineering/folio";

function MyFolioPage() {
  return (
    <div className="folio-page">
      <h1>My Folio Documents</h1>
      <FolioEmbed
        width="100%"
        height="800px"
        className="my-custom-class"
        style={{ border: "1px solid #ccc" }}
      />
    </div>
  );
}
```

### Working with Folio Data

Use the Folio hooks to get and manipulate Folio data:

```jsx
import {
  useFolioProjects,
  useFolioFiles,
  useAddFolioProject,
  useAddFolioFiles,
  useAddFolioDirectoriesWithFiles,
  useFolioUserMetadata,
  type DirectoryEntry,
  type MetadataValue,
} from "@shiftengineering/folio";
import { useState } from "react";

function FolioProjectManager() {
  const [selectedProjectId, setSelectedProjectId] = useState(null);

  // Get projects with loading and error states
  const {
    projects,
    isLoading: isProjectsLoading,
    error: projectsError,
  } = useFolioProjects();

  // Get files for the selected project
  const { files, isLoading: isFilesLoading } = useFolioFiles(selectedProjectId);

  // Get and update user metadata
  const {
    metadata: userMetadata,
    updateMetadata,
    isLoading: isMetadataLoading
  } = useFolioUserMetadata();

  // Add a new project
  const { addProject, isAdding: isCreatingProject } = useAddFolioProject();

  // Add files to a project
  const { addFiles, isAdding: isAddingFiles } =
    useAddFolioFiles(selectedProjectId);

  // Add directories with files to a project
  const { addDirectoriesWithFiles, isAdding: isAddingDirectory } =
    useAddFolioDirectoriesWithFiles(selectedProjectId);

  const handleCreateProject = () => {
    addProject("My New Project");
  };

  const handleUpdateUserMetadata = () => {
    updateMetadata({
      role: "Project Manager",
      industry: "Finance",
      interestedIn: "State contracts"
    });
  };

  const handleAddFile = () => {
    addFiles([{
      blobUrl: "/path/to/file.pdf",
      name: "My Document.pdf",
      userProvidedId: "doc-123" // Required unique identifier for this file
    }]);
  };

  const handleAddSingleDirectory = () => {
    // Create metadata with nested structure
    const metadata = {
      category: "reports",
      details: {
        owner: "John Doe",
        department: "Finance",
        tags: ["important", "quarterly"],
      },
      status: {
        reviewed: true,
        approvalDate: "2023-10-15"
      }
    };

    const directoryEntry: DirectoryEntry = {
      directoryName: "My Documents",
      directoryMetadata: metadata,
      files: [
        {
          blobUrl: "/path/to/file1.pdf",
          name: "Document 1.pdf",
          userProvidedId: "doc-456" // Required unique identifier for this file
        },
        {
          blobUrl: "/path/to/file2.pdf",
          name: "Document 2.pdf",
          userProvidedId: "doc-789" // Required unique identifier for this file
        },
      ],
    };

    addDirectoriesWithFiles(directoryEntry);
  };

  const handleAddMultipleDirectories = () => {
    // Create metadata for each directory with nested structures
    const metadata1 = {
      category: "reports",
      details: {
        owner: "Jane Smith",
        department: "Accounting",
        tags: ["quarterly", "financial"]
      }
    };

    const metadata2 = {
      category: "contracts",
      details: {
        owner: "Legal Team",
        priority: "high",
        clients: ["Acme Inc", "Globex Corp"]
      },
      approvalChain: {
        legalApproved: true,
        executiveApproved: false
      }
    };

    const directories: DirectoryEntry[] = [
      {
        directoryName: "Reports",
        directoryMetadata: metadata1,
        files: [
          {
            blobUrl: "/path/to/report1.pdf",
            name: "Report 1.pdf",
            userProvidedId: "report-1" // Required unique identifier for this file
          },
          {
            blobUrl: "/path/to/report2.pdf",
            name: "Report 2.pdf",
            userProvidedId: "report-2" // Required unique identifier for this file
          },
        ],
      },
      {
        directoryName: "Contracts",
        directoryMetadata: metadata2,
        files: [
          {
            blobUrl: "/path/to/contract1.pdf",
            name: "Contract 1.pdf",
            userProvidedId: "contract-1" // Required unique identifier for this file
          },
          {
            blobUrl: "/path/to/contract2.pdf",
            name: "Contract 2.pdf",
            userProvidedId: "contract-2" // Required unique identifier for this file
          },
        ],
      },
    ];

    addDirectoriesWithFiles(directories);
  };

  if (isProjectsLoading) return <div>Loading projects...</div>;
  if (projectsError) return <div>Error: {projectsError.message}</div>;

  return (
    <div>
      <button onClick={handleCreateProject} disabled={isCreatingProject}>
        {isCreatingProject ? "Creating..." : "Create New Project"}
      </button>

      <h2>Your Projects</h2>
      <ul>
        {projects.map((project) => (
          <li
            key={project.id}
            onClick={() => setSelectedProjectId(project.id)}
            style={{
              fontWeight: project.id === selectedProjectId ? "bold" : "normal",
            }}
          >
            {project.name}
          </li>
        ))}
      </ul>

      {selectedProjectId && (
        <>
          <h2>Project Files</h2>
          <button onClick={handleAddFile} disabled={isAddingFiles}>
            {isAddingFiles ? "Adding..." : "Add File"}
          </button>
          <button onClick={handleAddSingleDirectory} disabled={isAddingDirectory}>
            {isAddingDirectory ? "Adding..." : "Add Directory with Files"}
          </button>
          <button onClick={handleAddMultipleDirectories} disabled={isAddingDirectory}>
            {isAddingDirectory ? "Adding..." : "Add Multiple Directories"}
          </button>

          {isFilesLoading ? (
            <div>Loading files...</div>
          ) : (
            <ul>
              {files.map((file) => (
                <li key={file.id}>
                  {file.name}
                  {file.userProvidedId && <small> (ID: {file.userProvidedId})</small>}
                </li>
              ))}
            </ul>
          )}
        </>
      )}
    </div>
  );
}
```

## Google Analytics 4 Integration

Folio supports analytics event tracking that can be used with Google Analytics 4 or any other analytics provider. This feature enables tracking key user interactions and provides visibility into how users engage with the application.

### Configuration

#### For Google Analytics 4

To enable built-in Google Analytics 4 tracking, add the measurement ID to your environment variables provided to the docker container:

```
VITE_GA4_MEASUREMENT_ID=G-XXXXXXXXXX
```

When this environment variable is present, Folio will automatically initialize GA4 and send events to Google Analytics.

## Server Environment: Configuration

If you're hosting the Folio backend yourself, you can configure various aspects of the server via environment variables.

### Langfuse Observability

Folio supports [Langfuse](https://langfuse.com) for tracing and observability of OpenAI API calls. To enable Langfuse tracing, add the following environment variables:

```
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_BASE_URL=https://cloud.langfuse.com  # or https://us.cloud.langfuse.com for US region
```

When these variables are set, all OpenAI chat and embedding requests will be traced in Langfuse, giving you visibility into:

- Token usage and costs
- Request latency
- User sessions (when using Clerk authentication)
- Error tracking

If these variables are not set, the application will run normally without tracing.

### Embedding Configuration

You can control the embeddings API behavior on a per-container basis via environment variables:

### Embedding Request Rate Limit

```
EMBEDDING_RATE_LIMIT_PER_MINUTE=5000
```

- **What it does**: Caps embedding requests at the specified number per minute for that container.
- **Default**: `5000` if not set.
- **Scaling**: In multi-container setups, set this per container to your desired per-container limit.

### Embedding Token Rate Limit

```
EMBEDDING_TOKENS_PER_MINUTE=5000000
```

- **What it does**: Caps the total number of tokens processed for embeddings per minute for that container.
- **Default**: `5000000` (5 million) if not set.
- **Note**: This helps manage costs and avoid hitting provider token-based rate limits.

### Embedding Batch Size

```
EMBEDDING_MAX_BATCH_SIZE=100
```

- **What it does**: Sets the maximum number of items to include in a single embedding batch request.
- **Default**: `100` if not set.
- **Note**: Larger batches are more efficient but may hit size limits with some providers.

Example with Docker Compose (build args):

```yaml
services:
  folio:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        EMBEDDING_RATE_LIMIT_PER_MINUTE: ${EMBEDDING_RATE_LIMIT_PER_MINUTE:-5000}
        EMBEDDING_TOKENS_PER_MINUTE: ${EMBEDDING_TOKENS_PER_MINUTE:-5000000}
        EMBEDDING_MAX_BATCH_SIZE: ${EMBEDDING_MAX_BATCH_SIZE:-100}
```

#### For Any Analytics Provider

**Important:** Even if you don't configure GA4, you can still capture all analytics events by providing the `onAnalyticsEvent` callback to the `FolioProvider`. This gives you complete flexibility to use any analytics provider of your choice.

### Events Tracked

The following events are tracked automatically:

1. `page_view` - When a user navigates to a new page
2. `chat_sent` - When a user sends a chat message (includes the query)
3. `highlight` - When a user creates a highlight (includes file path and selection length)
4. `add_to_chat` - When a user adds content to chat (includes file path and snippet size)
5. `extract` - When content is extracted (includes file path and extractor type)
6. `switch_project` - When a user switches between projects
7. `file_view` - When a user views a file

### Analytics Event Structure

All analytics events follow this structure:

```typescript
export type AnalyticsEvent =
  | { name: "page_view"; data: { pathname: string; projectId?: string } }
  | { name: "chat_sent"; data: { query: string } }
  | { name: "highlight"; data: { filePath: string; selectionLength: number } }
  | { name: "add_to_chat"; data: { filePath: string; snippetSize: number } }
  | { name: "extract"; data: { filePath: string; extractor: string } }
  | {
      name: "switch_project";
      data: { fromProjectId: string; toProjectId: string };
    }
  | { name: "file_view"; data: { filePath: string } };
```

Each event has:

- A `name` property identifying the event type
- A `data` object with event-specific parameters

### Host Integration

If you're embedding Folio in your application, you can access the analytics event stream by providing the `onAnalyticsEvent` callback to the `FolioProvider`:

```jsx
import { FolioProvider, AnalyticsEvent } from "@shiftengineering/folio";

function YourApp() {
  const handleAnalyticsEvent = (event: AnalyticsEvent) => {
    // Forward to your own analytics system or process the data
    console.log(`Folio event: ${event.name}`, event.data);

    // Example: Send to Google Analytics
    if (window.gtag) {
      window.gtag('event', event.name, event.data);
    }

    // Example: Send to Mixpanel
    if (window.mixpanel) {
      window.mixpanel.track(event.name, event.data);
    }

    // Example: Send to custom analytics endpoint
    fetch('https://your-analytics-api.com/track', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(event)
    });
  };

  return (
    <FolioProvider
      host="http://your-folio-server.com"
      port={5174}
      token="your-auth-token"
      onAnalyticsEvent={handleAnalyticsEvent}
    >
      <YourAppContent />
    </FolioProvider>
  );
}
```

Alternatively, you can listen to the raw custom event directly:

```javascript
window.addEventListener("folio-analytics", (event) => {
  // Access the event data from event.detail
  const { name, data } = event.detail;

  // Forward to your own analytics system or process the data
  console.log(`Folio event: ${name}`, data);
});
```

Both approaches allow host applications to consume the same events regardless of whether GA4 is configured, enabling integration with any analytics service or custom tracking solution.

## Deep Linking

Folio supports deep linking for directories, allowing users to navigate back to the original platform when Folio is embedded in an iframe. This feature is useful when you want to provide a seamless experience between your platform and Folio.

**Note:** Currently, only directories support deep linking. Individual files do not have deep link functionality.

### How It Works

When a directory is created with a `userProvidedId` (using the `useAddFolioDirectoriesWithFiles` hook), a deep link button (external link icon) will appear next to the directory in:

- The file list sidebar
- The directory viewer page

When clicked, this button emits a deep link event containing:

- `userProvidedId` - The unique identifier that was provided when the directory was created
- `metadata` - The complete metadata object that was originally provided when creating the directory

### Deep Link Event Structure

```typescript
export type DeepLinkEvent = {
  userProvidedId: string;
  metadata: Record<string, unknown> | null;
};
```

### Host Integration

To handle deep link events in your host application, provide the `onDeepLinkEvent` callback to the `FolioProvider`:

```jsx
import { FolioProvider, DeepLinkEvent } from "@shiftengineering/folio";

function YourApp() {
  const handleDeepLinkEvent = (event: DeepLinkEvent) => {
    // Navigate back to the original platform
    console.log(`Deep link clicked:`, event);
    console.log(`User Provided ID: ${event.userProvidedId}`);
    console.log(`Metadata:`, event.metadata);

    // Example: Navigate to a specific page in your platform
    if (event.userProvidedId) {
      window.location.href = `/records/${event.userProvidedId}`;
    }

    // Example: Use metadata to construct a more complex URL
    if (event.metadata?.recordType === "contract") {
      window.location.href = `/contracts/${event.userProvidedId}`;
    }
  };

  return (
    <FolioProvider
      host="http://your-folio-server.com"
      port={5174}
      token="your-auth-token"
      onDeepLinkEvent={handleDeepLinkEvent}
    >
      <YourAppContent />
    </FolioProvider>
  );
}
```

## API Reference

### FolioProvider

Context provider that manages Folio application connection settings.

| Prop                    | Type                            | Default              | Description                                                                          |
| ----------------------- | ------------------------------- | -------------------- | ------------------------------------------------------------------------------------ |
| `host`                  | string                          | `'http://localhost'` | Host for the Folio API and iframe                                                    |
| `port`                  | number                          | `5174`               | Port for the Folio API and iframe                                                    |
| `token`                 | string                          | -                    | JWT authentication token that the Folio backend is configured to accept              |
| `userMetadata`          | Record<string, MetadataValue>   | -                    | Optional metadata for the current user that will be used to personalize AI responses |
| `onAnalyticsEvent`      | (event: AnalyticsEvent) => void | -                    | Optional callback for handling analytics events from Folio                           |
| `onDeepLinkEvent`       | (event: DeepLinkEvent) => void  | -                    | Optional callback for handling deep link events when users click directory links     |
| `passTokenInQueryParam` | boolean                         | `false`              | Whether to pass the token in URL (legacy, less secure) instead of using postMessage  |

### FolioEmbed

React component to embed Folio in an iframe.

| Prop          | Type             | Default                                                             | Description                                     |
| ------------- | ---------------- | ------------------------------------------------------------------- | ----------------------------------------------- |
| `width`       | string \| number | `'100%'`                                                            | Width of the iframe                             |
| `height`      | string \| number | `'100vh'`                                                           | Height of the iframe                            |
| `allow`       | string           | `'camera; microphone; clipboard-read; clipboard-write; fullscreen'` | Allow attributes for the iframe                 |
| `style`       | object           | `{}`                                                                | Additional styles for the iframe container      |
| `className`   | string           | `''`                                                                | Additional class names for the iframe container |
| `iframeProps` | object           | `{}`                                                                | Additional props to pass to the iframe          |

### Hooks

#### `useFolioProjects()`

Hook for getting all projects for the current user. Each project includes statistics about its size and file count.

| Return Property | Type                 | Description                           |
| --------------- | -------------------- | ------------------------------------- |
| `projects`      | `FolioProject[]`     | Array of projects with stats          |
| `isLoading`     | `boolean`            | Whether projects are being loaded     |
| `isError`       | `boolean`            | Whether an error occurred             |
| `error`         | `Error \| null`      | Error object if an error occurred     |
| `refetch`       | `() => Promise<...>` | Function to manually refetch projects |

**Project Statistics:**
Each project in the `projects` array includes:

- `approximateSizeInBytes`: Approximate total size in bytes (sum of extracted text content character counts for all files, plus metadata character counts for directories)
- `fileCount`: Total number of files and directories in the project

#### `useFolioFiles(projectId?: number)`

Hook for getting all files for a specific project.

| Return Property | Type                 | Description                        |
| --------------- | -------------------- | ---------------------------------- |
| `files`         | `FolioFile[]`        | Array of files                     |
| `isLoading`     | `boolean`            | Whether files are being loaded     |
| `isError`       | `boolean`            | Whether an error occurred          |
| `error`         | `Error \| null`      | Error object if an error occurred  |
| `refetch`       | `() => Promise<...>` | Function to manually refetch files |

#### `useAddFolioProject()`

Hook for adding a new project.

| Return Property   | Type                                      | Description                            |
| ----------------- | ----------------------------------------- | -------------------------------------- |
| `addProject`      | `(name: string) => void`                  | Function to add a project              |
| `addProjectAsync` | `(name: string) => Promise<FolioProject>` | Async version returning a promise      |
| `isAdding`        | `boolean`                                 | Whether a project is being added       |
| `isError`         | `boolean`                                 | Whether an error occurred              |
| `error`           | `Error \| null`                           | Error object if an error occurred      |
| `newProject`      | `FolioProject \| undefined`               | The newly created project if available |

#### `useDeleteFolioProject()`

Hook for deleting a project and all its associated files, directories, and chunks.

| Return Property      | Type                                   | Description                        |
| -------------------- | -------------------------------------- | ---------------------------------- |
| `deleteProject`      | `(projectId: number) => void`          | Function to delete a project       |
| `deleteProjectAsync` | `(projectId: number) => Promise<void>` | Async version returning a promise  |
| `isDeleting`         | `boolean`                              | Whether a project is being deleted |
| `isError`            | `boolean`                              | Whether an error occurred          |
| `error`              | `Error \| null`                        | Error object if an error occurred  |

#### `useAddFolioFiles(projectId?: number)`

Hook for adding files to a project. Files are always created at the root level (parentId = null) and are not directories.

| Return Property | Type                                                                                           | Description                        |
| --------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------- |
| `addFiles`      | `(files: { blobUrl: string; name: string; userProvidedId: string }[]) => void`                 | Function to add files              |
| `addFilesAsync` | `(files: { blobUrl: string; name: string; userProvidedId: string }[]) => Promise<FolioFile[]>` | Async version returning a promise  |
| `isAdding`      | `boolean`                                                                                      | Whether files are being added      |
| `isError`       | `boolean`                                                                                      | Whether an error occurred          |
| `error`         | `Error \| null`                                                                                | Error object if an error occurred  |
| `newFiles`      | `FolioFile[] \| undefined`                                                                     | The newly added files if available |

#### `useAddFolioDirectoriesWithFiles(projectId?: number)`

Hook for adding one or more directories with files to a project. Directory names must be unique at the root level (duplicates will be silently skipped with a console warning).

| Return Property                | Type                                                                                                                                                                           | Description                                                                                                                                      |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `addDirectoriesWithFiles`      | `(params: DirectoryEntry \| DirectoryEntry[]) => void`                                                                                                                         | Function to add one or more directories with files                                                                                               |
| `addDirectoriesWithFilesAsync` | `(params: DirectoryEntry \| DirectoryEntry[]) => Promise<{ directory: FolioFile \| null; files: FolioFile[] } \| Array<{ directory: FolioFile \| null; files: FolioFile[] }>>` | Async version returning a promise. Returns a single result when given a single directory, or an array of results when given multiple directories |
| `isAdding`                     | `boolean`                                                                                                                                                                      | Whether the directories and files are being added                                                                                                |
| `isError`                      | `boolean`                                                                                                                                                                      | Whether an error occurred                                                                                                                        |
| `error`                        | `Error \| null`                                                                                                                                                                | Error object if an error occurred                                                                                                                |
| `result`                       | `{ directory: FolioFile \| null; files: FolioFile[] } \| Array<{ directory: FolioFile \| null; files: FolioFile[] }> \| undefined`                                             | The newly added directories and files. If a directory is null in a result, it means a directory with that name already existed                   |

#### `useFolioUserMetadata()`

Hook for retrieving and updating the current user's metadata.

| Return Property       | Type                                                         | Description                              |
| --------------------- | ------------------------------------------------------------ | ---------------------------------------- |
| `metadata`            | `string \| null`                                             | The user's metadata as a string, or null |
| `isLoading`           | `boolean`                                                    | Whether metadata is being loaded         |
| `isError`             | `boolean`                                                    | Whether an error occurred                |
| `error`               | `Error \| null`                                              | Error object if an error occurred        |
| `updateMetadata`      | `(metadata: Record<string, MetadataValue>) => void`          | Function to update user metadata         |
| `updateMetadataAsync` | `(metadata: Record<string, MetadataValue>) => Promise<void>` | Async version returning a promise        |
| `isUpdating`          | `boolean`                                                    | Whether metadata is being updated        |
| `refetch`             | `() => Promise<...>`                                         | Function to manually refetch metadata    |

## Types

The library exports these TypeScript types:

| Type                 | Description                                                                                                                                                                                                                                                                                                 |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FolioFile`          | Represents a file in Folio. Contains properties: `id`, `name`, `blobUrl`, `parentId` (null for root items), `isDirectory` (boolean), `userProvidedId` (string), `createdAt` (Date), and `updatedAt` (Date)                                                                                                  |
| `FolioProject`       | Represents a project in Folio. Contains properties: `id`, `name`, `createdAt` (Date), `updatedAt` (Date), `approximateSizeInBytes` (number - sum of extracted text content lengths for all files and metadata lengths for directories), and `fileCount` (number - total count of all files and directories) |
| `MetadataValue`      | Represents metadata values that can be nested. Can be a string, number, boolean, null, object, or array of these types. Used for both directory metadata and user metadata.                                                                                                                                 |
| `DirectoryEntry`     | Represents a directory with metadata and files to be added to Folio. Contains properties: `directoryName`, `directoryMetadata` (now supports nested objects), and `files`                                                                                                                                   |
| `FolioFileInput`     | Input type for adding files to Folio. Contains properties: `blobUrl`, `name`, and `userProvidedId` (required for deduplication)                                                                                                                                                                             |
| `AnalyticsEvent`     | A union type for analytics events sent by Folio. Each event has a `name` property (like "page_view" or "file_view") and a `data` object with event-specific parameters. See the [Analytics Event Structure](#analytics-event-structure) section for details on all event types.                             |
| `DeepLinkEvent`      | Event emitted when a user clicks a deep link button in Folio. Contains properties: `userProvidedId` (string) and `metadata` (Record<string, unknown> \| null). See the [Deep Linking](#deep-linking) section for details.                                                                                   |
| `FolioEmbedProps`    | Props for the FolioEmbed component                                                                                                                                                                                                                                                                          |
| `FolioProviderProps` | Props for the FolioProvider component                                                                                                                                                                                                                                                                       |
| `FolioClient`        | Interface for the client that interacts with the Folio API                                                                                                                                                                                                                                                  |

## License