# @theorchard/lambda-apollo

This package provides an ApolloClient for usage in node lambdas.

## Usage

You must specify a Profile and Identity for your lambda to execute as, as well as the name of your lambda.

You should create your client at the top-level of the file to ensure it is shared between executions.

```ts
import { createClient } from '@theorchard/lambda-apollo';
import { authentication, lambdaName } from 'constants';

const client = createClient({
    authentication,
    lambdaName,
});
```

To execute a query call the [query](https://www.apollographql.com/docs/react/api/core/ApolloClient#ApolloClient.query) method.

To execute a mutation call the [mutate](https://www.apollographql.com/docs/react/api/core/ApolloClient#ApolloClient.mutate) method.

```ts
import { gql } from '@theorchard/lambda-apollo';

const queryDocument = gql`
    query { ... }
`;
const mutationDocument = gql`
    mutation { ... }
`;

export const handler = SentryLambda.wrapHandler(async (event) => {
    const response = await client.query({
        query: queryDocument,
        variables: {
            // Variables here.
        },
    });
    if (response.errors) throw new Error(response.errors);

    const response2 = await client.mutate({
        mutation: mutationDocument,
        variables: {
            // variables here.
        },
    });
    if (response2.errors) throw new Error(response2.errors);
});
```

**Note that GraphQL errors are not thrown by default, you have to manage the `errors` key in the response yourself.**

## Batching

The client is built with the [BatchHttpLink](https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/) by default, with `batchMax` set to `100`. You can send additional configuration to the batch link with the `batchOptions` constructor argument.

To use batching, call `.query` or `.mutate` in parallel:

```ts
const results = await Promise.all([
    client.query({
        query,
        variables: {
            id: 1,
        },
    }),
    client.query({
        query,
        variables: {
            id: 2,
        },
    }),
]);
```

Each query will get its own data and errors. Note that this results in a single network request, but each query is executed separately on the GraphQL gateway - meaning that this may not fully utilize DataLoaders.

## Additional Links

You can pass additional links in when you create the Apollo Client with the `links` field.

### Retry Link

If your individual operations are idempotent, you can pass in a [RetryLink](https://www.apollographql.com/docs/react/api/link/apollo-link-retry/) to retry network errors transparently:

```ts
import { apolloLinks, createClient } from '@theorchard/lambda-apollo';
import { authentication, lambdaName } from 'constants';

const retryLink = new apolloLinks.RetryLink();

const client = createClient({
    authentication,
    lambdaName,
    links: [retryLink],
});
```

## Authorization Link

You can override the link used with the `authorizationLink` field. By default, the authorizationLink will use a shared M2M token. However, this token is identity-less, and thus, difficult to make authorization decisions on.

As you migrate a Lambda fn to use the Dedicated M2M Token system (where the token has a unique identity and thus easier to make authorization decisions on), you should override the default shared M2M Token usage. This looks like:

```ts
import { m2mAuthorizationLink, M2MTokenManager } from '@theorchard/backend-m2m-token-manager';
import { SecretsManager } from '@theorchard/backend-secrets-manager-sdk-v3';
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { createClient, apolloLinks } from '@theorchard/lambda-apollo';
import { APPLICATION_NAME, AUTHENTICATION } from '../constants';
import { ENVIRONMENT } from '../config';

const client = createClient({
    authentication: AUTHENTICATION, // These provide Profile Headers, but we are moving toward deprecating them
    lambdaName: APPLICATION_NAME,
    authorizationLink: m2mAuthorizationLink({
        m2mTokenManager: new M2MTokenManager({
            secretsManager: new SecretsManager({
                secretsManagerClient: new SecretsManagerClient(),
            }),
            environment: ENVIRONMENT,
            serviceName: APPLICATION_NAME,
        }),
    }),
});
```
