Skip to content

Instantly share code, notes, and snippets.

@SandroMaglione
Last active June 10, 2021 18:57
Show Gist options
  • Select an option

  • Save SandroMaglione/efb5b261bf10f3bc47c54a286a5af930 to your computer and use it in GitHub Desktop.

Select an option

Save SandroMaglione/efb5b261bf10f3bc47c54a286a5af930 to your computer and use it in GitHub Desktop.
Utility to simplify the API of supabase-js and make the response fully functional using TaskEither and Option from fp-ts.
// fp_ts
import * as O from 'fp-ts/Option';
import * as TE from 'fp-ts/TaskEither';
import { pipe } from 'fp-ts/lib/function';
import { head } from 'fp-ts/lib/Array';
// Supabase
import { PostgrestFilterBuilder } from '@supabase/postgrest-js';
import { SupabaseQueryBuilder } from '@supabase/supabase-js/dist/main/lib/SupabaseQueryBuilder';
import { SupabaseClient } from '@supabase/supabase-js';
// List of all the tables in your database
// No risk of misspelling the name of the table
type SupabaseTable = 'social';
// Generic error message with payload
interface ErrorMessage<Extra = unknown> {
title: string;
message: string;
extra: O.Option<Extra>;
}
interface PostgrestError {
message: string;
details: string;
hint: string;
code: string;
}
const _supabaseRequest = <ReturnType, ErrorType = unknown>(
query: PostgrestFilterBuilder<ReturnType>,
{
noDataErrorMessage,
onRequestErrorMessage,
extraError,
}: {
noDataErrorMessage: string;
onRequestErrorMessage: (error: PostgrestError) => string;
extraError?: ErrorType;
}
): TE.TaskEither<ErrorMessage<ErrorType>, ReturnType[]> =>
TE.tryCatch<ErrorMessage<ErrorType>, ReturnType[]>(
() =>
new Promise((resolve, reject) =>
query.then(({ error, data }) =>
error == null
? data != null
? resolve(data)
: reject(noDataErrorMessage)
: reject(onRequestErrorMessage(error))
)
),
(reason) => ({
title: `Data request error`,
message: `${reason}`,
extra: O.fromNullable(extraError),
})
);
// Request a list of rows
export const supabaseRequestList =
<ReturnType, ErrorType = unknown>(
table: SupabaseTable,
execute: (
query: SupabaseQueryBuilder<ReturnType>
) => PostgrestFilterBuilder<ReturnType>,
extra?: { extraError?: ErrorType }
) =>
(
source: SupabaseClient
): TE.TaskEither<ErrorMessage<ErrorType>, ReturnType[]> =>
_supabaseRequest(execute(source.from<ReturnType>(table)), {
noDataErrorMessage: `No data list is available (in table ${table})`,
onRequestErrorMessage: ({ message }) =>
`Request list error (in table ${table}): ${message}`,
extraError: extra?.extraError,
});
// Request only a single row
export const supabaseRequestSingle =
<ReturnType, ErrorType = unknown>(
table: SupabaseTable,
execute: (
query: SupabaseQueryBuilder<ReturnType>
) => PostgrestFilterBuilder<ReturnType>,
extra?: { extraError?: ErrorType }
) =>
(
source: SupabaseClient
): TE.TaskEither<ErrorMessage<ErrorType>, ReturnType> =>
pipe(
_supabaseRequest(execute(source.from<ReturnType>(table)).limit(1), {
noDataErrorMessage: `No data is available (in table ${table})`,
onRequestErrorMessage: ({ message }) =>
`Request error (in table ${table}): ${message}`,
extraError: extra?.extraError,
}),
TE.chain((dataList) =>
pipe(
dataList,
head,
O.fold(
(): TE.TaskEither<ErrorMessage<ErrorType>, ReturnType> =>
TE.left({
title: 'Request error',
message: `Data requested is missing (in table ${table})`,
extra: O.none,
}),
(data) => TE.of(data)
)
)
)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment