# `@orchard/suite-plugin-persisted-cache`

This package provides the `ApolloPersistedCachePlugin` to help persisting the apollo cache to the browser.

## Usage

Create a new `persistenceWorker.ts` file with the following content:
**Notice that you need to import the `registerWorker` function from the `/worker` folder**

```ts
import { registerWorker } from '@theorchard/suite-plugin-persisted-cache/worker';

registerWorker();
```

Then import it using the special Worker constructor like shown below.
**Note this works only for webpack >= 5.**

```ts
import { ApolloBackgroundPersistedCachePlugin } from '@orchard/suite-plugin-persisted-cache';

const backgroundWorker = new Worker(new URL('./persistenceWorker.ts', import.meta.url));

ApolloPersistedCachePlugin({
    typePolicies,
    backgroundWorker,
});
```

## Filtering in the background

You can filter the incoming data in the background worker by defining `typePersistencePolicies` in the `registerWorker` function.
e.g:

```ts
import { registerWorker } from '@theorchard/suite-plugin-persisted-cache/worker';

registerWorker({
    typePersistencePolicies: {
        GlobalParticipant: {
            // Do not persist the analytics property.
            analytics: false,
        },
        GlobalSoundRecording: {
            analytics: {
                // Only persist if the argument `days` equals -28.
                streamsBySourceOfStreams: (data, { days }) => {
                    if (days === -28) return data;
                    return undefined;
                },
            },
        },
        Query: {
            // Do not persist the `topGlobalSoundRecordings` query.
            // Note that all other queries will be persisted.
            topGlobalSoundRecordings: false,
        },
    },
});
```

## Type persistence policies

The persistence policies mimics the cache type policies. It allows describing which fields and types should be persisted in the browser.

Each type and field can either be a filter function, boolean or a child policy for nested objects.

Note that all types and fields are persisted by default. Policies can **opt-out** of being persisted.

### Examples

#### Exclude whole types

```ts
registerWorker({
    typePersistencePolicies: {
        // Do not persist objects of type `Cart`.
        // All other types will be persisted.
        Chart: false,
    },
});
```

#### Exclude specific properties

```ts
registerWorker({
    typePersistencePolicies: {
        GlobalParticipant: {
            // Do not persist the analytics property.
            // All other properties will be persisted.
            analytics: false,
        },
    },
});
```

#### Conditional filtering

```ts
registerWorker({
    typePersistencePolicies: {
        GlobalParticipant: {
            analytics: {
                streams: {
                    // Only persist timeseries if data set is small.
                    timeseries: (data) => {
                        if (data.length < 1000) return data;
                        return undefined;
                    },
                },
            },
        },
    },
});
```

#### Using variables

Example on how to only persist the first rows of a default query.
This is useful to prevent persisting different permutations of the same query.

```ts
registerWorker({
    typePersistencePolicies: {
        Query: {
            topVideos: (data, variables) => {
                const isDefaultQuery =
                    isEmpty(variables.countries) &&
                    isEmpty(variables.storeIds) &&
                    isEmpty(variables.labelIds) &&
                    isEmpty(variables.subaccountIds) &&
                    isEmpty(variables.globalParticipantIds) &&
                    isEmpty(variables.channelIds) &&
                    isEmpty(variables.distributors) &&
                    variables.orderBy === 'views_1_month_back' &&
                    variables.orderDir === 'desc';

                if (isDefaultQuery)
                    return {
                        ...data,
                        videos: data.videos.slice(0, 25),
                    };
                return undefined;
            },
        },
    },
});
```

Since this is a common use case, there is a small filter util available.
The `defaultListQuery` function accepts an `defaults` object defining the default values.
Anything "empty" (like a an empty array or string) is considered default and need not be defined.

```ts
import { registerWorker, defaultListQuery } from "@theorchard/suite-plugin-persisted-cache/worker";

registerWorker({
    typePersistencePolicies: {
        Query: {
            topVideos: defaultListQuery({
                length: 25,
                dataProp: 'videos',
                defaults: {
                    orderBy:'views_1_month_back',
                    orderDir: 'desc'
                }
            });
        }
    }
});
```

#### Opt in/out

You can use the `optIn` and `optOut` utils to switch between opt-in or opt-out behaviors.
Note that the default behavior is opt-out, meaning you explicitly need to define the properties to exclude with a boolean false.

```ts
import { registerWorker, optIn } from '@theorchard/suite-plugin-persisted-cache/worker';

registerWorker({
    typePersistencePolicies: {
        Query: optIn({
            topGlobalSoundRecordings: true,
            globalSoundRecordingByIsrc: true,
        }),
    },
});
```

You can change the default opt-out behavior by wrapping the whole policy object with `optIn`.

```ts
import { registerWorker, optIn, optOut } from '@theorchard/suite-plugin-persisted-cache/worker';

registerWorker({
    typePersistencePolicies: optIn({
        Query: {
            topGlobalSoundRecordings: true,
            globalSoundRecordingByIsrc: true,
        },
        Artist: optOut({
            analytics: false,
        }),
    }),
});
```

### Debugging

To see what is being persisted, you can specify a set of ids to log to the output.

```ts
import { registerWorker, filter } from '@theorchard/suite-plugin-persisted-cache/worker';

registerWorker({
    debug: {
        inspect: [
            'GlobalSoundRecording:{"isrc":"USSD12100307"}.analytics',
            'GlobalSoundRecording:{"isrc":"QM6P42169803"}',
            'GlobalParticipant:{"id":"e49ea9f9-0a74-4623-b3c6-ff819c7c0a4d"}',
            'ROOT_QUERY',
        ],
    },
});
```
