# DataClientPlugin

[Vue plugin](https://vuejs.org/guide/reusability/plugins.html) that creates the store and
[Controller](https://dataclient.io/vue/api/Controller.md), and provides them to every component in the app. Install it once,
before `app.mount()`; composables only work in components of an app it is installed on.

```ts title="main.ts"
import { createApp } from 'vue';
import { DataClientPlugin } from '@data-client/vue';
import App from './App.vue';

const app = createApp(App);
app.use(DataClientPlugin);
app.mount('#app');
```

[Managers](https://dataclient.io/vue/api/Manager.md) start when the plugin is installed, and stop when the app is unmounted.

## Options

```ts
app.use(DataClientPlugin, options);
```

```typescript
interface ProvideOptions {
  managers?: Manager[];
  initialState?: State<unknown>;
  Controller?: new (props: { gcPolicy: GCInterface }) => Controller;
  gcPolicy?: GCInterface;
}
```

### managers?: Manager\[] {#managers}

List of [Managers](https://dataclient.io/vue/api/Manager.md) to use. This is the main extensibility point of the store.

Defaults to [getDefaultManagers()](https://dataclient.io/vue/api/getDefaultManagers.md), which can also be used to extend the defaults.

```ts title="main.ts"
import { createApp } from 'vue';
import { DataClientPlugin, getDefaultManagers } from '@data-client/vue';
import App from './App.vue';
import MyManager from './MyManager';

const app = createApp(App);
app.use(DataClientPlugin, {
  managers: [...getDefaultManagers(), new MyManager()],
});
```

Default Production:

```typescript
[new NetworkManager(), new SubscriptionManager(PollingSubscription)];
```

Default Development:

```typescript
[
  new DevToolsManager(),
  new NetworkManager(),
  new SubscriptionManager(PollingSubscription),
];
```

### initialState?: State\<unknown> {#initialState}

Instead of starting with an empty cache, you can provide your own initial state. This can
be useful for testing, or rehydrating the cache state when using server side rendering.
[mockInitialState()](https://dataclient.io/vue/api/mockInitialState.md) builds one from fixtures.

```ts title="main.ts"
app.use(DataClientPlugin, { initialState: window.__INITIAL_STATE__ });
```

```typescript
export interface State<T> {
  readonly entities: {
    readonly [entityKey: string]: { readonly [pk: string]: T } | undefined;
  };
  readonly endpoints: {
    readonly [key: string]: unknown | PK[] | PK | undefined;
  };
  readonly indexes: NormalizedIndex;
  readonly meta: {
    readonly [key: string]: {
      readonly date: number;
      readonly fetchedAt: number;
      readonly expiresAt: number;
      readonly prevExpiresAt?: number;
      readonly error?: ErrorTypes;
      readonly invalidated?: boolean;
      readonly errorPolicy?: 'hard' | 'soft' | undefined;
    };
  };
  readonly entitiesMeta: {
    readonly [entityKey: string]: {
      readonly [pk: string]: {
        readonly date: number;
        readonly expiresAt: number;
        readonly fetchedAt: number;
      };
    };
  };
  readonly optimistic: (SetResponseAction | OptimisticAction)[];
  readonly lastReset: number;
}
```

### Controller?: Controller class {#Controller}

This allows you to extend [Controller](https://dataclient.io/vue/api/Controller.md) to provide additional functionality.
This might be useful if you have additional actions you want to dispatch to custom [Managers](https://dataclient.io/vue/api/Manager.md).

```ts title="main.ts"
import { createApp } from 'vue';
import { Controller, DataClientPlugin } from '@data-client/vue';
import App from './App.vue';

export class MyController extends Controller {
  doSomething = () => {
    console.log('hi');
  };
}

const app = createApp(App);
app.use(DataClientPlugin, { Controller: MyController });
```

[useController()](https://dataclient.io/vue/api/useController.md) and `$dataClient` then return a `MyController` instance,
but they are still typed as `Controller`. Cast to reach the added members:

```ts
import { useController } from '@data-client/vue';
import type { MyController } from './main';

const ctrl = useController() as MyController;
ctrl.doSomething();
```

### gcPolicy?: GCInterface {#gcPolicy}

Removes data from the store once no component uses it and it has gone stale. Defaults to
`new GCPolicy()`; pass one to change how often it sweeps or how long unused data is kept.

```ts title="main.ts"
import { createApp } from 'vue';
import { DataClientPlugin, GCPolicy } from '@data-client/vue';
import App from './App.vue';

const app = createApp(App);
app.use(DataClientPlugin, {
  // sweep every 10 minutes
  gcPolicy: new GCPolicy({ intervalMS: 60 * 1000 * 10 }),
});
```

```ts title="GCPolicy options"
new GCPolicy({
  // how often to sweep (default 5 minutes)
  intervalMS: 60 * 1000 * 5,
  // how many stale lifetimes before data is removed (default 2)
  expiryMultiplier: 2,
  // or choose when unused data is removed (replaces expiryMultiplier)
  // here: one minute after it goes stale
  expiresAt: ({ expiresAt }) => expiresAt + 60 * 1000,
});
```

## $dataClient {#dataclient}

The plugin also adds the [Controller](https://dataclient.io/vue/api/Controller.md) as the `$dataClient` global property, so
templates and Options API components (as `this.$dataClient`) can use it without
[useController()](https://dataclient.io/vue/api/useController.md). It is typed as [Controller](https://dataclient.io/vue/api/Controller.md) with no extra setup.

```html title="DeleteTodo.vue"
<script setup lang="ts">
  import { TodoResource } from '@/resources/Todo';

  defineProps<{ id: number }>();
</script>

<template>
  <button @click="$dataClient.fetch(TodoResource.delete, { id })">
    Delete
  </button>
</template>
```

## Using composables

Composables like [useSuspense()](https://dataclient.io/vue/api/useSuspense.md) must run during a component's `setup`, so Vue
knows which app's store to use. Awaiting them requires `<script setup>`: in a hand-written
`async setup()`, composables called after the first `await` lose the component instance and throw.

```html title="TodoDetail.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { TodoResource } from '@/resources/Todo';
  import { UserResource } from '@/resources/User';

  const todo = await useSuspense(TodoResource.get, { id: 1 });
  // still works after the await
  const user = await useSuspense(UserResource.get, {
    id: todo.value.userId,
  });
</script>
```

Components that `await` must render inside a [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense.html)
boundary.
