0.2.1 • Published 3 years ago

@galaxis/utils v0.2.1

Weekly downloads
-
License
MIT
Repository
-
Last release
3 years ago

Galaxis Utils

npm

Common Galaxis utils.

Installation

yarn add @galaxis/utils

You need to install Galaxis Core as well, directly or indirectly.

The library is compiled to modern JS, but it should work in all reasonable browsers with the help of properly configured Babel.

Public API

⚠ Anything that is not documented here is not considered a part of public API and may change at any time.

getStaticRequest()

This utility creates a factory that merges provided request (query or mutation) with the request that was passed to the factory.

See Example of using requests for details.

const getRequest = getStaticRequest(request, merge);

Arguments

NameTypeDescriptionRequired
requestREQUESTA complete request (query or mutation).Yes
merge(r1: R1, r2: R2) => R1 & R2A function that will merge provided request with the request that was passed to the factory. mergeDeepNonUndefined() should work for a vast majority of cases.Yes

Return value

(request: Partial<REQUEST>) => REQUEST

getParametrizedRequest()

This utility is similar to getStaticRequest, but it's useful when you can't describe complete query or mutation statically. In that case, you provide a factory for creating one instead.

See Example of using requests for details.

const getRequest = getParametrizedRequest(factory, merge);

Arguments

NameTypeDescriptionRequired
factory(params: P) => REQUESTA factory for creating a request based on requestParams field of the passed request.Yes
merge(r1: R1, r2: R2) => R1 & R2A function that will merge the request from the factory with the rest of the passed request. mergeDeepNonUndefined() should work for a vast majority of cases.Yes

Return value

(request: Omit<REQUEST, 'requestParams'> & { requestParams: P }) => REQUEST

immerify()

This utility simplifies cache updates using Immer. With it, you can just update cacheData without worrying about immutability.

const query: AppQuery = {
    requestParams: { foo: 'foo' },
    toCache: immerify(({ cacheData, data }) => {
        cacheData.field = data;
    }),
};

Arguments

NameTypeDescriptionRequired
toCache(opts: CacheAndDataOptions) => void;A function like normal toCache, but the cacheData parameter can be updated directly.Yes

Return value

(opts: CacheAndDataOptions) => C;

memoize()

This utility helps with memoization of data, that is calculated based on the cache data.

const query: AppQuery = {
    requestParams: { id: '1' },
    fromCache: memoize(
        ({ cacheData, requestParams }) => {
            const firstDataPiece = cacheData.first[requestParams.id];
            const secondDataPiece = cacheData.second[requestParams.id];

            return firstDataPiece && secondDataPiece ? { ...firstDataPiece, ...secondDataPiece } : undefined;
        },
        ({ cacheData, requestParams }) => [cacheData.first[requestParams.id], cacheData.second[requestParams.id]],
    ),
};

Arguments

NameTypeDescriptionRequired
fromCache(opts: CacheOptions) => D | undefined A normal fromCache function.Yes
getDeps(opts: CacheOptions) => unknown[] A function that returns the parts of cacheData that this fromCache depends on. These dependencies are compared by reference. The new value will be calculated only if some dependency changes. Note that the length of dependencies can't change.Yes

Return value

(opts: CacheOptions) => D | undefined

mergeDeepNonUndefined()

It's a small wrapper for lodash.merge. The only difference is that it doesn't mutate arguments.

objectHash()

It's the object-hash without any changes.

Example of using requests

⚠ The example focuses on Query, but it's all the same for Mutation.

Let's say we got the following types, specific to the network interface we're using:

// Params that can change for the given resource
export interface DynamicParams {
    vars?: Record<string, string>;
}

// Params that are static for the given resource
export interface StaticParams {
    path: string;
}

// We use a generic to have the ability to type DynamicParams better
export type Params<T extends DynamicParams = DynamicParams> = StaticParams & T;

We can define the creators of parametrized queries once in the application:

import { getParametrizedRequest, getStaticRequest, mergeDeepNonUndefined } from '@galaxis/utils';
import { DynamicParams, Params } from 'some-network-interface';
import { CacheData } from './path/to/cache';

export type AppQuery<D extends NonUndefined, R extends DynamicParams = DynamicParams> = Query<
    CacheData,
    D,
    Error,
    Params<R>
>;

export function getQuery<D extends NonUndefined, R extends DynamicParams = DynamicParams, P = R>(
    factory: (params: P) => AppQuery<D, R>,
) {
    return getParametrizedRequest(factory, mergeDeepNonUndefined);
}

export function getStaticQuery<D extends NonUndefined, R extends DynamicParams = DynamicParams>(query: AppQuery<D, R>) {
    return getStaticRequest(query, mergeDeepNonUndefined);
}

Then use it like this:

import { getQuery } from './path/to/getQuery';
import { client } from './path/to/client';

interface MyQueryData {
    someField: string;
}

const myQuery = getQuery<MyQueryData>((params) => ({
    // Existence of 'requestParams' is checked, typed as Params
    requestParams: {
        path: '/resource', // Existence of 'path' is checked
        ...params, // Params are just DynamicParams
    },
    fetchPolicy: 'cache-and-network', // We can optionally specify other Query fields
}));

client
    .fetchQuery(
        myQuery({
            // Existence of 'requestParams' is checked, typed as DynamicParams
            requestParams: {
                vars: { foo: 'foo' },
            },
            fetchPolicy: 'cache-first', // We can optionally override other Query fields
        }),
    )
    .then((data) => console.log(data)); // 'data' is typed as MyQueryData

We can type dynamic params better:

import { getQuery } from './path/to/getQuery';
import { client } from './path/to/client';

interface MyQueryData {
    someField: string;
}

interface MyQueryParams {
    vars: {
        foo: string;
        bar: string;
    };
}

const myQuery = getQuery<MyQueryData, MyQueryParams>((params) => ({
    // Existence of 'requestParams' is checked, typed as Params<MyQueryParams>
    requestParams: {
        path: '/resource', // Existence of 'path' is checked
        ...params, // Params are MyQueryParams
    },
    fetchPolicy: 'cache-and-network', // We can optionally specify other Query fields
}));

client
    .fetchQuery(
        myQuery({
            // Existence of 'requestParams' is checked, typed as MyQueryParams
            requestParams: {
                vars: { foo: 'foo', bar: 'bar' },
            },
            fetchPolicy: 'cache-first', // We can optionally override other Query fields
        }),
    )
    .then((data) => console.log(data)); // 'data' is typed as MyQueryData

We can go even further and allow ourselves to partially specify dynamic params:

import { getQuery } from './path/to/getQuery';
import { client } from './path/to/client';

interface MyQueryData {
    someField: string;
}

interface MyQueryParams {
    vars: {
        foo: string;
        bar: string;
    };
}

interface MyQueryCustomParams {
    foo: string;
}

const myQuery = getQuery<MyQueryData, MyQueryParams, MyQueryCustomParams>((params) => ({
    // Existence of 'requestParams' is checked, typed as Params<MyQueryParams>
    requestParams: {
        path: '/resource', // Existence of 'path' is checked
        vars: {
            foo: params.foo, // Params are MyQueryCustomParams
            bar: 'bar',
        },
    },
    fetchPolicy: 'cache-and-network', // We can optionally specify other Query fields
}));

client
    .fetchQuery(
        myQuery({
            requestParams: { foo: 'foo' }, // Existence of 'requestParams' is checked, typed as MyQueryCustomParams
            fetchPolicy: 'cache-first', // We can optionally override other Query fields
        }),
    )
    .then((data) => console.log(data)); // 'data' is typed as MyQueryData

Finally, if our query is static:

import { getStaticQuery } from './path/to/getQuery';
import { client } from './path/to/client';

interface MyQueryData {
    someField: string;
}

interface MyQueryParams {
    vars: {
        foo: string;
        bar: string;
    };
}

const myQuery = getStaticQuery<MyQueryData, MyQueryParams>({
    // Existence of 'requestParams' is checked, typed as Params<MyQueryParams>
    requestParams: {
        path: '/resource',
        vars: {
            foo: 'foo',
            bar: 'bar',
        },
    },
    fetchPolicy: 'cache-and-network', // We can optionally specify other Query fields
});

client
    .fetchQuery(
        myQuery({
            // We could override 'requestParams' as well
            fetchPolicy: 'cache-first', // We can optionally override other Query fields
        }),
    )
    .then((data) => console.log(data)); // 'data' is typed as MyQueryData