# Snapshot

Snapshots passed to user-defined function that are used to compute state updates. These
allow safe and performant access to the denormalized data based on the current state.

```ts
interface Snapshot {
  get(schema, ...args)​ => DenormalizeNullable<typeof schema> | undefined;
  getResponse(endpoint, ...args)​ => { data, expiryStatus, expiresAt };
  getError(endpoint, ...args)​ => ErrorTypes | undefined;
  fetchedAt: number;
  abort: Error;
}
```

> **Tip**
>
> Use [Controller.snapshot()](https://dataclient.io/vue/api/Controller.md#snapshot) to construct a snapshot

## Usage

```ts title="Post"
import { Entity, EntityMixin } from '@data-client/rest';

export class Post extends Entity {
  id = 0;
  author = { id: 0 };
  title = '';
  body = '';
  votes = 0;

  static key = 'Post';

  static schema = {
    author: EntityMixin(
      class User {
        id = 0;
      },
    ),
  };

  get img() {
    return `//loremflickr.com/96/72/kitten,cat?lock=${this.id % 16}`;
  }
}
```

```ts title="PostResource" {15-22}
import { resource } from '@data-client/rest';
import { Post } from './Post';

export { Post };

export const PostResource = resource({
  path: '/posts/:id',
  searchParams: {} as { userId?: string | number } | undefined,
  schema: Post,
}).extend('vote', {
  path: '/posts/:id/vote',
  method: 'POST',
  body: undefined,
  schema: Post,
  getOptimisticResponse(snapshot, { id }) {
    const post = snapshot.get(Post, { id });
    if (!post) throw snapshot.abort;
    return {
      id,
      votes: post.votes + 1,
    };
  },
});
```

```html title="PostItem.vue" {9}
<script setup lang="ts">
  import { useController } from '@data-client/vue';
  import { PostResource, type Post } from './PostResource';

  const props = defineProps<{ post: Post }>();
  const ctrl = useController();

  const handleVote = () => {
    ctrl.fetch(PostResource.vote, { id: props.post.id });
  };
</script>

<template>
  <div>
    <div class="voteBlock">
      <small class="vote">
        <button class="up" @click="handleVote">&nbsp;</button>
        {{ post.votes }}
      </small>
      <img :src="post.img" width="70" height="52" />
    </div>
    <div>
      <h4>{{ post.title }}</h4>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
```

```html title="TotalVotes.vue" {13}
<script setup lang="ts">
  import { Query } from '@data-client/rest';
  import { useQuery } from '@data-client/vue';
  import { PostResource } from './PostResource';

  const queryTotalVotes = new Query(
    PostResource.getList.schema,
    posts => posts.reduce((total, post) => total + post.votes, 0),
  );

  const props = defineProps<{ userId: number }>();
  const totalVotes = useQuery(queryTotalVotes, () => ({ userId: props.userId }));
</script>

<template>
  <div style="text-align: center">
    <small>{{ totalVotes }} votes total</small>
  </div>
</template>
```

```html title="PostList.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { PostResource } from './PostResource';
  import PostItem from './PostItem.vue';
  import TotalVotes from './TotalVotes.vue';

  const userId = 2;
  const posts = await useSuspense(PostResource.getList, { userId });
</script>

<template>
  <div>
    <PostItem v-for="post in posts" :key="post.pk()" :post="post" />
    <TotalVotes :userId="userId" />
  </div>
</template>
```

## Members

### get(schema, ...args) {#get}

Looks up any [Queryable](https://dataclient.io/vue/api/useQuery.md#queryable) [Schema](https://dataclient.io/rest/api/schema.md#schema-overview).

### getResponse(endpoint, ...args) {#getResponse}

```ts title="returns"
{
  data: DenormalizeNullable<E['schema']>;
  expiryStatus: ExpiryStatus;
  expiresAt: number;
}
```

Gets the (globally referentially stable) response for a given endpoint/args pair from state given.

#### data

The denormalize response data. Guarantees global referential stability for all members.

#### [expiryStatus](https://dataclient.io/vue/concepts/expiry-policy.md#expiry-status)

```ts
export enum ExpiryStatus {
  Invalid = 1,
  InvalidIfStale,
  Valid,
}
```

Vue components only suspend while mounting; mounted components keep showing their data while it
refetches.

##### Valid

- Will never suspend.
- Might fetch if data is stale

##### InvalidIfStale

- Will suspend if data is stale.
- Might fetch if data is stale

##### Invalid

- Will always suspend
- Will always fetch

#### expiresAt

A number representing time when it expires. Compare to Date.now().

### getError(endpoint, ...args) {#getError}

Gets the error, if any, for a given endpoint. Returns undefined for no errors.

### fetchedAt

When the fetch was called that resulted in this snapshot.

### abort

This is an Error to be thrown in [Endpoint.getOptimisticResponse()](https://dataclient.io/rest/api/RestEndpoint.md#getoptimisticresponse)
to cancel an optimistic update.
