npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@open-condo/apollo

v1.1.0

Published

A wrapper over @apollo/client that allows you to use persistent cache from local storage, configure TTL, invalidate cache, and use a single configuration for getServerSideProps, SSR, and CSR

Downloads

108

Readme

@open-condo/apollo NPM

A wrapper over @apollo/client that allows you to use persistent cache from local storage, configure TTL, invalidate cache, and use a single configuration for getServerSideProps, SSR, and CSR.

Table of contents

Installation

Peer dependencies

NOTE: This package uses react / react-dom and @apollo/client as its peer dependencies, so make sure you've got ones installed.

You should have no trouble with any react version having a hooks, but we're testing on versions >=16.

Any apollo 3.x.x should be fine too, but all utils are tested on ^3.11.8

Installing packages

Install all (NPM)

npm i @open-condo/apollo react react-dom @apollo/client

Install all (Yarn)

yarn add @open-condo/apollo react react-dom  @apollo/client

Usage

Basic setup

Init utils

To start using @open-condo/apollo in your application, you must first configure ApolloHelper and generate the necessary utilities. To do this, paste the following code somewhere in your application:

// ./lib/apollo.ts

import { ApolloHelper } from '@open-condo/apollo'
import type { InitCacheConfig, InitializeApollo, UseApollo } from '@open-condo/apollo'
import type { NormalizedCacheObject, ApolloClient } from '@apollo/client'

const serverUrl = process.env.SERVER_URL || 'http://localhost:3000'

const cacheConfig: InitCacheConfig = () => {
    return {
        invalidationPolicies: {
            timeToLive: 15 * 60 * 1000, // 15 minutes in milliseconds
        },
    }
}

const apolloHelper = new ApolloHelper({
    uri: `${serverUrl}/api/graphql`,
    cacheConfig,
})

export const initializeApollo: InitializeApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.initializeApollo
export const useApollo: UseApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.generateUseApolloHook()

Init apollo client in your pages/_app.tsx:

Then, simply use generated useApollo hook to obtain client and cachePersistor, which you can pass to your apps child components via standard ApolloProvider:

import { ApolloProvider } from '@apollo/client'

import { CachePersistorContext } from '@open-condo/apollo'

import type { AppProps } from 'next/app'
import type { ReactNode } from 'react'

import { useApollo } from '@/lib/apollo'

export default function App ({ Component, pageProps, router }: AppProps): ReactNode {
    const { client, cachePersistor } = useApollo(pageProps)

    return (
        <ApolloProvider client={client}>
            <CachePersistorContext.Provider value={{ persistor: cachePersistor }}>
                <Component {...pageProps} />
            </CachePersistorContext.Provider>
        </ApolloProvider>
    )
}

After that, you can use any Apollo functions / hooks / utilities as you did before! 🥳

Client usage

Nothing additional is required to use Apollo in client components. cachePersistor can be obtained from the provided useCachePersistor hook to avoid requests while the cache is being loaded.

import React from 'react'

import { useQuery } from '@apollo/client'
import { useCachePersistor } from '@open-condo/apollo'

const MyComponent: React.FC = () => {
    const { persistor } = useCachePersistor()
    const { data, loading } = useQuery({
        query: ...,
        variables: {},
        skip: !persistor,
    })
    // ...
}

SSR usage

To use apollo in SSR environment, use generated initializeApollo to obtain fresh client and extractApolloState to pass prefetched data to the client:

import React from 'react'
import { extractApolloState } from '@open-condo/apollo'
import { prepareSSRContext } from '@open-condo/miniapp-utils/helpers/apollo'

import { initializeApollo } from '@/lib/apollo'

import type { GetServerSideProps } from 'next'

const MyPage: React.FC = () => {
    return null
}

export default MyPage

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
    // NOTE: You should implement this function yourself depending on your business logic, 
    // Common pattern is to extract cookies to "cookie" header, or create Authorization header and so on
    const { headers } = prepareSSRContext(req, res)

    // Init new apollo with initial headers, which will be sent with each request
    const client = initializeApollo({ headers })

    await client.query({ ... })

    // Extract fetched data to pageProps
    return extractApolloState(client, {
        props: { ... }
    })
}

List pagination helpers

@open-condo/apollo also provides a set of utilities to make it easier for you to work with list pagination. To use them, initialise the ListHelper class in your cacheConfig like so:

// ./lib/apollo.ts

import { ApolloHelper } from '@open-condo/apollo'
import type { InitCacheConfig, InitializeApollo, UseApollo } from '@open-condo/apollo'
import type { NormalizedCacheObject, ApolloClient } from '@apollo/client'

const serverUrl = process.env.SERVER_URL || 'http://localhost:3000'

const cacheConfig: InitCacheConfig = (cacheOptions) => {
    // Default helper, use skip / first as pagination arguments
    const listHelper = new ListHelper({ cacheOptions })

    // You can override pagination args like so
    const customListHelper = new ListHelper({ cacheOptions, skipArgName: 'offset', firstArgName: 'limit' })

    return {
        typePolicies: {
            Query: {
                fields: {
                    allMeters: {
                        keyArgs: ['where'],
                        merge: listHelper.mergeLists,
                        read: listHelper.getReadFunction('paginate'),
                    },
                    allResidents: {
                        keyArgs: ['where'],
                        merge: listHelper.mergeLists,
                        read: listHelper.getReadFunction('showAll'),
                    },
                    customQuery: {
                        keyArgs: ['where'],
                        merge: customListHelper.mergeLists,
                        read: customListHelper.getReadFunction('paginate'),
                    }
                },
            },
        },
        invalidationPolicies: {
            timeToLive: 15 * 60 * 1000, // 15 minutes in milliseconds
        },
    }
}

const apolloHelper = new ApolloHelper({
    uri: `${serverUrl}/api/graphql`,
    cacheConfig,
})

export const initializeApollo: InitializeApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.initializeApollo
export const useApollo: UseApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.generateUseApolloHook()

Dynamic API uri

ApolloHelper can accept a function as uri. This function is called when the client is initialised (via initializeApollo or useApollo)

// ./lib/apollo.ts

import getConfig from 'next/config'

import { ApolloHelper } from '@open-condo/apollo'
import type { InitializeApollo, UseApollo } from '@open-condo/apollo'
import type { NormalizedCacheObject, ApolloClient } from '@apollo/client'

const { publicRuntimeConfig: { serviceUrl } } = getConfig()

/**
 * Gets API url.
 * If it's in SSR / production the absolute url is used
 * In dev mode relative url is allowed on a client,
 * so you can debug app on another device sharing the same network
 */
function getApiUrl () {
    if (isDebug() && !isSSR()) {
        return '/api/graphql'
    }

    return `${serviceUrl}/api/graphql`
}

const apolloHelper = new ApolloHelper({
    uri: getApiUrl,
})

export const initializeApollo: InitializeApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.initializeApollo
export const useApollo: UseApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.generateUseApolloHook()

Middlewares

ApolloHelper can accept a set of middlewares representing an ApolloLink | RequestHandler type from @apollo/client, from which a common link is subsequently assembled using the from utility from @apollo/client.

This can be useful if your logic requires additional processing of all requests (headers / error handling, etc. etc.). You can see more details here

// ./lib/apollo.ts

import getConfig from 'next/config'

import { ApolloHelper } from '@open-condo/apollo'
import type { InitializeApollo, UseApollo } from '@open-condo/apollo'
import type { NormalizedCacheObject, ApolloClient } from '@apollo/client'

const { publicRuntimeConfig: { serviceUrl, revision } } = getConfig()

const apolloHelper = new ApolloHelper({
    uri: `${serviceUrl}/api/graphql`,
    middlewares: [
        getTracingMiddleware({
            serviceUrl,
            codeVersion: revision,
        }),
    ],
})

export const initializeApollo: InitializeApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.initializeApollo
export const useApollo: UseApollo<ApolloClient<NormalizedCacheObject>> = apolloHelper.generateUseApolloHook()

Cache invalidation

Cache from @open-condo/apollo are extended from @nerdwallet/apollo-cache-policies, so you can freely explore and use their TTL mechanism.

const cacheConfig: InitCacheConfig = (cacheOptions) => {
    const listHelper = new ListHelper({ cacheOptions })

    return {
        typePolicies: {
            Query: {
                fields: {
                    allMeters: {
                        keyArgs: ['where'],
                        merge: listHelper.mergeLists,
                        read: listHelper.getReadFunction('paginate'),
                    },
                    allResidents: {
                        keyArgs: ['where'],
                        merge: listHelper.mergeLists,
                        read: listHelper.getReadFunction('showAll'),
                    },
                    allServiceConsumers: {
                        keyArgs: ['where'],
                        merge: listHelper.mergeLists,
                        read: listHelper.getReadFunction('showAll'),
                    },
                },
            },
        },
        invalidationPolicies: {
            timeToLive: 15 * 60 * 1000, // 15 minutes in milliseconds,
            types: {
                Contact: {
                    timeToLive: 2 * 60 * 60 * 1000, // 2 hours in milliseconds,
                },
            },
        },
    }
}

Cache identity

@open-condo/apollo also provides a cache identification mechanism. It allows not loading cache from localStorage if its identity does not match the current clients cache (obtained from SSR / CSR). By default, all caches are compared at the following path:

const DEFAULT_IDENTITY_PATH = ['ROOT_QUERY', 'authenticatedUser', '__ref']

To override it - pass the cacheIdentityKey parameter to the cache configuration:

const cacheConfig: InitCacheConfig = (cacheOptions) => {
    const listHelper = new ListHelper({ cacheOptions })

    return {
        cacheIdentityKey: ['ROOT_QUERY', 'me', 'id'],
    }
}