UNPKG

supabase-service-manager

Version:

TypeScript library for user authentication, profile management, and basic CRUD operations using Supabase, designed for vanilla JavaScript consumption

693 lines (692 loc) 21.2 kB
import { createClient } from "@supabase/supabase-js"; function createSuccess(data) { return { success: true, data }; } function createFailure(error) { return { success: false, error }; } function isSuccess(result) { return result.success === true; } function isFailure(result) { return result.success === false; } class AuthService { /** * Initialize the authentication service * @param config - Supabase configuration */ constructor(config) { this.supabase = createClient(config.url, config.anonKey); } /** * Sign up a new user with email and password * @param email - User's email address * @param password - User's password * @param profile - Optional profile data to set during signup * @returns Promise resolving to Result with user and verification status */ async signUp(email, password, profile) { try { const { data, error } = await this.supabase.auth.signUp({ email, password, options: { data: profile || {} } }); if (error) { return createFailure(new Error(error.message)); } if (!data.user) { return createFailure(new Error("User creation failed")); } const user = this.mapSupabaseUserToUser(data.user); const needsVerification = !data.user.email_confirmed_at; return createSuccess({ user, needsVerification }); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Sign up failed")); } } /** * Sign in an existing user with email and password * @param email - User's email address * @param password - User's password * @returns Promise resolving to Result with user and session */ async signIn(email, password) { try { const { data, error } = await this.supabase.auth.signInWithPassword({ email, password }); if (error) { return createFailure(new Error(error.message)); } if (!data.user || !data.session) { return createFailure(new Error("Sign in failed")); } const user = this.mapSupabaseUserToUser(data.user); const session = this.mapSupabaseSessionToAuthSession(data.session, user); return createSuccess({ user, session }); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Sign in failed")); } } /** * Sign out the current user * @returns Promise resolving to Result indicating success or failure */ async signOut() { try { const { error } = await this.supabase.auth.signOut(); if (error) { return createFailure(new Error(error.message)); } return createSuccess(void 0); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Sign out failed")); } } /** * Get the current authenticated user * @returns Promise resolving to Result with current user or null */ async getCurrentUser() { try { const { data: { user }, error } = await this.supabase.auth.getUser(); if (error) { return createFailure(new Error(error.message)); } if (!user) { return createSuccess(null); } const mappedUser = this.mapSupabaseUserToUser(user); return createSuccess(mappedUser); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Failed to get current user")); } } /** * Get the current authentication session * @returns Promise resolving to Result with current session or null */ async getCurrentSession() { try { const { data: { session }, error } = await this.supabase.auth.getSession(); if (error) { return createFailure(new Error(error.message)); } if (!session) { return createSuccess(null); } const user = this.mapSupabaseUserToUser(session.user); const authSession = this.mapSupabaseSessionToAuthSession(session, user); return createSuccess(authSession); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Failed to get current session")); } } /** * Resend email verification for the current user * @returns Promise resolving to Result indicating success or failure */ async resendVerificationEmail() { try { const { data: { user } } = await this.supabase.auth.getUser(); if (!user) { return createFailure(new Error("No authenticated user found")); } const { error } = await this.supabase.auth.resend({ type: "signup", email: user.email }); if (error) { return createFailure(new Error(error.message)); } return createSuccess(void 0); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Failed to resend verification email")); } } // TODO: extract as utility function /** * Map Supabase user object to our User interface * @param supabaseUser - Supabase user object * @returns Mapped User object */ mapSupabaseUserToUser(supabaseUser) { var _a, _b, _c, _d, _e, _f; return { id: supabaseUser.id, email: supabaseUser.email, firstName: ((_a = supabaseUser.user_metadata) == null ? void 0 : _a.firstName) || ((_b = supabaseUser.user_metadata) == null ? void 0 : _b.first_name), lastName: ((_c = supabaseUser.user_metadata) == null ? void 0 : _c.lastName) || ((_d = supabaseUser.user_metadata) == null ? void 0 : _d.last_name), avatar: ((_e = supabaseUser.user_metadata) == null ? void 0 : _e.avatar) || ((_f = supabaseUser.user_metadata) == null ? void 0 : _f.avatar_url), emailVerified: !!supabaseUser.email_confirmed_at, createdAt: supabaseUser.created_at, updatedAt: supabaseUser.updated_at }; } /** * Map Supabase session to our AuthSession interface * @param supabaseSession - Supabase session object * @param user - Mapped user object * @returns Mapped AuthSession object */ mapSupabaseSessionToAuthSession(supabaseSession, user) { return { user, accessToken: supabaseSession.access_token, refreshToken: supabaseSession.refresh_token, expiresAt: supabaseSession.expires_at }; } } class UserService { /** * Initialize the user service * @param supabaseClient - Configured Supabase client */ constructor(supabaseClient) { this.supabase = supabaseClient; } /** * Update the current user's profile information * @param updates - Profile fields to update * @returns Promise resolving to Result with updated user */ async updateProfile(updates) { try { const { data: { user }, error: getUserError } = await this.supabase.auth.getUser(); if (getUserError) { return createFailure(new Error(getUserError.message)); } if (!user) { return createFailure(new Error("No authenticated user found")); } const { data, error } = await this.supabase.auth.updateUser({ data: { ...user.user_metadata, ...updates } }); if (error) { return createFailure(new Error(error.message)); } if (!data.user) { return createFailure(new Error("Profile update failed")); } const updatedUser = this.mapSupabaseUserToUser(data.user); return createSuccess(updatedUser); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Profile update failed")); } } /** * Get the current authenticated user with fresh data * @returns Promise resolving to Result with current user or null */ async getCurrentUser() { try { const { data: { user }, error } = await this.supabase.auth.getUser(); if (error) { return createFailure(new Error(error.message)); } if (!user) { return createSuccess(null); } const mappedUser = this.mapSupabaseUserToUser(user); return createSuccess(mappedUser); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Failed to get current user")); } } // TODO: extract as utility function /** * Map Supabase user object to our User interface * @param supabaseUser - Supabase user object * @returns Mapped User object */ mapSupabaseUserToUser(supabaseUser) { var _a, _b, _c, _d, _e, _f; return { id: supabaseUser.id, email: supabaseUser.email, firstName: ((_a = supabaseUser.user_metadata) == null ? void 0 : _a.firstName) || ((_b = supabaseUser.user_metadata) == null ? void 0 : _b.first_name), lastName: ((_c = supabaseUser.user_metadata) == null ? void 0 : _c.lastName) || ((_d = supabaseUser.user_metadata) == null ? void 0 : _d.last_name), avatar: ((_e = supabaseUser.user_metadata) == null ? void 0 : _e.avatar) || ((_f = supabaseUser.user_metadata) == null ? void 0 : _f.avatar_url), emailVerified: !!supabaseUser.email_confirmed_at, createdAt: supabaseUser.created_at, updatedAt: supabaseUser.updated_at }; } } class CrudService { /** * Initialize the CRUD service * @param supabaseClient - Configured Supabase client */ constructor(supabaseClient) { this.supabase = supabaseClient; } /** * Create a new record in the specified table * @param table - Table name * @param data - Data to insert * @returns Promise resolving to Result with created record */ async create(table, data) { try { const { data: result, error } = await this.supabase.from(table).insert(data).select().single(); if (error) { return createFailure(new Error(error.message)); } return createSuccess(result); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Create operation failed")); } } /** * Read a record by ID from the specified table * @param table - Table name * @param id - Record ID * @returns Promise resolving to Result with record or null */ async read(table, id) { try { const { data, error } = await this.supabase.from(table).select("*").eq("id", id).single(); if (error) { if (error.code === "PGRST116") { return createSuccess(null); } return createFailure(new Error(error.message)); } return createSuccess(data); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Read operation failed")); } } /** * Update a record by ID in the specified table * @param table - Table name * @param id - Record ID * @param data - Data to update * @returns Promise resolving to Result with updated record */ async update(table, id, data) { try { const { data: result, error } = await this.supabase.from(table).update(data).eq("id", id).select().single(); if (error) { return createFailure(new Error(error.message)); } return createSuccess(result); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Update operation failed")); } } /** * Delete a record by ID from the specified table * @param table - Table name * @param id - Record ID * @returns Promise resolving to Result indicating success or failure */ async delete(table, id) { try { const { error } = await this.supabase.from(table).delete().eq("id", id); if (error) { return createFailure(new Error(error.message)); } return createSuccess(void 0); } catch (error) { return createFailure(error instanceof Error ? error : new Error("Delete operation failed")); } } /** * List records from the specified table with optional basic filtering * @param table - Table name * @param filters - Optional filters to apply * @returns Promise resolving to Result with array of records */ async list(table, filters) { try { let query = this.supabase.from(table).select("*"); if (filters) { Object.entries(filters).forEach(([key, value]) => { query = query.eq(key, value); }); } const { data, error } = await query; if (error) { return createFailure(new Error(error.message)); } return createSuccess(data || []); } catch (error) { return createFailure(error instanceof Error ? error : new Error("List operation failed")); } } } class ServiceManager { /** * Initialize the ServiceManager with Supabase configuration * @param config - ServiceManager configuration containing Supabase settings */ constructor(config) { this.supabase = createClient(config.supabase.url, config.supabase.anonKey); this.authService = new AuthService(config.supabase); this.userService = new UserService(this.supabase); this.crudService = new CrudService(this.supabase); } // Authentication Methods /** * Sign up a new user with email and password * @param email - User's email address * @param password - User's password * @param profile - Optional profile data to set during signup * @returns Promise resolving to Result with user and verification status * * @example * ```typescript * const result = await serviceManager.signUp("user@example.com", "password123", { * firstName: "John", * lastName: "Doe" * }); * * if (result.success) { * if (result.data.needsVerification) { * console.log("Please check your email for verification"); * } * console.log("User created:", result.data.user); * } else { * console.error("Sign up failed:", result.error.message); * } * ``` */ async signUp(email, password, profile) { return this.authService.signUp(email, password, profile); } /** * Sign in an existing user with email and password * @param email - User's email address * @param password - User's password * @returns Promise resolving to Result with user and session * * @example * ```typescript * const result = await serviceManager.signIn("user@example.com", "password123"); * * if (result.success) { * console.log("Signed in:", result.data.user); * console.log("Session expires at:", new Date(result.data.session.expiresAt)); * } else { * console.error("Sign in failed:", result.error.message); * } * ``` */ async signIn(email, password) { return this.authService.signIn(email, password); } /** * Sign out the current user * @returns Promise resolving to Result indicating success or failure * * @example * ```typescript * const result = await serviceManager.signOut(); * * if (result.success) { * console.log("Successfully signed out"); * } else { * console.error("Sign out failed:", result.error.message); * } * ``` */ async signOut() { return this.authService.signOut(); } /** * Resend email verification for the current user * @returns Promise resolving to Result indicating success or failure * * @example * ```typescript * const result = await serviceManager.resendVerificationEmail(); * * if (result.success) { * console.log("Verification email sent"); * } else { * console.error("Failed to send verification email:", result.error.message); * } * ``` */ async resendVerificationEmail() { return this.authService.resendVerificationEmail(); } // Profile Management Methods /** * Update the current user's profile information * @param updates - Profile fields to update (firstName, lastName, avatar) * @returns Promise resolving to Result with updated user * * @example * ```typescript * const result = await serviceManager.updateProfile({ * firstName: "John", * lastName: "Doe", * avatar: "https://example.com/avatar.jpg" * }); * * if (result.success) { * console.log("Profile updated:", result.data); * } else { * console.error("Profile update failed:", result.error.message); * } * ``` */ async updateProfile(updates) { return this.userService.updateProfile(updates); } /** * Get the current authenticated user * @returns Promise resolving to Result with current user or null * * @example * ```typescript * const result = await serviceManager.getCurrentUser(); * * if (result.success) { * if (result.data) { * console.log("Current user:", result.data); * } else { * console.log("No user is currently signed in"); * } * } else { * console.error("Failed to get current user:", result.error.message); * } * ``` */ async getCurrentUser() { return this.userService.getCurrentUser(); } /** * Get the current authentication session * @returns Promise resolving to Result with current session or null * * @example * ```typescript * const result = await serviceManager.getCurrentSession(); * * if (result.success) { * if (result.data) { * console.log("Current session:", result.data); * console.log("Expires at:", new Date(result.data.expiresAt)); * } else { * console.log("No active session"); * } * } else { * console.error("Failed to get current session:", result.error.message); * } * ``` */ async getCurrentSession() { return this.authService.getCurrentSession(); } // Basic CRUD Operations /** * Create a new record in the specified table * @param table - Table name * @param data - Data to insert * @returns Promise resolving to Result with created record * * @example * ```typescript * const result = await serviceManager.create("social_links", { * user_id: "user-id", * platform: "twitter", * url: "https://twitter.com/username" * }); * * if (result.success) { * console.log("Record created:", result.data); * } else { * console.error("Create failed:", result.error.message); * } * ``` */ async create(table, data) { return this.crudService.create(table, data); } /** * Read a record by ID from the specified table * @param table - Table name * @param id - Record ID * @returns Promise resolving to Result with record or null * * @example * ```typescript * const result = await serviceManager.read("social_links", "link-id"); * * if (result.success) { * if (result.data) { * console.log("Record found:", result.data); * } else { * console.log("Record not found"); * } * } else { * console.error("Read failed:", result.error.message); * } * ``` */ async read(table, id) { return this.crudService.read(table, id); } /** * Update a record by ID in the specified table * @param table - Table name * @param id - Record ID * @param data - Data to update * @returns Promise resolving to Result with updated record * * @example * ```typescript * const result = await serviceManager.update("social_links", "link-id", { * url: "https://twitter.com/newusername" * }); * * if (result.success) { * console.log("Record updated:", result.data); * } else { * console.error("Update failed:", result.error.message); * } * ``` */ async update(table, id, data) { return this.crudService.update(table, id, data); } /** * Delete a record by ID from the specified table * @param table - Table name * @param id - Record ID * @returns Promise resolving to Result indicating success or failure * * @example * ```typescript * const result = await serviceManager.delete("social_links", "link-id"); * * if (result.success) { * console.log("Record deleted successfully"); * } else { * console.error("Delete failed:", result.error.message); * } * ``` */ async delete(table, id) { return this.crudService.delete(table, id); } /** * List records from the specified table with optional basic filtering * @param table - Table name * @param filters - Optional filters to apply (key-value pairs for exact matches) * @returns Promise resolving to Result with array of records * * @example * ```typescript * // List all records * const allResult = await serviceManager.list("social_links"); * * // List with filters * const filteredResult = await serviceManager.list("social_links", { * user_id: "user-id", * platform: "twitter" * }); * * if (filteredResult.success) { * console.log("Records found:", filteredResult.data); * } else { * console.error("List failed:", filteredResult.error.message); * } * ``` */ async list(table, filters) { return this.crudService.list(table, filters); } } const version = "1.0.0"; export { AuthService, CrudService, ServiceManager, UserService, createFailure, createSuccess, isFailure, isSuccess, version }; //# sourceMappingURL=index.js.map