Skip to content

Commit

Permalink
add shared functions to create browser or server clients
Browse files Browse the repository at this point in the history
  • Loading branch information
dijonmusters committed Aug 30, 2023
1 parent 9b58a60 commit c5f7428
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/shared/src/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// these variables are defined by tsup
declare const PACKAGE_NAME: string;
declare const PACKAGE_VERSION: string;
67 changes: 67 additions & 0 deletions packages/shared/src/createBrowserClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
BrowserCookieAuthStorageAdapter,
CookieOptionsWithName,
SupabaseClientOptionsWithoutAuth,
createSupabaseClient
} from './';

import type { SupabaseClient } from '@supabase/supabase-js';
import type { GenericSchema } from '@supabase/supabase-js/dist/module/lib/types';

// can't type this properly as `Database`, `SchemaName` and `Schema` are only available within `createClientComponentClient` function
let supabase: any;

export function createBrowserClient<
Database = any,
SchemaName extends string & keyof Database = 'public' extends keyof Database
? 'public'
: string & keyof Database,
Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
? Database[SchemaName]
: any
>(
supabaseUrl: string,
supabaseKey: string,
options?: SupabaseClientOptionsWithoutAuth<SchemaName> & {
cookieOptions?: CookieOptionsWithName;
isSingleton?: boolean;
}
): SupabaseClient<Database, SchemaName, Schema> {
if (!supabaseUrl || !supabaseKey) {
throw new Error('supabaseUrl and supabaseKey are required!');
}

const isSingleton = options?.isSingleton ?? true;

const createNewClient = () =>
createSupabaseClient<Database, SchemaName, Schema>(supabaseUrl, supabaseKey, {
...options,
global: {
...options?.global,
headers: {
...options?.global?.headers,
'X-Client-Info': `${PACKAGE_NAME}@${PACKAGE_VERSION}`
}
},
auth: {
storageKey: options?.cookieOptions?.name,
storage: new BrowserCookieAuthStorageAdapter(options?.cookieOptions)
}
});

if (isSingleton) {
// The `Singleton` pattern is the default to simplify the instantiation
// of a Supabase client in the browser - there must only be one
const _supabase = supabase ?? createNewClient();
// For SSG and SSR always create a new Supabase client
if (typeof window === 'undefined') return _supabase;
// Create the Supabase client once in the client
if (!supabase) supabase = _supabase;
return supabase;
}

// This allows for multiple Supabase clients, which may be required when using
// multiple schemas. The user will be responsible for ensuring a single
// instance of Supabase is used for each schema in the browser.
return createNewClient();
}
68 changes: 68 additions & 0 deletions packages/shared/src/createServerClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
CookieAuthStorageAdapter,
CookieOptions,
CookieOptionsWithName,
SupabaseClientOptionsWithoutAuth,
createSupabaseClient
} from './';

import { GenericSchema } from '@supabase/supabase-js/dist/module/lib/types';
import type { SupabaseClient } from '@supabase/supabase-js';

type CookieStore = {
get: (key: string) => string | undefined;
set: (key: string, value: string, options: CookieOptions) => void;
delete: (key: string) => void;
};

class ServerCookieAuthStorageAdapter extends CookieAuthStorageAdapter {
constructor(private readonly cookies: CookieStore, cookieOptions?: CookieOptions) {
super(cookieOptions);
}

protected getCookie(name: string): string | null | undefined {
return this.cookies.get(name);
}
protected setCookie(name: string, value: string): void {
this.cookies.set(name, value, this.cookieOptions);
}
protected deleteCookie(name: string): void {
this.cookies.delete(name);
}
}

export function createServerClient<
Database = any,
SchemaName extends string & keyof Database = 'public' extends keyof Database
? 'public'
: string & keyof Database,
Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
? Database[SchemaName]
: any
>(
supabaseUrl: string,
supabaseKey: string,
options: SupabaseClientOptionsWithoutAuth<SchemaName> & {
cookieOptions?: CookieOptionsWithName;
cookies: CookieStore;
}
): SupabaseClient<Database, SchemaName, Schema> {
if (!supabaseUrl || !supabaseKey) {
throw new Error('supabaseUrl and supabaseKey are required!');
}

return createSupabaseClient<Database, SchemaName, Schema>(supabaseUrl, supabaseKey, {
...options,
global: {
...options?.global,
headers: {
...options?.global?.headers,
'X-Client-Info': `${PACKAGE_NAME}@${PACKAGE_VERSION}`
}
},
auth: {
storageKey: options.cookieOptions?.name,
storage: new ServerCookieAuthStorageAdapter(options.cookies, options?.cookieOptions)
}
});
}
7 changes: 6 additions & 1 deletion packages/shared/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Options } from 'tsup';
import pkg from './package.json';

export const tsup: Options = {
dts: true,
Expand All @@ -11,5 +12,9 @@ export const tsup: Options = {
sourcemap: true,
splitting: false,
bundle: true,
clean: true
clean: true,
define: {
PACKAGE_NAME: JSON.stringify(pkg.name),
PACKAGE_VERSION: JSON.stringify(pkg.version)
}
};

0 comments on commit c5f7428

Please sign in to comment.