@ezcoder.dev/sdk 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth/neonAuthClient.ts","../src/auth/AuthProvider.tsx"],"sourcesContent":["import { createAuthClient } from 'better-auth/react';\nimport { env, isNeonAuthConfigured } from '../core/config';\n\n/**\n * Per-project Neon Auth (Better Auth) client.\n *\n * A project uses Neon Auth when `env.NEON_AUTH_URL` (the per-project Better Auth\n * `base_url`, e.g. `https://ep-xxx.neonauth.<region>.aws.neon.tech/neondb/auth`)\n * is injected as `VITE_NEON_AUTH_URL`. When present, the SDK authenticates the\n * generated app against its OWN isolated Neon Auth server rather than the shared\n * tenant Supabase.\n *\n * The client is created lazily so that:\n * - non-Neon projects never instantiate it (zero behavioural change), and\n * - SSR/build passes that import the SDK without a base URL don't throw.\n *\n * This is the official Better Auth React client (`createAuthClient` from\n * `better-auth/react`). It owns token storage and cross-origin session handling;\n * do NOT hand-roll fetch against the Better Auth endpoints.\n */\nexport type NeonAuthClient = ReturnType<typeof createAuthClient>;\n\nlet cachedClient: NeonAuthClient | null = null;\n\n/**\n * Returns the lazily-created Better Auth client, or `null` when Neon Auth is not\n * configured for this project. Callers on the Neon path should treat a `null`\n * return as \"not configured\" and surface a clear error.\n */\nexport function getNeonAuthClient(): NeonAuthClient | null {\n if (!isNeonAuthConfigured || !env.NEON_AUTH_URL) {\n return null;\n }\n if (!cachedClient) {\n cachedClient = createAuthClient({\n baseURL: env.NEON_AUTH_URL,\n });\n }\n return cachedClient;\n}\n\n/** True when this project ships its own per-project Neon Auth server. */\nexport { isNeonAuthConfigured };\n","import { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';\nimport type { User, Session } from '@supabase/supabase-js';\nimport { supabase, isSupabaseConfigured } from '../core/supabase';\nimport { env, isNeonAuthConfigured } from '../core/config';\nimport { ezcoder, ezcoderAuthIntegration } from '../core/platform';\nimport { getNeonAuthClient } from './neonAuthClient';\nimport type { UserProfile } from '../core/types';\n\ninterface AuthResult<T = unknown> {\n data: T | null;\n error: Error | null;\n}\n\ninterface SignUpOptions {\n metadata?: Record<string, unknown>;\n}\n\ninterface SignInWithProviderOptions {\n redirectTo?: string;\n [key: string]: unknown;\n}\n\nexport interface AuthContextType {\n user: User | null;\n profile: UserProfile | null;\n session: Session | null;\n loading: boolean;\n isConfigured: boolean;\n signUp: (email: string, password: string, options?: SignUpOptions) => Promise<AuthResult>;\n signIn: (email: string, password: string) => Promise<AuthResult>;\n signInWithProvider: (provider: string, options?: SignInWithProviderOptions) => Promise<AuthResult>;\n signOut: () => Promise<{ error: Error | null }>;\n resetPassword: (email: string) => Promise<AuthResult>;\n updatePassword: (newPassword: string) => Promise<AuthResult>;\n updateProfile: (updates: Partial<UserProfile>) => Promise<AuthResult>;\n refetchProfile: (userId: string) => Promise<UserProfile | null>;\n}\n\nconst NOT_CONFIGURED_MSG = 'Authentication is not configured. Ensure EzCoder platform credentials are injected, or connect your own Supabase database in Settings → Databases.';\nfunction notConfiguredError() {\n return new Error(NOT_CONFIGURED_MSG);\n}\n\nexport const AuthContext = createContext<AuthContextType>({\n user: null,\n profile: null,\n session: null,\n loading: true,\n isConfigured: false,\n signUp: async () => ({ data: null, error: null }),\n signIn: async () => ({ data: null, error: null }),\n signInWithProvider: async () => ({ data: null, error: null }),\n signOut: async () => ({ error: null }),\n resetPassword: async () => ({ data: null, error: null }),\n updatePassword: async () => ({ data: null, error: null }),\n updateProfile: async () => ({ data: null, error: null }),\n refetchProfile: async () => null,\n});\n\n// ─── Path selector ──────────────────────────────────────────────────────────\n//\n// A project authenticates against its OWN per-project Neon Auth (Better Auth)\n// server when `VITE_NEON_AUTH_URL` is injected. Otherwise it falls back to the\n// existing shared-tenant Supabase path, which is behaviour-preserved verbatim\n// below in `SupabaseAuthProvider`.\nexport function AuthProvider({ children }: { children: React.ReactNode }) {\n if (isNeonAuthConfigured) {\n return <NeonAuthProvider>{children}</NeonAuthProvider>;\n }\n return <SupabaseAuthProvider>{children}</SupabaseAuthProvider>;\n}\n\n// ─── Neon Auth (Better Auth) path ────────────────────────────────────────────\n\n// The Better Auth user shape (subset we rely on). The full object also carries\n// emailVerified, image, role, banned, createdAt, updatedAt, etc.\ninterface BetterAuthUser {\n id?: string;\n email?: string;\n name?: string;\n image?: string | null;\n emailVerified?: boolean;\n role?: string;\n createdAt?: string;\n updatedAt?: string;\n [key: string]: unknown;\n}\n\n/**\n * Normalize a Better Auth user into the SDK's `user` shape. The context type\n * declares `User` (Supabase) for backwards compatibility, so we project the\n * Better Auth fields onto a Supabase-User-compatible object: `id`, `email`, and\n * a `user_metadata` carrying the display name + avatar. Consumers that read\n * `user.id`, `user.email`, or `user.user_metadata.full_name` keep working.\n */\nfunction mapNeonUser(u: BetterAuthUser | null | undefined): User | null {\n if (!u || !u.id) return null;\n const nowIso = new Date().toISOString();\n return {\n id: u.id,\n email: u.email,\n app_metadata: { provider: 'neon-auth', ...(u.role ? { role: u.role } : {}) },\n user_metadata: {\n full_name: u.name,\n name: u.name,\n avatar_url: u.image ?? undefined,\n email_verified: u.emailVerified,\n },\n aud: 'authenticated',\n created_at: u.createdAt || nowIso,\n updated_at: u.updatedAt || nowIso,\n role: u.role,\n } as unknown as User;\n}\n\n/**\n * Project the Better Auth session payload onto the SDK's `session` shape. Better\n * Auth manages the real token + cookie internally; this object exists only so\n * consumers reading `session.user` / truthiness keep working. The bearer token\n * (when exposed by useSession) is surfaced as `access_token` best-effort.\n */\nfunction mapNeonSession(rawSession: unknown, user: User | null): Session | null {\n if (!user) return null;\n const s = (rawSession || {}) as Record<string, unknown>;\n const token =\n (s.token as string | undefined) ||\n ((s.session as Record<string, unknown> | undefined)?.token as string | undefined) ||\n '';\n return {\n access_token: token,\n refresh_token: '',\n token_type: 'bearer',\n expires_in: 0,\n expires_at: undefined,\n user,\n } as unknown as Session;\n}\n\nconst NEON_NOT_SUPPORTED = (feature: string) =>\n new Error(`${feature} is not supported on the Neon Auth path yet.`);\n\nfunction NeonAuthProvider({ children }: { children: React.ReactNode }) {\n const authClient = getNeonAuthClient();\n const [user, setUser] = useState<User | null>(null);\n const [profile, setProfile] = useState<UserProfile | null>(null);\n const [session, setSession] = useState<Session | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n const previousUserIdRef = useRef<string | null>(null);\n\n // Drive user/session/loading off Better Auth's reactive session hook. The hook\n // is stable across renders and re-renders the provider on auth state changes.\n const sessionState = authClient?.useSession?.() as\n | { data?: { user?: BetterAuthUser; session?: unknown } | null; isPending?: boolean }\n | undefined;\n\n const rawUser = sessionState?.data?.user ?? null;\n const isPending = sessionState?.isPending ?? false;\n\n useEffect(() => {\n const mappedUser = mapNeonUser(rawUser);\n setUser(mappedUser);\n setSession(mapNeonSession(sessionState?.data?.session ?? sessionState?.data ?? null, mappedUser));\n setLoading(Boolean(isPending));\n\n // Fire login/logout analytics on transitions, mirroring the Supabase path.\n const currentId = mappedUser?.id ?? null;\n const prevId = previousUserIdRef.current;\n if (currentId && currentId !== prevId) {\n ezcoderAuthIntegration.onLogin(mappedUser as unknown as { id: string; email?: string });\n previousUserIdRef.current = currentId;\n } else if (!currentId && prevId) {\n ezcoderAuthIntegration.onLogout(prevId);\n previousUserIdRef.current = null;\n setProfile(null);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [rawUser, isPending]);\n\n const signUp = useCallback(async (email: string, password: string, options: SignUpOptions = {}): Promise<AuthResult> => {\n if (!authClient) return { data: null, error: notConfiguredError() };\n const { metadata = {} } = options;\n const name = (metadata.display_name as string | undefined) || (metadata.name as string | undefined) || email;\n try {\n const result = await authClient.signUp.email({ email, password, name });\n const error = (result as { error?: unknown })?.error;\n if (error) {\n return { data: null, error: toError(error, 'Sign up failed') };\n }\n const data = (result as { data?: unknown })?.data ?? result;\n const newUser = (data as { user?: BetterAuthUser })?.user;\n if (newUser?.id) {\n ezcoderAuthIntegration.onSignup({ id: newUser.id, email: newUser.email });\n }\n return { data, error: null };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Sign up failed') };\n }\n }, [authClient]);\n\n const signIn = useCallback(async (email: string, password: string): Promise<AuthResult> => {\n if (!authClient) return { data: null, error: notConfiguredError() };\n try {\n const result = await authClient.signIn.email({ email, password });\n const error = (result as { error?: unknown })?.error;\n if (error) {\n return { data: null, error: toError(error, 'Sign in failed') };\n }\n const data = (result as { data?: unknown })?.data ?? result;\n return { data, error: null };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Sign in failed') };\n }\n }, [authClient]);\n\n const signInWithProvider = useCallback(async (provider: string, options: SignInWithProviderOptions = {}): Promise<AuthResult> => {\n if (!authClient) return { data: null, error: notConfiguredError() };\n try {\n const origin = typeof window !== 'undefined' ? window.location.origin : '';\n const callbackURL = options.redirectTo || origin;\n const framed = typeof window !== 'undefined' && window.self !== window.top;\n const social = provider as 'google' | 'github';\n\n if (framed && typeof window !== 'undefined') {\n // Provider login pages refuse to render inside an iframe (X-Frame-Options /\n // frame-ancestors). Ask Better Auth for the OAuth URL WITHOUT redirecting\n // (disableRedirect) and open it in a popup. The popup completes on the\n // app origin (callbackURL) and the session cookie is set; Better Auth's\n // useSession picks it up here on the next focus/poll. A small focus poke\n // nudges the hook to refetch promptly.\n const result = await authClient.signIn.social({\n provider: social,\n callbackURL,\n disableRedirect: true,\n });\n const error = (result as { error?: unknown })?.error;\n if (error) return { data: null, error: toError(error, 'OAuth sign in failed') };\n const data = (result as { data?: { url?: string } })?.data ?? null;\n const url = data?.url;\n if (url) window.open(url, 'ezc-auth', 'popup,width=500,height=680');\n return { data, error: null };\n }\n\n // Standalone (deployed app / own tab): let Better Auth do the full-page\n // redirect to the provider.\n const result = await authClient.signIn.social({ provider: social, callbackURL });\n const error = (result as { error?: unknown })?.error;\n if (error) return { data: null, error: toError(error, 'OAuth sign in failed') };\n const data = (result as { data?: unknown })?.data ?? null;\n return { data, error: null };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('OAuth sign in failed') };\n }\n }, [authClient]);\n\n const signOut = useCallback(async (): Promise<{ error: Error | null }> => {\n if (!authClient) return { error: notConfiguredError() };\n const userId = user?.id;\n try {\n const result = await authClient.signOut();\n const error = (result as { error?: unknown })?.error;\n if (error) return { error: toError(error, 'Sign out failed') };\n if (userId) ezcoder.users.trackLogout(userId);\n return { error: null };\n } catch (err: unknown) {\n return { error: err instanceof Error ? err : new Error('Sign out failed') };\n }\n }, [authClient, user]);\n\n const resetPassword = useCallback(async (email: string): Promise<AuthResult> => {\n if (!authClient) return { data: null, error: notConfiguredError() };\n // Better Auth renamed `forgetPassword` → `requestPasswordReset`. Support both\n // so the SDK works regardless of the server's Better Auth version.\n const client = authClient as unknown as {\n requestPasswordReset?: (args: { email: string; redirectTo?: string }) => Promise<unknown>;\n forgetPassword?: (args: { email: string; redirectTo?: string }) => Promise<unknown>;\n };\n const redirectTo = `${typeof window !== 'undefined' ? window.location.origin : ''}/auth/reset-password`;\n const fn = client.requestPasswordReset || client.forgetPassword;\n if (!fn) {\n return { data: null, error: NEON_NOT_SUPPORTED('Password reset') };\n }\n try {\n const result = await fn({ email, redirectTo });\n const error = (result as { error?: unknown })?.error;\n if (error) return { data: null, error: toError(error, 'Password reset failed') };\n const data = (result as { data?: unknown })?.data ?? result;\n return { data, error: null };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Password reset failed') };\n }\n }, [authClient]);\n\n const updatePassword = useCallback(async (newPassword: string): Promise<AuthResult> => {\n if (!authClient) return { data: null, error: notConfiguredError() };\n // Two distinct Better Auth operations land here:\n // - `resetPassword({ newPassword, token })` after a reset-email link (token in URL)\n // - `changePassword({ newPassword, currentPassword })` for a logged-in user\n // We only have the new password, so we use the token-based reset when a\n // reset token is present in the URL; otherwise there is no safe call to make\n // (changePassword requires the current password we don't have).\n const token = typeof window !== 'undefined'\n ? new URLSearchParams(window.location.search).get('token') ||\n new URLSearchParams(window.location.search).get('reset_token')\n : null;\n const client = authClient as unknown as {\n resetPassword?: (args: { newPassword: string; token: string }) => Promise<unknown>;\n };\n if (token && client.resetPassword) {\n try {\n const result = await client.resetPassword({ newPassword, token });\n const error = (result as { error?: unknown })?.error;\n if (error) return { data: null, error: toError(error, 'Password update failed') };\n const data = (result as { data?: unknown })?.data ?? result;\n return { data, error: null };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Password update failed') };\n }\n }\n // TODO(neon-auth): wire changePassword once the UI can collect the current\n // password (Better Auth changePassword requires { newPassword, currentPassword }).\n return { data: null, error: NEON_NOT_SUPPORTED('Updating the password without a reset token') };\n }, [authClient]);\n\n // TODO(neon-auth): the user profile lives in the project's Neon\n // `public.user_profiles` table. Reading it requires the platform DB path\n // (useDatabase / DatabaseClient), which is provider-scoped and not available\n // here without threading a client in. We intentionally do NOT call Supabase on\n // the Neon path. Profile reads/writes no-op gracefully for now.\n const fetchProfile = useCallback(async (_userId: string): Promise<UserProfile | null> => {\n return null;\n }, []);\n\n const updateProfile = useCallback(async (_updates: Partial<UserProfile>): Promise<AuthResult> => {\n if (!user) return { data: null, error: new Error('Not authenticated') };\n return { data: null, error: NEON_NOT_SUPPORTED('Updating the profile') };\n }, [user]);\n\n const value: AuthContextType = {\n user,\n profile,\n session,\n loading,\n isConfigured: Boolean(authClient),\n signUp,\n signIn,\n signInWithProvider,\n signOut,\n resetPassword,\n updatePassword,\n updateProfile,\n refetchProfile: fetchProfile,\n };\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\nfunction toError(error: unknown, fallback: string): Error {\n if (error instanceof Error) return error;\n if (error && typeof error === 'object') {\n const msg = (error as { message?: string; statusText?: string }).message\n || (error as { statusText?: string }).statusText;\n if (msg) return new Error(msg);\n }\n if (typeof error === 'string') return new Error(error);\n return new Error(fallback);\n}\n\n// ─── Supabase path (UNCHANGED behaviour) ─────────────────────────────────────\n\nfunction SupabaseAuthProvider({ children }: { children: React.ReactNode }) {\n const [user, setUser] = useState<User | null>(null);\n const [profile, setProfile] = useState<UserProfile | null>(null);\n const [session, setSession] = useState<Session | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n const previousUserIdRef = useRef<string | null>(null);\n\n const fetchProfile = useCallback(async (userId: string): Promise<UserProfile | null> => {\n if (!userId) return null;\n\n try {\n let query = supabase\n .from('user_profiles')\n .select('*')\n .eq('id', userId);\n\n if (env.EZC_PROJECT_ID) {\n query = query.eq('project_id', env.EZC_PROJECT_ID);\n }\n\n const result = await query.single();\n\n const { data, error } = result || { data: null, error: null };\n\n if (error) {\n return null;\n }\n\n setProfile(data as UserProfile);\n return data as UserProfile;\n } catch {\n return null;\n }\n }, []);\n\n // SEC-1: bind the end-user to THIS project server-side (authoritative; app_metadata\n // is service-role-only). OAuth users have no signup metadata so the migration-226\n // trigger can't bind them — this is what stops migration 227 from locking them out.\n // No-ops if already bound. Non-blocking.\n const bindProjectIfNeeded = useCallback(async (currentUser: User | null): Promise<void> => {\n if (!currentUser || !env.EZC_PROJECT_ID || !env.EZCODER_API_URL) return;\n const appMeta = currentUser.app_metadata as Record<string, unknown> | undefined;\n if (appMeta?.bound_project_id) return;\n try {\n const { data } = await supabase.auth.getSession();\n const token = data?.session?.access_token;\n if (!token) return;\n const res = await fetch(`${env.EZCODER_API_URL.replace(/\\/$/, '')}/api/managed-auth/bind`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(env.EZC_PROJECT_TOKEN_PUBLIC ? { Authorization: `Bearer ${env.EZC_PROJECT_TOKEN_PUBLIC}` } : {}),\n 'X-EZC-User-Token': token,\n },\n body: JSON.stringify({ projectId: env.EZC_PROJECT_ID }),\n });\n const j = await res.json().catch(() => ({}));\n if (j?.changed) {\n // The new bound_project_id claim only appears after a token refresh.\n await supabase.auth.refreshSession();\n }\n } catch {\n // non-blocking — login still works; binding reconciles on next load / via backfill\n }\n }, []);\n\n useEffect(() => {\n supabase.auth.getSession().then(async ({ data: { session: initialSession } }) => {\n setSession(initialSession);\n setUser(initialSession?.user ?? null);\n\n if (initialSession?.user) {\n await fetchProfile(initialSession.user.id);\n bindProjectIfNeeded(initialSession.user);\n }\n\n setLoading(false);\n });\n\n const {\n data: { subscription },\n } = supabase.auth.onAuthStateChange(async (event, currentSession) => {\n setSession(currentSession);\n setUser(currentSession?.user ?? null);\n\n if (event === 'SIGNED_IN' && currentSession?.user) {\n await fetchProfile(currentSession.user.id);\n bindProjectIfNeeded(currentSession.user);\n ezcoderAuthIntegration.onLogin(currentSession.user);\n previousUserIdRef.current = currentSession.user.id;\n } else if (event === 'SIGNED_OUT') {\n setProfile(null);\n if (previousUserIdRef.current) {\n ezcoderAuthIntegration.onLogout(previousUserIdRef.current);\n previousUserIdRef.current = null;\n }\n } else if (event === 'USER_UPDATED' && currentSession?.user) {\n ezcoder.analytics.identify(currentSession.user.id, {\n email: currentSession.user.email,\n name: currentSession.user.user_metadata?.full_name,\n });\n }\n });\n\n // In-editor popup sign-in: the /auth/callback popup postMessages on completion;\n // the storage event is a same-origin fallback for the popup's localStorage write.\n const refreshFromExternal = () => {\n supabase.auth.getSession().then(({ data }) => {\n const s = data?.session ?? null;\n setSession(s);\n setUser(s?.user ?? null);\n if (s?.user) { fetchProfile(s.user.id); bindProjectIfNeeded(s.user); }\n });\n };\n const onMsg = (e: MessageEvent) => { if (e?.data?.type === 'ezc-auth-complete') refreshFromExternal(); };\n const onStorage = (e: StorageEvent) => { if (e.key && /auth-token/.test(e.key)) refreshFromExternal(); };\n if (typeof window !== 'undefined') {\n window.addEventListener('message', onMsg);\n window.addEventListener('storage', onStorage);\n }\n\n return () => {\n subscription.unsubscribe();\n if (typeof window !== 'undefined') {\n window.removeEventListener('message', onMsg);\n window.removeEventListener('storage', onStorage);\n }\n };\n }, [fetchProfile, bindProjectIfNeeded]);\n\n const signUp = useCallback(async (email: string, password: string, options: SignUpOptions = {}): Promise<AuthResult> => {\n if (!isSupabaseConfigured) {\n return { data: null, error: notConfiguredError() };\n }\n const { metadata = {} } = options;\n\n try {\n const signUpMetadata = { ...metadata };\n if (env.EZC_PROJECT_ID) {\n signUpMetadata.project_id = env.EZC_PROJECT_ID;\n signUpMetadata.bound_project_id = env.EZC_PROJECT_ID;\n }\n\n const result = await supabase.auth.signUp({\n email,\n password,\n options: { data: signUpMetadata },\n });\n\n const { data, error } = result || { data: null, error: new Error('Sign up failed') };\n\n if (error) {\n return { data: null, error };\n }\n\n if (data?.user) {\n const profileData: Record<string, unknown> = {\n id: data.user.id,\n email,\n };\n if (metadata.display_name) {\n profileData.display_name = metadata.display_name;\n }\n if (env.EZC_PROJECT_ID) {\n profileData.project_id = env.EZC_PROJECT_ID;\n }\n if (metadata.display_name || env.EZC_PROJECT_ID) {\n await supabase.from('user_profiles').upsert(profileData);\n }\n try {\n await supabase.rpc('assign_default_role', { user_uuid: data.user.id });\n } catch {\n // Non-blocking — role tables may not exist in all projects\n }\n ezcoderAuthIntegration.onSignup(data.user);\n }\n\n return { data, error: null };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Sign up failed') };\n }\n }, []);\n\n const signIn = useCallback(async (email: string, password: string): Promise<AuthResult> => {\n if (!isSupabaseConfigured) {\n return { data: null, error: notConfiguredError() };\n }\n try {\n const result = await supabase.auth.signInWithPassword({ email, password });\n const { data, error } = result || { data: null, error: new Error('Sign in failed') };\n return { data, error };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Sign in failed') };\n }\n }, []);\n\n const signInWithProvider = useCallback(async (provider: string, options: SignInWithProviderOptions = {}): Promise<AuthResult> => {\n if (!isSupabaseConfigured) {\n return { data: null, error: notConfiguredError() };\n }\n try {\n const provider2 = provider as 'google' | 'github' | 'facebook' | 'apple' | 'twitter';\n const origin = typeof window !== 'undefined' ? window.location.origin : '';\n const framed = typeof window !== 'undefined' && window.self !== window.top;\n\n if (framed && typeof window !== 'undefined') {\n // Provider login pages refuse to render inside an iframe (X-Frame-Options /\n // frame-ancestors), so we cannot do a full-page redirect in the editor preview.\n // Open the flow in a popup; the /auth/callback page completes PKCE and\n // postMessages back (storage event is the fallback). Land on the callback route.\n const redirectTo = options.redirectTo || (origin ? `${origin}/auth/callback` : '');\n const result = await supabase.auth.signInWithOAuth({\n provider: provider2,\n options: { redirectTo, skipBrowserRedirect: true, ...options },\n });\n if (result?.error) return { data: null, error: result.error };\n const url = (result?.data as { url?: string } | null)?.url;\n if (url) window.open(url, 'ezc-auth', 'popup,width=500,height=680');\n return { data: result?.data ?? null, error: null };\n }\n\n // Standalone (deployed app / own tab): full-page redirect. Default to the app\n // origin (root) so apps without a scaffolded callback route still complete via\n // detectSessionInUrl; new apps pass /auth/callback explicitly.\n const result = await supabase.auth.signInWithOAuth({\n provider: provider2,\n options: {\n redirectTo: options.redirectTo || origin,\n ...options,\n },\n });\n const { data, error } = result || { data: null, error: new Error('OAuth sign in failed') };\n return { data, error };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('OAuth sign in failed') };\n }\n }, []);\n\n const signOut = useCallback(async (): Promise<{ error: Error | null }> => {\n const userId = user?.id;\n try {\n const result = await supabase.auth.signOut();\n const { error } = result || { error: null };\n if (!error && userId) {\n ezcoder.users.trackLogout(userId);\n }\n return { error };\n } catch (err: unknown) {\n return { error: err instanceof Error ? err : new Error('Sign out failed') };\n }\n }, [user]);\n\n const resetPassword = useCallback(async (email: string): Promise<AuthResult> => {\n try {\n const result = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: `${typeof window !== 'undefined' ? window.location.origin : ''}/auth/reset-password`,\n });\n const { data, error } = result || { data: null, error: new Error('Password reset failed') };\n return { data, error };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Password reset failed') };\n }\n }, []);\n\n // Set a new password. Used on the /auth/reset-password landing page, where the\n // recovery link has already established a (recovery) session via detectSessionInUrl.\n const updatePassword = useCallback(async (newPassword: string): Promise<AuthResult> => {\n if (!isSupabaseConfigured) {\n return { data: null, error: notConfiguredError() };\n }\n try {\n const result = await supabase.auth.updateUser({ password: newPassword });\n const { data, error } = result || { data: null, error: new Error('Password update failed') };\n return { data, error };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Password update failed') };\n }\n }, []);\n\n const updateProfile = useCallback(\n async (updates: Partial<UserProfile>): Promise<AuthResult> => {\n if (!user) {\n return { data: null, error: new Error('Not authenticated') };\n }\n try {\n let query = supabase\n .from('user_profiles')\n .update(updates)\n .eq('id', user.id);\n\n if (env.EZC_PROJECT_ID) {\n query = query.eq('project_id', env.EZC_PROJECT_ID);\n }\n\n const result = await query.select().single();\n const { data, error } = result || { data: null, error: null };\n return { data, error };\n } catch (err: unknown) {\n return { data: null, error: err instanceof Error ? err : new Error('Update failed') };\n }\n },\n [user]\n );\n\n const value: AuthContextType = {\n user,\n profile,\n session,\n loading,\n isConfigured: isSupabaseConfigured,\n signUp,\n signIn,\n signInWithProvider,\n signOut,\n resetPassword,\n updatePassword,\n updateProfile,\n refetchProfile: fetchProfile,\n };\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\nexport function useAuth(): AuthContextType {\n const context = useContext(AuthContext);\n if (context === undefined) {\n throw new Error('useAuth must be used within an AuthProvider');\n }\n return context;\n}\n\nexport type { AuthResult, SignUpOptions, SignInWithProviderOptions };\n"],"mappings":"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","names":["result","error","data"]}
@@ -19,6 +19,13 @@ var env = {
19
19
  SUPABASE_ANON_KEY: getEnv("VITE_SUPABASE_ANON_KEY", "NEXT_PUBLIC_SUPABASE_ANON_KEY"),
20
20
  EZCODER_AUTH_URL: getEnv("VITE_EZCODER_AUTH_URL", "NEXT_PUBLIC_EZCODER_AUTH_URL"),
21
21
  EZCODER_AUTH_ANON_KEY: getEnv("VITE_EZCODER_AUTH_ANON_KEY", "NEXT_PUBLIC_EZCODER_AUTH_ANON_KEY"),
22
+ // Per-project Neon Auth (Better Auth) server base URL. When present, the app
23
+ // authenticates against its OWN isolated Neon Auth server (e.g.
24
+ // https://ep-xxx.neonauth.<region>.aws.neon.tech/neondb/auth) instead of the
25
+ // shared tenant Supabase. Injected at provision time as VITE_NEON_AUTH_URL;
26
+ // NEON_AUTH_BASE_URL is the server/Next fallback name. Presence of this value
27
+ // is the sole trigger for the Better Auth code path in AuthProvider.
28
+ NEON_AUTH_URL: getEnv("VITE_NEON_AUTH_URL", "NEON_AUTH_BASE_URL"),
22
29
  STRIPE_PUBLISHABLE_KEY: getEnv("VITE_STRIPE_PUBLISHABLE_KEY", "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY"),
23
30
  EZC_PROJECT_ID: getEnv("VITE_EZC_PROJECT_ID", "NEXT_PUBLIC_EZC_PROJECT_ID"),
24
31
  EZCODER_API_URL: getEnv("VITE_EZCODER_API_URL", "NEXT_PUBLIC_EZCODER_API_URL"),
@@ -49,9 +56,10 @@ var env = {
49
56
  STORAGE_BUCKET_PUBLIC: getEnv("VITE_STORAGE_BUCKET_PUBLIC", "NEXT_PUBLIC_STORAGE_BUCKET_PUBLIC"),
50
57
  STORAGE_BUCKET_PRIVATE: getEnv("VITE_STORAGE_BUCKET_PRIVATE", "NEXT_PUBLIC_STORAGE_BUCKET_PRIVATE")
51
58
  };
59
+ var isNeonAuthConfigured = Boolean(env.NEON_AUTH_URL);
52
60
  var features = {
53
61
  auth: Boolean(
54
- env.SUPABASE_URL && env.SUPABASE_ANON_KEY || env.EZCODER_AUTH_URL && env.EZCODER_AUTH_ANON_KEY
62
+ isNeonAuthConfigured || env.SUPABASE_URL && env.SUPABASE_ANON_KEY || env.EZCODER_AUTH_URL && env.EZCODER_AUTH_ANON_KEY
55
63
  ),
56
64
  payments: Boolean(env.STRIPE_PUBLISHABLE_KEY),
57
65
  analytics: Boolean(env.EZCODER_API_URL && env.EZC_PROJECT_ID),
@@ -66,7 +74,8 @@ function isFeatureConfigured(feature) {
66
74
 
67
75
  export {
68
76
  env,
77
+ isNeonAuthConfigured,
69
78
  features,
70
79
  isFeatureConfigured
71
80
  };
72
- //# sourceMappingURL=chunk-LIUE7M7K.js.map
81
+ //# sourceMappingURL=chunk-X4JP7DCK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/config.ts"],"sourcesContent":["const getEnv = (viteKey: string, nextKey?: string): string => {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n return (import.meta.env[viteKey] as string) || '';\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n try {\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[nextKey || viteKey] || '';\r\n }\r\n } catch {\r\n // process not available\r\n }\r\n return '';\r\n};\r\n\r\nexport const env = {\r\n SUPABASE_URL: getEnv('VITE_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_URL'),\r\n SUPABASE_ANON_KEY: getEnv('VITE_SUPABASE_ANON_KEY', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'),\r\n EZCODER_AUTH_URL: getEnv('VITE_EZCODER_AUTH_URL', 'NEXT_PUBLIC_EZCODER_AUTH_URL'),\r\n EZCODER_AUTH_ANON_KEY: getEnv('VITE_EZCODER_AUTH_ANON_KEY', 'NEXT_PUBLIC_EZCODER_AUTH_ANON_KEY'),\r\n // Per-project Neon Auth (Better Auth) server base URL. When present, the app\r\n // authenticates against its OWN isolated Neon Auth server (e.g.\r\n // https://ep-xxx.neonauth.<region>.aws.neon.tech/neondb/auth) instead of the\r\n // shared tenant Supabase. Injected at provision time as VITE_NEON_AUTH_URL;\r\n // NEON_AUTH_BASE_URL is the server/Next fallback name. Presence of this value\r\n // is the sole trigger for the Better Auth code path in AuthProvider.\r\n NEON_AUTH_URL: getEnv('VITE_NEON_AUTH_URL', 'NEON_AUTH_BASE_URL'),\r\n STRIPE_PUBLISHABLE_KEY: getEnv('VITE_STRIPE_PUBLISHABLE_KEY', 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY'),\r\n EZC_PROJECT_ID: getEnv('VITE_EZC_PROJECT_ID', 'NEXT_PUBLIC_EZC_PROJECT_ID'),\r\n EZCODER_API_URL: getEnv('VITE_EZCODER_API_URL', 'NEXT_PUBLIC_EZCODER_API_URL'),\r\n // Server-only secret for Stripe + authenticated platform API calls. Deployment\r\n // injects this as `EZCODER_SECRET_KEY` (see lib/deployment/fetch-project-env-vars.js);\r\n // resolve that name first, then fall back to the legacy `EZC_SECRET_KEY` so older\r\n // deploys keep working. Both are non-VITE/non-NEXT_PUBLIC (never exposed to the browser).\r\n EZC_SECRET_KEY: getEnv('EZCODER_SECRET_KEY', 'EZCODER_SECRET_KEY') || getEnv('EZC_SECRET_KEY', 'EZC_SECRET_KEY'),\r\n // B1: two-class project token model.\r\n //\r\n // EZC_PROJECT_TOKEN_PUBLIC — browser-safe. Read-only endpoints (manifest,\r\n // list, etc). Available in both server and\r\n // browser bundles via NEXT_PUBLIC_/VITE_\r\n // prefixes.\r\n // EZC_PROJECT_TOKEN_SERVER — server-only. Authenticates write APIs\r\n // (email send, cron register, notifications\r\n // send). NEVER prefixed with NEXT_PUBLIC_ or\r\n // VITE_; accessing it from a browser bundle\r\n // returns '' and the calling SDK function\r\n // must error out with a clear message.\r\n EZC_PROJECT_TOKEN_PUBLIC: getEnv('VITE_EZC_PROJECT_TOKEN_PUBLIC', 'NEXT_PUBLIC_EZC_PROJECT_TOKEN_PUBLIC'),\r\n EZC_PROJECT_TOKEN_SERVER: getEnv('EZC_PROJECT_TOKEN_SERVER', 'EZC_PROJECT_TOKEN_SERVER'),\r\n // Legacy single-class token. Deprecated, kept for backwards compat during\r\n // Phase 1 of the rollout. New code MUST use the split pair above.\r\n EZC_PROJECT_TOKEN_LEGACY: getEnv('VITE_EZC_PROJECT_TOKEN', 'NEXT_PUBLIC_EZC_PROJECT_TOKEN'),\r\n S3_BUCKET: getEnv('VITE_S3_BUCKET', 'S3_BUCKET'),\r\n CLOUDINARY_URL: getEnv('VITE_CLOUDINARY_URL', 'CLOUDINARY_URL'),\r\n STORAGE_BUCKET_PUBLIC: getEnv('VITE_STORAGE_BUCKET_PUBLIC', 'NEXT_PUBLIC_STORAGE_BUCKET_PUBLIC'),\r\n STORAGE_BUCKET_PRIVATE: getEnv('VITE_STORAGE_BUCKET_PRIVATE', 'NEXT_PUBLIC_STORAGE_BUCKET_PRIVATE'),\r\n};\r\n\r\n// True when the project ships its own per-project Neon Auth (Better Auth)\r\n// server. This is authoritative for which auth backend AuthProvider uses.\r\nexport const isNeonAuthConfigured: boolean = Boolean(env.NEON_AUTH_URL);\r\n\r\nexport const features = {\r\n auth: Boolean(\r\n isNeonAuthConfigured ||\r\n (env.SUPABASE_URL && env.SUPABASE_ANON_KEY) ||\r\n (env.EZCODER_AUTH_URL && env.EZCODER_AUTH_ANON_KEY)\r\n ),\r\n payments: Boolean(env.STRIPE_PUBLISHABLE_KEY),\r\n analytics: Boolean(env.EZCODER_API_URL && env.EZC_PROJECT_ID),\r\n storage: Boolean(env.SUPABASE_URL || env.EZCODER_AUTH_URL || env.S3_BUCKET || env.CLOUDINARY_URL),\r\n database: Boolean(\r\n (env.EZCODER_AUTH_URL || env.SUPABASE_URL) && env.EZC_PROJECT_ID\r\n ),\r\n};\r\n\r\nexport function isFeatureConfigured(feature: keyof typeof features): boolean {\r\n return features[feature];\r\n}\r\n"],"mappings":";AAAA,IAAM,SAAS,CAAC,SAAiB,YAA6B;AAC5D,MAAI;AACF,QAAI,OAAO,gBAAgB,eAAe,YAAY,KAAK;AACzD,aAAQ,YAAY,IAAI,OAAO,KAAgB;AAAA,IACjD;AAAA,EACF,QAAQ;AAAA,EAER;AACA,MAAI;AACF,QAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,aAAO,QAAQ,IAAI,WAAW,OAAO,KAAK;AAAA,IAC5C;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEO,IAAM,MAAM;AAAA,EACjB,cAAc,OAAO,qBAAqB,0BAA0B;AAAA,EACpE,mBAAmB,OAAO,0BAA0B,+BAA+B;AAAA,EACnF,kBAAkB,OAAO,yBAAyB,8BAA8B;AAAA,EAChF,uBAAuB,OAAO,8BAA8B,mCAAmC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/F,eAAe,OAAO,sBAAsB,oBAAoB;AAAA,EAChE,wBAAwB,OAAO,+BAA+B,oCAAoC;AAAA,EAClG,gBAAgB,OAAO,uBAAuB,4BAA4B;AAAA,EAC1E,iBAAiB,OAAO,wBAAwB,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7E,gBAAgB,OAAO,sBAAsB,oBAAoB,KAAK,OAAO,kBAAkB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAa/G,0BAA0B,OAAO,iCAAiC,sCAAsC;AAAA,EACxG,0BAA0B,OAAO,4BAA4B,0BAA0B;AAAA;AAAA;AAAA,EAGvF,0BAA0B,OAAO,0BAA0B,+BAA+B;AAAA,EAC1F,WAAW,OAAO,kBAAkB,WAAW;AAAA,EAC/C,gBAAgB,OAAO,uBAAuB,gBAAgB;AAAA,EAC9D,uBAAuB,OAAO,8BAA8B,mCAAmC;AAAA,EAC/F,wBAAwB,OAAO,+BAA+B,oCAAoC;AACpG;AAIO,IAAM,uBAAgC,QAAQ,IAAI,aAAa;AAE/D,IAAM,WAAW;AAAA,EACtB,MAAM;AAAA,IACJ,wBACC,IAAI,gBAAgB,IAAI,qBACxB,IAAI,oBAAoB,IAAI;AAAA,EAC/B;AAAA,EACA,UAAU,QAAQ,IAAI,sBAAsB;AAAA,EAC5C,WAAW,QAAQ,IAAI,mBAAmB,IAAI,cAAc;AAAA,EAC5D,SAAS,QAAQ,IAAI,gBAAgB,IAAI,oBAAoB,IAAI,aAAa,IAAI,cAAc;AAAA,EAChG,UAAU;AAAA,KACP,IAAI,oBAAoB,IAAI,iBAAiB,IAAI;AAAA,EACpD;AACF;AAEO,SAAS,oBAAoB,SAAyC;AAC3E,SAAO,SAAS,OAAO;AACzB;","names":[]}
package/dist/cms/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  supabase
3
- } from "../chunk-I2YGB7Z6.js";
3
+ } from "../chunk-KKTY5NCR.js";
4
4
  import {
5
5
  features
6
- } from "../chunk-LIUE7M7K.js";
6
+ } from "../chunk-X4JP7DCK.js";
7
7
 
8
8
  // src/cms/cmsClient.ts
9
9
  function slugify(text) {
@@ -0,0 +1,29 @@
1
+ declare const env: {
2
+ SUPABASE_URL: string;
3
+ SUPABASE_ANON_KEY: string;
4
+ EZCODER_AUTH_URL: string;
5
+ EZCODER_AUTH_ANON_KEY: string;
6
+ NEON_AUTH_URL: string;
7
+ STRIPE_PUBLISHABLE_KEY: string;
8
+ EZC_PROJECT_ID: string;
9
+ EZCODER_API_URL: string;
10
+ EZC_SECRET_KEY: string;
11
+ EZC_PROJECT_TOKEN_PUBLIC: string;
12
+ EZC_PROJECT_TOKEN_SERVER: string;
13
+ EZC_PROJECT_TOKEN_LEGACY: string;
14
+ S3_BUCKET: string;
15
+ CLOUDINARY_URL: string;
16
+ STORAGE_BUCKET_PUBLIC: string;
17
+ STORAGE_BUCKET_PRIVATE: string;
18
+ };
19
+ declare const isNeonAuthConfigured: boolean;
20
+ declare const features: {
21
+ auth: boolean;
22
+ payments: boolean;
23
+ analytics: boolean;
24
+ storage: boolean;
25
+ database: boolean;
26
+ };
27
+ declare function isFeatureConfigured(feature: keyof typeof features): boolean;
28
+
29
+ export { isNeonAuthConfigured as a, env as e, features as f, isFeatureConfigured as i };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  resolveServerToken
3
- } from "../chunk-CQKYANAW.js";
3
+ } from "../chunk-4WGGFJPE.js";
4
4
  import {
5
5
  env
6
- } from "../chunk-LIUE7M7K.js";
6
+ } from "../chunk-X4JP7DCK.js";
7
7
 
8
8
  // src/cron/registerJob.ts
9
9
  async function registerCronJob(name, cronExpression, endpointUrl, options = {}) {
@@ -11,10 +11,10 @@ import {
11
11
  useDatabaseOptional,
12
12
  useIsDatabaseConfigured,
13
13
  useRealtime
14
- } from "../chunk-O3GL4ZVC.js";
15
- import "../chunk-HJ2EIZ4S.js";
16
- import "../chunk-I2YGB7Z6.js";
17
- import "../chunk-LIUE7M7K.js";
14
+ } from "../chunk-5QYGP7G7.js";
15
+ import "../chunk-DU6N3HVQ.js";
16
+ import "../chunk-KKTY5NCR.js";
17
+ import "../chunk-X4JP7DCK.js";
18
18
  export {
19
19
  ConnectionError,
20
20
  DatabaseClient,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  resolveServerToken
3
- } from "../chunk-CQKYANAW.js";
3
+ } from "../chunk-4WGGFJPE.js";
4
4
  import {
5
5
  env
6
- } from "../chunk-LIUE7M7K.js";
6
+ } from "../chunk-X4JP7DCK.js";
7
7
 
8
8
  // src/email/sendEmail.ts
9
9
  async function sendEmail(to, subject, html, options = {}) {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { e as env, f as features, i as isFeatureConfigured, a as isNeonAuthConfigured } from './config-D5ajnLCe.js';
1
2
  import { SupabaseClient } from '@supabase/supabase-js';
2
3
  import { E as EzcoderClient, A as AuthIntegration } from './types-1uP3V_pe.js';
3
4
  export { a as AnalyticsResult, b as AuthUser, C as CheckoutOptions, S as StorageFile, c as StorageResult, d as SubscriptionStatus, e as SubscriptionTier, U as UserProfile } from './types-1uP3V_pe.js';
@@ -5,32 +6,6 @@ export { D as DatabaseClient, a as DatabaseProvider, u as useDatabase } from './
5
6
  import 'react/jsx-runtime';
6
7
  import 'react';
7
8
 
8
- declare const env: {
9
- SUPABASE_URL: string;
10
- SUPABASE_ANON_KEY: string;
11
- EZCODER_AUTH_URL: string;
12
- EZCODER_AUTH_ANON_KEY: string;
13
- STRIPE_PUBLISHABLE_KEY: string;
14
- EZC_PROJECT_ID: string;
15
- EZCODER_API_URL: string;
16
- EZC_SECRET_KEY: string;
17
- EZC_PROJECT_TOKEN_PUBLIC: string;
18
- EZC_PROJECT_TOKEN_SERVER: string;
19
- EZC_PROJECT_TOKEN_LEGACY: string;
20
- S3_BUCKET: string;
21
- CLOUDINARY_URL: string;
22
- STORAGE_BUCKET_PUBLIC: string;
23
- STORAGE_BUCKET_PRIVATE: string;
24
- };
25
- declare const features: {
26
- auth: boolean;
27
- payments: boolean;
28
- analytics: boolean;
29
- storage: boolean;
30
- database: boolean;
31
- };
32
- declare function isFeatureConfigured(feature: keyof typeof features): boolean;
33
-
34
9
  /**
35
10
  * Runtime config bootstrap (Parity Doctrine W2).
36
11
  *
@@ -243,4 +218,4 @@ interface UseEzCoderManifestResult {
243
218
  */
244
219
  declare function useEzCoderManifest(): UseEzCoderManifestResult;
245
220
 
246
- export { AuthIntegration, type EnvRefs, EzCoderManifest, EzcoderClient, type RequiredModules, type RuntimeConfig, type SanitizedManifest, type UseEzCoderManifestResult, configValue, env, ezcoder, ezcoderAuthIntegration, features, getRuntimeConfig, isFeatureConfigured, isSupabaseConfigured, loadBootstrap, supabase, useEzCoderManifest };
221
+ export { AuthIntegration, type EnvRefs, EzCoderManifest, EzcoderClient, type RequiredModules, type RuntimeConfig, type SanitizedManifest, type UseEzCoderManifestResult, configValue, ezcoder, ezcoderAuthIntegration, getRuntimeConfig, isSupabaseConfigured, loadBootstrap, supabase, useEzCoderManifest };
package/dist/index.js CHANGED
@@ -5,20 +5,21 @@ import {
5
5
  getRuntimeConfig,
6
6
  loadBootstrap,
7
7
  useDatabase
8
- } from "./chunk-O3GL4ZVC.js";
8
+ } from "./chunk-5QYGP7G7.js";
9
9
  import {
10
10
  ezcoder,
11
11
  ezcoderAuthIntegration
12
- } from "./chunk-HJ2EIZ4S.js";
12
+ } from "./chunk-DU6N3HVQ.js";
13
13
  import {
14
14
  isSupabaseConfigured,
15
15
  supabase
16
- } from "./chunk-I2YGB7Z6.js";
16
+ } from "./chunk-KKTY5NCR.js";
17
17
  import {
18
18
  env,
19
19
  features,
20
- isFeatureConfigured
21
- } from "./chunk-LIUE7M7K.js";
20
+ isFeatureConfigured,
21
+ isNeonAuthConfigured
22
+ } from "./chunk-X4JP7DCK.js";
22
23
 
23
24
  // src/manifest-consumer.ts
24
25
  var manifestCache = /* @__PURE__ */ new Map();
@@ -282,6 +283,7 @@ export {
282
283
  features,
283
284
  getRuntimeConfig,
284
285
  isFeatureConfigured,
286
+ isNeonAuthConfigured,
285
287
  isSupabaseConfigured,
286
288
  loadBootstrap,
287
289
  supabase,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest-consumer.ts","../src/useEzCoderManifest.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @module manifest-consumer\r\n * @description Runtime + build-time consumer of the EzCoder Capability Manifest.\r\n *\r\n * Track B (B7) — the user-app SDK reads the project's Capability Manifest so\r\n * it knows which feature modules are actually required (auth, storage,\r\n * payments, email, cron, notifications, database) and which env refs to\r\n * expect. The SDK historically auto-registered everything regardless of\r\n * project needs; this consumer adds *opt-in* awareness without breaking\r\n * existing consumers.\r\n *\r\n * Failure mode is deliberately permissive: any fetch/parse error falls\r\n * through to \"all modules considered required\" so a flaky network blip on\r\n * the user's deployed site never silently disables a feature.\r\n */\r\n\r\nimport { env } from './core/config';\r\n\r\n// ─── Public surface ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * The seven modules the SDK can register. Mirrors the keys of\r\n * `service_requirements` in lib/manifest/schema.ts plus `database` derived\r\n * from the `database` requirement block.\r\n */\r\nexport interface RequiredModules {\r\n auth: boolean;\r\n storage: boolean;\r\n payments: boolean;\r\n email: boolean;\r\n cron: boolean;\r\n database: boolean;\r\n notifications: boolean;\r\n}\r\n\r\n/**\r\n * Public + (publicly-visible names of) secret env refs. The server-side\r\n * endpoint deliberately strips `secret` before returning, but we keep the\r\n * field on the consumer type as an empty array for forward-compat — once a\r\n * privileged consumer path exists it can populate it.\r\n */\r\nexport interface EnvRefs {\r\n public: ReadonlyArray<string>;\r\n secret: ReadonlyArray<string>;\r\n}\r\n\r\n/**\r\n * The shape the SDK actually consumes. A trimmed projection of the full\r\n * Capability Manifest; the server endpoint enforces the projection so the\r\n * SDK can never see secret-ref names even if it asks for them.\r\n */\r\nexport interface SanitizedManifest {\r\n manifest_version: number;\r\n project_id: string;\r\n service_requirements: {\r\n database?: { provider: string; rls_required: boolean; tables: ReadonlyArray<string> };\r\n auth?: { provider: string; flows: ReadonlyArray<string> };\r\n storage?: { buckets: ReadonlyArray<string> };\r\n payments?: { mode: string };\r\n email?: { provider: string };\r\n cron?: { jobs: ReadonlyArray<{ name: string; schedule: string }> };\r\n };\r\n env_refs: { public: ReadonlyArray<string> };\r\n validation_gates: ReadonlyArray<string>;\r\n}\r\n\r\n// ─── Module-level cache (keyed by projectId) ──────────────────────────────\r\n\r\nconst manifestCache: Map<string, EzCoderManifest> = new Map();\r\nconst inflightLoads: Map<string, Promise<EzCoderManifest>> = new Map();\r\n\r\n/**\r\n * Default-on RequiredModules — used as the safe fallback when no manifest\r\n * is available. Preserves the SDK's historical behaviour (everything wired)\r\n * so a missing/failed manifest never silently disables a feature in\r\n * already-deployed user apps.\r\n */\r\nconst PERMISSIVE_DEFAULT_MODULES: Readonly<RequiredModules> = Object.freeze({\r\n auth: true,\r\n storage: true,\r\n payments: true,\r\n email: true,\r\n cron: true,\r\n database: true,\r\n notifications: true,\r\n});\r\n\r\n// ─── EzCoderManifest class ────────────────────────────────────────────────\r\n\r\n/**\r\n * Wraps a sanitized manifest payload and exposes module/env-ref queries.\r\n * Instances are immutable — `manifest` is captured at construction time and\r\n * never mutated. Use the static `load()` / `loadFromBuildOutput()` factories\r\n * rather than calling `new` directly so the page-lifetime cache is honoured.\r\n */\r\nexport class EzCoderManifest {\r\n private readonly manifest: SanitizedManifest | null;\r\n private readonly source: 'runtime' | 'build' | 'fallback';\r\n\r\n constructor(manifest: SanitizedManifest | null, source: 'runtime' | 'build' | 'fallback') {\r\n this.manifest = manifest;\r\n this.source = source;\r\n }\r\n\r\n /**\r\n * Fetch the manifest at runtime from the platform API. Returns a\r\n * cached instance for the lifetime of the page; concurrent callers share\r\n * a single in-flight fetch. Falls back to a permissive (all-modules-on)\r\n * instance on any error.\r\n */\r\n static async load(): Promise<EzCoderManifest> {\r\n const projectId = env.EZC_PROJECT_ID;\r\n const apiUrl = env.EZCODER_API_URL;\r\n const publicToken = readPublicToken();\r\n\r\n if (!projectId || !apiUrl) {\r\n return new EzCoderManifest(null, 'fallback');\r\n }\r\n\r\n const cacheKey = `${projectId}::${apiUrl}`;\r\n const cached = manifestCache.get(cacheKey);\r\n if (cached) return cached;\r\n\r\n const inflight = inflightLoads.get(cacheKey);\r\n if (inflight) return inflight;\r\n\r\n const loadPromise = fetchManifest(apiUrl, publicToken)\r\n .then((m) => {\r\n const instance = new EzCoderManifest(m, 'runtime');\r\n manifestCache.set(cacheKey, instance);\r\n return instance;\r\n })\r\n .catch((err: unknown) => {\r\n const message = err instanceof Error ? err.message : 'unknown';\r\n if (typeof console !== 'undefined') {\r\n console.warn(`[EzCoder SDK] manifest fetch failed (${message}); falling back to permissive mode.`);\r\n }\r\n const fallback = new EzCoderManifest(null, 'fallback');\r\n manifestCache.set(cacheKey, fallback);\r\n return fallback;\r\n })\r\n .finally(() => {\r\n inflightLoads.delete(cacheKey);\r\n });\r\n\r\n inflightLoads.set(cacheKey, loadPromise);\r\n return loadPromise;\r\n }\r\n\r\n /**\r\n * Read a manifest written by the build-time codegen step (see\r\n * scripts/fetch-build-manifest.js). Used by SSG callers that want to\r\n * avoid a runtime network round-trip. Returns null when no build-time\r\n * manifest is present — callers should fall back to `load()`.\r\n *\r\n * Implementation note: we deliberately avoid a static `import` because\r\n * (a) the file may not exist, which would be a hard module-resolution\r\n * error, and (b) browser bundles must not pull in the JSON. We probe\r\n * `process` to detect Node, then read the file from disk via the eval'd\r\n * dynamic require — the eval indirection keeps bundlers from following\r\n * the path at build time.\r\n */\r\n static loadFromBuildOutput(): EzCoderManifest | null {\r\n try {\r\n if (typeof process === 'undefined' || !process.versions?.node) {\r\n return null;\r\n }\r\n // Avoid a static `import('fs')` because (a) the file may not exist,\r\n // (b) bundlers would attempt to resolve it for browser targets, and\r\n // (c) we want a Node-only branch the bundler can drop. Construct the\r\n // require lookup via the Function constructor so the literal \"require\"\r\n // identifier never appears in any statically-analyzable position.\r\n const getRequire = new Function(\r\n 'return typeof require === \"function\" ? require : null;',\r\n ) as () => ((id: string) => unknown) | null;\r\n const dynamicRequire = getRequire();\r\n if (!dynamicRequire) return null;\r\n const fs = dynamicRequire('fs') as { readFileSync: (p: string, enc: string) => string };\r\n const path = dynamicRequire('path') as { resolve: (...segments: string[]) => string };\r\n const cwd = process.cwd();\r\n const full = path.resolve(cwd, 'node_modules', '.ezcoder', 'manifest.json');\r\n const raw = fs.readFileSync(full, 'utf8');\r\n const payload: unknown = JSON.parse(raw);\r\n const parsed = parseManifestPayload(payload);\r\n if (!parsed) return null;\r\n return new EzCoderManifest(parsed, 'build');\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boolean enablement map for each SDK module. When no\r\n * manifest is loaded, returns the permissive default (everything on) so\r\n * legacy behaviour is preserved.\r\n */\r\n getRequiredModules(): RequiredModules {\r\n if (!this.manifest) {\r\n return { ...PERMISSIVE_DEFAULT_MODULES };\r\n }\r\n const sr = this.manifest.service_requirements;\r\n return {\r\n auth: Boolean(sr.auth),\r\n storage: Boolean(sr.storage),\r\n payments: Boolean(sr.payments),\r\n email: Boolean(sr.email),\r\n cron: Boolean(sr.cron),\r\n database: Boolean(sr.database),\r\n // notifications is not in service_requirements v0; derive from email\r\n // OR auth so the existing notifications module behaves sensibly.\r\n notifications: Boolean(sr.email || sr.auth),\r\n };\r\n }\r\n\r\n /**\r\n * Returns the env refs declared by the manifest. The `secret` array is\r\n * always empty when the manifest came from the public SDK endpoint —\r\n * server-only refs are stripped before transit (see pages/api/sdk/manifest.js).\r\n */\r\n getEnvRefs(): EnvRefs {\r\n if (!this.manifest) {\r\n return { public: [], secret: [] };\r\n }\r\n return {\r\n public: this.manifest.env_refs.public,\r\n secret: [],\r\n };\r\n }\r\n\r\n /**\r\n * Convenience: is a given module required by this app?\r\n */\r\n has(module: keyof RequiredModules): boolean {\r\n return this.getRequiredModules()[module];\r\n }\r\n\r\n /**\r\n * Where the manifest was loaded from. Useful for debugging and for\r\n * surface-level telemetry (`fallback` means we never got the real one).\r\n */\r\n getSource(): 'runtime' | 'build' | 'fallback' {\r\n return this.source;\r\n }\r\n\r\n /**\r\n * Test-only helper: wipe the page-lifetime cache. Never call this in\r\n * production code.\r\n */\r\n static __resetCacheForTests(): void {\r\n manifestCache.clear();\r\n inflightLoads.clear();\r\n }\r\n}\r\n\r\n// ─── Internals ────────────────────────────────────────────────────────────\r\n\r\nfunction readPublicToken(): string {\r\n // The PUBLIC token is intentionally distinct from the server-only secret\r\n // key. We accept both Vite and Next env shapes; both must be safe to\r\n // expose to the browser (i.e. namespaced as PUBLIC).\r\n const fromVite = safeEnv('VITE_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromVite) return fromVite;\r\n const fromNext = safeEnv('NEXT_PUBLIC_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromNext) return fromNext;\r\n return safeEnv('EZC_PROJECT_TOKEN_PUBLIC');\r\n}\r\n\r\nfunction safeEnv(key: string): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const v = (import.meta.env as Record<string, unknown>)[key];\r\n if (typeof v === 'string') return v;\r\n }\r\n } catch {\r\n // import.meta unavailable in this runtime — fall through.\r\n }\r\n try {\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[key] || '';\r\n }\r\n } catch {\r\n // process unavailable — fall through.\r\n }\r\n return '';\r\n}\r\n\r\nasync function fetchManifest(apiUrl: string, publicToken: string): Promise<SanitizedManifest> {\r\n const headers: Record<string, string> = {\r\n 'Accept': 'application/json',\r\n };\r\n if (publicToken) {\r\n headers['X-EzCoder-Public-Token'] = publicToken;\r\n }\r\n const res = await fetch(`${apiUrl}/api/sdk/manifest`, {\r\n method: 'GET',\r\n headers,\r\n // Browsers will fold the 5-minute cache header automatically; this hint\r\n // is here for non-browser runtimes that respect it.\r\n cache: 'default',\r\n });\r\n if (!res.ok) {\r\n throw new Error(`HTTP ${res.status}`);\r\n }\r\n const body: unknown = await res.json();\r\n const parsed = parseManifestPayload(body);\r\n if (!parsed) {\r\n throw new Error('manifest payload failed shape validation');\r\n }\r\n return parsed;\r\n}\r\n\r\n/**\r\n * Permissive boundary parser. We deliberately do NOT pull Zod into the SDK\r\n * bundle — instead we run a lightweight shape check and let unknown fields\r\n * pass through. The server has already validated the manifest with Zod\r\n * before sending; this is just defense-in-depth against a malformed\r\n * response (e.g. an HTML error page).\r\n */\r\nfunction parseManifestPayload(input: unknown): SanitizedManifest | null {\r\n if (!isRecord(input)) return null;\r\n const projectId = input.project_id;\r\n const serviceReqs = input.service_requirements;\r\n const envRefs = input.env_refs;\r\n const gates = input.validation_gates;\r\n if (typeof projectId !== 'string' || projectId.length === 0) return null;\r\n if (!isRecord(serviceReqs)) return null;\r\n if (!isRecord(envRefs) || !Array.isArray(envRefs.public)) return null;\r\n if (!Array.isArray(gates)) return null;\r\n const manifestVersion =\r\n typeof input.manifest_version === 'number' ? input.manifest_version : 0;\r\n return {\r\n manifest_version: manifestVersion,\r\n project_id: projectId,\r\n service_requirements: serviceReqs as SanitizedManifest['service_requirements'],\r\n env_refs: { public: envRefs.public.filter((v): v is string => typeof v === 'string') },\r\n validation_gates: gates.filter((v): v is string => typeof v === 'string'),\r\n };\r\n}\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n","/**\r\n * @module useEzCoderManifest\r\n * @description React hook that exposes the loaded Capability Manifest to\r\n * components so they can branch on whether a feature module is required by\r\n * the current project.\r\n *\r\n * Importing this file pulls `react` as a peer dep — kept in a dedicated\r\n * file so SSR / non-React entries can consume `manifest-consumer.ts`\r\n * without touching React.\r\n */\r\n\r\nimport { useEffect, useState } from 'react';\r\nimport { EzCoderManifest, type RequiredModules } from './manifest-consumer';\r\n\r\nexport interface UseEzCoderManifestResult {\r\n manifest: EzCoderManifest | null;\r\n modules: RequiredModules | null;\r\n loading: boolean;\r\n source: 'runtime' | 'build' | 'fallback' | null;\r\n}\r\n\r\n/**\r\n * Synchronously prefers a build-time manifest (avoids a render-blocking\r\n * fetch on SSG/SSR); otherwise kicks off the async runtime load. Until the\r\n * load resolves, `modules` is null and callers should treat features as\r\n * unknown (recommended: render the historical default, then re-render once\r\n * `modules` settles).\r\n */\r\nexport function useEzCoderManifest(): UseEzCoderManifestResult {\r\n // Lazy initializer — `loadFromBuildOutput` touches the filesystem on Node\r\n // and must NOT run on every render. Memoized once across the mount.\r\n const [manifest, setManifest] = useState<EzCoderManifest | null>(\r\n () => EzCoderManifest.loadFromBuildOutput(),\r\n );\r\n const [loading, setLoading] = useState<boolean>(manifest === null);\r\n\r\n useEffect(() => {\r\n // If we already have a build-time manifest, no need to fetch.\r\n if (manifest) return;\r\n\r\n let cancelled = false;\r\n EzCoderManifest.load()\r\n .then((m) => {\r\n if (cancelled) return;\r\n setManifest(m);\r\n setLoading(false);\r\n })\r\n .catch(() => {\r\n if (cancelled) return;\r\n // load() already handles errors and returns a fallback, but be\r\n // defensive: ensure we never leave the UI stuck in loading state.\r\n setLoading(false);\r\n });\r\n\r\n return () => {\r\n cancelled = true;\r\n };\r\n // We only want this effect to run on mount; once `manifest` settles,\r\n // re-running would re-fetch unnecessarily.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n return {\r\n manifest,\r\n modules: manifest ? manifest.getRequiredModules() : null,\r\n loading,\r\n source: manifest ? manifest.getSource() : null,\r\n };\r\n}\r\n","export { env, features, isFeatureConfigured } from './core/config';\r\nexport { loadBootstrap, getRuntimeConfig, configValue } from './core/bootstrap';\r\nexport type { RuntimeConfig } from './core/bootstrap';\r\nexport { supabase, isSupabaseConfigured } from './core/supabase';\r\nexport { ezcoder, ezcoderAuthIntegration } from './core/platform';\r\nexport { DatabaseProvider, useDatabase, DatabaseClient } from './database';\r\nexport type {\r\n AuthUser,\r\n UserProfile,\r\n SubscriptionTier,\r\n SubscriptionStatus,\r\n StorageResult,\r\n StorageFile,\r\n CheckoutOptions,\r\n EzcoderClient,\r\n AnalyticsResult,\r\n AuthIntegration,\r\n} from './core/types';\r\n\r\n// ─── Capability Manifest (B7) ────────────────────────────────────────────\r\n//\r\n// Manifest awareness is OPT-IN. The legacy module auto-registration is\r\n// preserved, so existing consumers see no behavioural change. New code can\r\n// import these to gate features:\r\n//\r\n// import { EzCoderManifest, useEzCoderManifest } from '@ezcoder.dev/sdk';\r\n// const { modules, loading } = useEzCoderManifest();\r\n// if (!loading && modules?.payments) renderCheckout();\r\n//\r\nexport { EzCoderManifest } from './manifest-consumer';\r\nexport type { RequiredModules, EnvRefs, SanitizedManifest } from './manifest-consumer';\r\nexport { useEzCoderManifest } from './useEzCoderManifest';\r\nexport type { UseEzCoderManifestResult } from './useEzCoderManifest';\r\n\r\n// Kick off an opportunistic load on import so the cache is warm by the\r\n// time components call useEzCoderManifest(). Errors are swallowed inside\r\n// load() and produce a fallback instance — never throws.\r\nimport { EzCoderManifest as _EzCoderManifestForBoot } from './manifest-consumer';\r\nif (typeof window !== 'undefined') {\r\n // Browser only. SSR/SSG paths that want the manifest should call\r\n // loadFromBuildOutput() or load() explicitly to control timing.\r\n void _EzCoderManifestForBoot.load();\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/manifest-consumer.ts","../src/useEzCoderManifest.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @module manifest-consumer\r\n * @description Runtime + build-time consumer of the EzCoder Capability Manifest.\r\n *\r\n * Track B (B7) — the user-app SDK reads the project's Capability Manifest so\r\n * it knows which feature modules are actually required (auth, storage,\r\n * payments, email, cron, notifications, database) and which env refs to\r\n * expect. The SDK historically auto-registered everything regardless of\r\n * project needs; this consumer adds *opt-in* awareness without breaking\r\n * existing consumers.\r\n *\r\n * Failure mode is deliberately permissive: any fetch/parse error falls\r\n * through to \"all modules considered required\" so a flaky network blip on\r\n * the user's deployed site never silently disables a feature.\r\n */\r\n\r\nimport { env } from './core/config';\r\n\r\n// ─── Public surface ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * The seven modules the SDK can register. Mirrors the keys of\r\n * `service_requirements` in lib/manifest/schema.ts plus `database` derived\r\n * from the `database` requirement block.\r\n */\r\nexport interface RequiredModules {\r\n auth: boolean;\r\n storage: boolean;\r\n payments: boolean;\r\n email: boolean;\r\n cron: boolean;\r\n database: boolean;\r\n notifications: boolean;\r\n}\r\n\r\n/**\r\n * Public + (publicly-visible names of) secret env refs. The server-side\r\n * endpoint deliberately strips `secret` before returning, but we keep the\r\n * field on the consumer type as an empty array for forward-compat — once a\r\n * privileged consumer path exists it can populate it.\r\n */\r\nexport interface EnvRefs {\r\n public: ReadonlyArray<string>;\r\n secret: ReadonlyArray<string>;\r\n}\r\n\r\n/**\r\n * The shape the SDK actually consumes. A trimmed projection of the full\r\n * Capability Manifest; the server endpoint enforces the projection so the\r\n * SDK can never see secret-ref names even if it asks for them.\r\n */\r\nexport interface SanitizedManifest {\r\n manifest_version: number;\r\n project_id: string;\r\n service_requirements: {\r\n database?: { provider: string; rls_required: boolean; tables: ReadonlyArray<string> };\r\n auth?: { provider: string; flows: ReadonlyArray<string> };\r\n storage?: { buckets: ReadonlyArray<string> };\r\n payments?: { mode: string };\r\n email?: { provider: string };\r\n cron?: { jobs: ReadonlyArray<{ name: string; schedule: string }> };\r\n };\r\n env_refs: { public: ReadonlyArray<string> };\r\n validation_gates: ReadonlyArray<string>;\r\n}\r\n\r\n// ─── Module-level cache (keyed by projectId) ──────────────────────────────\r\n\r\nconst manifestCache: Map<string, EzCoderManifest> = new Map();\r\nconst inflightLoads: Map<string, Promise<EzCoderManifest>> = new Map();\r\n\r\n/**\r\n * Default-on RequiredModules — used as the safe fallback when no manifest\r\n * is available. Preserves the SDK's historical behaviour (everything wired)\r\n * so a missing/failed manifest never silently disables a feature in\r\n * already-deployed user apps.\r\n */\r\nconst PERMISSIVE_DEFAULT_MODULES: Readonly<RequiredModules> = Object.freeze({\r\n auth: true,\r\n storage: true,\r\n payments: true,\r\n email: true,\r\n cron: true,\r\n database: true,\r\n notifications: true,\r\n});\r\n\r\n// ─── EzCoderManifest class ────────────────────────────────────────────────\r\n\r\n/**\r\n * Wraps a sanitized manifest payload and exposes module/env-ref queries.\r\n * Instances are immutable — `manifest` is captured at construction time and\r\n * never mutated. Use the static `load()` / `loadFromBuildOutput()` factories\r\n * rather than calling `new` directly so the page-lifetime cache is honoured.\r\n */\r\nexport class EzCoderManifest {\r\n private readonly manifest: SanitizedManifest | null;\r\n private readonly source: 'runtime' | 'build' | 'fallback';\r\n\r\n constructor(manifest: SanitizedManifest | null, source: 'runtime' | 'build' | 'fallback') {\r\n this.manifest = manifest;\r\n this.source = source;\r\n }\r\n\r\n /**\r\n * Fetch the manifest at runtime from the platform API. Returns a\r\n * cached instance for the lifetime of the page; concurrent callers share\r\n * a single in-flight fetch. Falls back to a permissive (all-modules-on)\r\n * instance on any error.\r\n */\r\n static async load(): Promise<EzCoderManifest> {\r\n const projectId = env.EZC_PROJECT_ID;\r\n const apiUrl = env.EZCODER_API_URL;\r\n const publicToken = readPublicToken();\r\n\r\n if (!projectId || !apiUrl) {\r\n return new EzCoderManifest(null, 'fallback');\r\n }\r\n\r\n const cacheKey = `${projectId}::${apiUrl}`;\r\n const cached = manifestCache.get(cacheKey);\r\n if (cached) return cached;\r\n\r\n const inflight = inflightLoads.get(cacheKey);\r\n if (inflight) return inflight;\r\n\r\n const loadPromise = fetchManifest(apiUrl, publicToken)\r\n .then((m) => {\r\n const instance = new EzCoderManifest(m, 'runtime');\r\n manifestCache.set(cacheKey, instance);\r\n return instance;\r\n })\r\n .catch((err: unknown) => {\r\n const message = err instanceof Error ? err.message : 'unknown';\r\n if (typeof console !== 'undefined') {\r\n console.warn(`[EzCoder SDK] manifest fetch failed (${message}); falling back to permissive mode.`);\r\n }\r\n const fallback = new EzCoderManifest(null, 'fallback');\r\n manifestCache.set(cacheKey, fallback);\r\n return fallback;\r\n })\r\n .finally(() => {\r\n inflightLoads.delete(cacheKey);\r\n });\r\n\r\n inflightLoads.set(cacheKey, loadPromise);\r\n return loadPromise;\r\n }\r\n\r\n /**\r\n * Read a manifest written by the build-time codegen step (see\r\n * scripts/fetch-build-manifest.js). Used by SSG callers that want to\r\n * avoid a runtime network round-trip. Returns null when no build-time\r\n * manifest is present — callers should fall back to `load()`.\r\n *\r\n * Implementation note: we deliberately avoid a static `import` because\r\n * (a) the file may not exist, which would be a hard module-resolution\r\n * error, and (b) browser bundles must not pull in the JSON. We probe\r\n * `process` to detect Node, then read the file from disk via the eval'd\r\n * dynamic require — the eval indirection keeps bundlers from following\r\n * the path at build time.\r\n */\r\n static loadFromBuildOutput(): EzCoderManifest | null {\r\n try {\r\n if (typeof process === 'undefined' || !process.versions?.node) {\r\n return null;\r\n }\r\n // Avoid a static `import('fs')` because (a) the file may not exist,\r\n // (b) bundlers would attempt to resolve it for browser targets, and\r\n // (c) we want a Node-only branch the bundler can drop. Construct the\r\n // require lookup via the Function constructor so the literal \"require\"\r\n // identifier never appears in any statically-analyzable position.\r\n const getRequire = new Function(\r\n 'return typeof require === \"function\" ? require : null;',\r\n ) as () => ((id: string) => unknown) | null;\r\n const dynamicRequire = getRequire();\r\n if (!dynamicRequire) return null;\r\n const fs = dynamicRequire('fs') as { readFileSync: (p: string, enc: string) => string };\r\n const path = dynamicRequire('path') as { resolve: (...segments: string[]) => string };\r\n const cwd = process.cwd();\r\n const full = path.resolve(cwd, 'node_modules', '.ezcoder', 'manifest.json');\r\n const raw = fs.readFileSync(full, 'utf8');\r\n const payload: unknown = JSON.parse(raw);\r\n const parsed = parseManifestPayload(payload);\r\n if (!parsed) return null;\r\n return new EzCoderManifest(parsed, 'build');\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boolean enablement map for each SDK module. When no\r\n * manifest is loaded, returns the permissive default (everything on) so\r\n * legacy behaviour is preserved.\r\n */\r\n getRequiredModules(): RequiredModules {\r\n if (!this.manifest) {\r\n return { ...PERMISSIVE_DEFAULT_MODULES };\r\n }\r\n const sr = this.manifest.service_requirements;\r\n return {\r\n auth: Boolean(sr.auth),\r\n storage: Boolean(sr.storage),\r\n payments: Boolean(sr.payments),\r\n email: Boolean(sr.email),\r\n cron: Boolean(sr.cron),\r\n database: Boolean(sr.database),\r\n // notifications is not in service_requirements v0; derive from email\r\n // OR auth so the existing notifications module behaves sensibly.\r\n notifications: Boolean(sr.email || sr.auth),\r\n };\r\n }\r\n\r\n /**\r\n * Returns the env refs declared by the manifest. The `secret` array is\r\n * always empty when the manifest came from the public SDK endpoint —\r\n * server-only refs are stripped before transit (see pages/api/sdk/manifest.js).\r\n */\r\n getEnvRefs(): EnvRefs {\r\n if (!this.manifest) {\r\n return { public: [], secret: [] };\r\n }\r\n return {\r\n public: this.manifest.env_refs.public,\r\n secret: [],\r\n };\r\n }\r\n\r\n /**\r\n * Convenience: is a given module required by this app?\r\n */\r\n has(module: keyof RequiredModules): boolean {\r\n return this.getRequiredModules()[module];\r\n }\r\n\r\n /**\r\n * Where the manifest was loaded from. Useful for debugging and for\r\n * surface-level telemetry (`fallback` means we never got the real one).\r\n */\r\n getSource(): 'runtime' | 'build' | 'fallback' {\r\n return this.source;\r\n }\r\n\r\n /**\r\n * Test-only helper: wipe the page-lifetime cache. Never call this in\r\n * production code.\r\n */\r\n static __resetCacheForTests(): void {\r\n manifestCache.clear();\r\n inflightLoads.clear();\r\n }\r\n}\r\n\r\n// ─── Internals ────────────────────────────────────────────────────────────\r\n\r\nfunction readPublicToken(): string {\r\n // The PUBLIC token is intentionally distinct from the server-only secret\r\n // key. We accept both Vite and Next env shapes; both must be safe to\r\n // expose to the browser (i.e. namespaced as PUBLIC).\r\n const fromVite = safeEnv('VITE_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromVite) return fromVite;\r\n const fromNext = safeEnv('NEXT_PUBLIC_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromNext) return fromNext;\r\n return safeEnv('EZC_PROJECT_TOKEN_PUBLIC');\r\n}\r\n\r\nfunction safeEnv(key: string): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const v = (import.meta.env as Record<string, unknown>)[key];\r\n if (typeof v === 'string') return v;\r\n }\r\n } catch {\r\n // import.meta unavailable in this runtime — fall through.\r\n }\r\n try {\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[key] || '';\r\n }\r\n } catch {\r\n // process unavailable — fall through.\r\n }\r\n return '';\r\n}\r\n\r\nasync function fetchManifest(apiUrl: string, publicToken: string): Promise<SanitizedManifest> {\r\n const headers: Record<string, string> = {\r\n 'Accept': 'application/json',\r\n };\r\n if (publicToken) {\r\n headers['X-EzCoder-Public-Token'] = publicToken;\r\n }\r\n const res = await fetch(`${apiUrl}/api/sdk/manifest`, {\r\n method: 'GET',\r\n headers,\r\n // Browsers will fold the 5-minute cache header automatically; this hint\r\n // is here for non-browser runtimes that respect it.\r\n cache: 'default',\r\n });\r\n if (!res.ok) {\r\n throw new Error(`HTTP ${res.status}`);\r\n }\r\n const body: unknown = await res.json();\r\n const parsed = parseManifestPayload(body);\r\n if (!parsed) {\r\n throw new Error('manifest payload failed shape validation');\r\n }\r\n return parsed;\r\n}\r\n\r\n/**\r\n * Permissive boundary parser. We deliberately do NOT pull Zod into the SDK\r\n * bundle — instead we run a lightweight shape check and let unknown fields\r\n * pass through. The server has already validated the manifest with Zod\r\n * before sending; this is just defense-in-depth against a malformed\r\n * response (e.g. an HTML error page).\r\n */\r\nfunction parseManifestPayload(input: unknown): SanitizedManifest | null {\r\n if (!isRecord(input)) return null;\r\n const projectId = input.project_id;\r\n const serviceReqs = input.service_requirements;\r\n const envRefs = input.env_refs;\r\n const gates = input.validation_gates;\r\n if (typeof projectId !== 'string' || projectId.length === 0) return null;\r\n if (!isRecord(serviceReqs)) return null;\r\n if (!isRecord(envRefs) || !Array.isArray(envRefs.public)) return null;\r\n if (!Array.isArray(gates)) return null;\r\n const manifestVersion =\r\n typeof input.manifest_version === 'number' ? input.manifest_version : 0;\r\n return {\r\n manifest_version: manifestVersion,\r\n project_id: projectId,\r\n service_requirements: serviceReqs as SanitizedManifest['service_requirements'],\r\n env_refs: { public: envRefs.public.filter((v): v is string => typeof v === 'string') },\r\n validation_gates: gates.filter((v): v is string => typeof v === 'string'),\r\n };\r\n}\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n","/**\r\n * @module useEzCoderManifest\r\n * @description React hook that exposes the loaded Capability Manifest to\r\n * components so they can branch on whether a feature module is required by\r\n * the current project.\r\n *\r\n * Importing this file pulls `react` as a peer dep — kept in a dedicated\r\n * file so SSR / non-React entries can consume `manifest-consumer.ts`\r\n * without touching React.\r\n */\r\n\r\nimport { useEffect, useState } from 'react';\r\nimport { EzCoderManifest, type RequiredModules } from './manifest-consumer';\r\n\r\nexport interface UseEzCoderManifestResult {\r\n manifest: EzCoderManifest | null;\r\n modules: RequiredModules | null;\r\n loading: boolean;\r\n source: 'runtime' | 'build' | 'fallback' | null;\r\n}\r\n\r\n/**\r\n * Synchronously prefers a build-time manifest (avoids a render-blocking\r\n * fetch on SSG/SSR); otherwise kicks off the async runtime load. Until the\r\n * load resolves, `modules` is null and callers should treat features as\r\n * unknown (recommended: render the historical default, then re-render once\r\n * `modules` settles).\r\n */\r\nexport function useEzCoderManifest(): UseEzCoderManifestResult {\r\n // Lazy initializer — `loadFromBuildOutput` touches the filesystem on Node\r\n // and must NOT run on every render. Memoized once across the mount.\r\n const [manifest, setManifest] = useState<EzCoderManifest | null>(\r\n () => EzCoderManifest.loadFromBuildOutput(),\r\n );\r\n const [loading, setLoading] = useState<boolean>(manifest === null);\r\n\r\n useEffect(() => {\r\n // If we already have a build-time manifest, no need to fetch.\r\n if (manifest) return;\r\n\r\n let cancelled = false;\r\n EzCoderManifest.load()\r\n .then((m) => {\r\n if (cancelled) return;\r\n setManifest(m);\r\n setLoading(false);\r\n })\r\n .catch(() => {\r\n if (cancelled) return;\r\n // load() already handles errors and returns a fallback, but be\r\n // defensive: ensure we never leave the UI stuck in loading state.\r\n setLoading(false);\r\n });\r\n\r\n return () => {\r\n cancelled = true;\r\n };\r\n // We only want this effect to run on mount; once `manifest` settles,\r\n // re-running would re-fetch unnecessarily.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n return {\r\n manifest,\r\n modules: manifest ? manifest.getRequiredModules() : null,\r\n loading,\r\n source: manifest ? manifest.getSource() : null,\r\n };\r\n}\r\n","export { env, features, isFeatureConfigured, isNeonAuthConfigured } from './core/config';\r\nexport { loadBootstrap, getRuntimeConfig, configValue } from './core/bootstrap';\r\nexport type { RuntimeConfig } from './core/bootstrap';\r\nexport { supabase, isSupabaseConfigured } from './core/supabase';\r\nexport { ezcoder, ezcoderAuthIntegration } from './core/platform';\r\nexport { DatabaseProvider, useDatabase, DatabaseClient } from './database';\r\nexport type {\r\n AuthUser,\r\n UserProfile,\r\n SubscriptionTier,\r\n SubscriptionStatus,\r\n StorageResult,\r\n StorageFile,\r\n CheckoutOptions,\r\n EzcoderClient,\r\n AnalyticsResult,\r\n AuthIntegration,\r\n} from './core/types';\r\n\r\n// ─── Capability Manifest (B7) ────────────────────────────────────────────\r\n//\r\n// Manifest awareness is OPT-IN. The legacy module auto-registration is\r\n// preserved, so existing consumers see no behavioural change. New code can\r\n// import these to gate features:\r\n//\r\n// import { EzCoderManifest, useEzCoderManifest } from '@ezcoder.dev/sdk';\r\n// const { modules, loading } = useEzCoderManifest();\r\n// if (!loading && modules?.payments) renderCheckout();\r\n//\r\nexport { EzCoderManifest } from './manifest-consumer';\r\nexport type { RequiredModules, EnvRefs, SanitizedManifest } from './manifest-consumer';\r\nexport { useEzCoderManifest } from './useEzCoderManifest';\r\nexport type { UseEzCoderManifestResult } from './useEzCoderManifest';\r\n\r\n// Kick off an opportunistic load on import so the cache is warm by the\r\n// time components call useEzCoderManifest(). Errors are swallowed inside\r\n// load() and produce a fallback instance — never throws.\r\nimport { EzCoderManifest as _EzCoderManifestForBoot } from './manifest-consumer';\r\nif (typeof window !== 'undefined') {\r\n // Browser only. SSR/SSG paths that want the manifest should call\r\n // loadFromBuildOutput() or load() explicitly to control timing.\r\n void _EzCoderManifestForBoot.load();\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoEA,IAAM,gBAA8C,oBAAI,IAAI;AAC5D,IAAM,gBAAuD,oBAAI,IAAI;AAQrE,IAAM,6BAAwD,OAAO,OAAO;AAAA,EAC1E,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AAAA,EACV,eAAe;AACjB,CAAC;AAUM,IAAM,kBAAN,MAAM,iBAAgB;AAAA,EAI3B,YAAY,UAAoC,QAA0C;AACxF,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,OAAiC;AAC5C,UAAM,YAAY,IAAI;AACtB,UAAM,SAAS,IAAI;AACnB,UAAM,cAAc,gBAAgB;AAEpC,QAAI,CAAC,aAAa,CAAC,QAAQ;AACzB,aAAO,IAAI,iBAAgB,MAAM,UAAU;AAAA,IAC7C;AAEA,UAAM,WAAW,GAAG,SAAS,KAAK,MAAM;AACxC,UAAM,SAAS,cAAc,IAAI,QAAQ;AACzC,QAAI,OAAQ,QAAO;AAEnB,UAAM,WAAW,cAAc,IAAI,QAAQ;AAC3C,QAAI,SAAU,QAAO;AAErB,UAAM,cAAc,cAAc,QAAQ,WAAW,EAClD,KAAK,CAAC,MAAM;AACX,YAAM,WAAW,IAAI,iBAAgB,GAAG,SAAS;AACjD,oBAAc,IAAI,UAAU,QAAQ;AACpC,aAAO;AAAA,IACT,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,UAAI,OAAO,YAAY,aAAa;AAClC,gBAAQ,KAAK,wCAAwC,OAAO,qCAAqC;AAAA,MACnG;AACA,YAAM,WAAW,IAAI,iBAAgB,MAAM,UAAU;AACrD,oBAAc,IAAI,UAAU,QAAQ;AACpC,aAAO;AAAA,IACT,CAAC,EACA,QAAQ,MAAM;AACb,oBAAc,OAAO,QAAQ;AAAA,IAC/B,CAAC;AAEH,kBAAc,IAAI,UAAU,WAAW;AACvC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,OAAO,sBAA8C;AACnD,QAAI;AACF,UAAI,OAAO,YAAY,eAAe,CAAC,QAAQ,UAAU,MAAM;AAC7D,eAAO;AAAA,MACT;AAMA,YAAM,aAAa,IAAI;AAAA,QACrB;AAAA,MACF;AACA,YAAM,iBAAiB,WAAW;AAClC,UAAI,CAAC,eAAgB,QAAO;AAC5B,YAAM,KAAK,eAAe,IAAI;AAC9B,YAAM,OAAO,eAAe,MAAM;AAClC,YAAM,MAAM,QAAQ,IAAI;AACxB,YAAM,OAAO,KAAK,QAAQ,KAAK,gBAAgB,YAAY,eAAe;AAC1E,YAAM,MAAM,GAAG,aAAa,MAAM,MAAM;AACxC,YAAM,UAAmB,KAAK,MAAM,GAAG;AACvC,YAAM,SAAS,qBAAqB,OAAO;AAC3C,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,IAAI,iBAAgB,QAAQ,OAAO;AAAA,IAC5C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,qBAAsC;AACpC,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,EAAE,GAAG,2BAA2B;AAAA,IACzC;AACA,UAAM,KAAK,KAAK,SAAS;AACzB,WAAO;AAAA,MACL,MAAM,QAAQ,GAAG,IAAI;AAAA,MACrB,SAAS,QAAQ,GAAG,OAAO;AAAA,MAC3B,UAAU,QAAQ,GAAG,QAAQ;AAAA,MAC7B,OAAO,QAAQ,GAAG,KAAK;AAAA,MACvB,MAAM,QAAQ,GAAG,IAAI;AAAA,MACrB,UAAU,QAAQ,GAAG,QAAQ;AAAA;AAAA;AAAA,MAG7B,eAAe,QAAQ,GAAG,SAAS,GAAG,IAAI;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAsB;AACpB,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,IAClC;AACA,WAAO;AAAA,MACL,QAAQ,KAAK,SAAS,SAAS;AAAA,MAC/B,QAAQ,CAAC;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,QAAwC;AAC1C,WAAO,KAAK,mBAAmB,EAAE,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAA8C;AAC5C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,uBAA6B;AAClC,kBAAc,MAAM;AACpB,kBAAc,MAAM;AAAA,EACtB;AACF;AAIA,SAAS,kBAA0B;AAIjC,QAAM,WAAW,QAAQ,+BAA+B;AACxD,MAAI,SAAU,QAAO;AACrB,QAAM,WAAW,QAAQ,sCAAsC;AAC/D,MAAI,SAAU,QAAO;AACrB,SAAO,QAAQ,0BAA0B;AAC3C;AAEA,SAAS,QAAQ,KAAqB;AACpC,MAAI;AACF,QAAI,OAAO,gBAAgB,eAAe,YAAY,KAAK;AACzD,YAAM,IAAK,YAAY,IAAgC,GAAG;AAC1D,UAAI,OAAO,MAAM,SAAU,QAAO;AAAA,IACpC;AAAA,EACF,QAAQ;AAAA,EAER;AACA,MAAI;AACF,QAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,aAAO,QAAQ,IAAI,GAAG,KAAK;AAAA,IAC7B;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEA,eAAe,cAAc,QAAgB,aAAiD;AAC5F,QAAM,UAAkC;AAAA,IACtC,UAAU;AAAA,EACZ;AACA,MAAI,aAAa;AACf,YAAQ,wBAAwB,IAAI;AAAA,EACtC;AACA,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,qBAAqB;AAAA,IACpD,QAAQ;AAAA,IACR;AAAA;AAAA;AAAA,IAGA,OAAO;AAAA,EACT,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,QAAQ,IAAI,MAAM,EAAE;AAAA,EACtC;AACA,QAAM,OAAgB,MAAM,IAAI,KAAK;AACrC,QAAM,SAAS,qBAAqB,IAAI;AACxC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,SAAO;AACT;AASA,SAAS,qBAAqB,OAA0C;AACtE,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,QAAM,YAAY,MAAM;AACxB,QAAM,cAAc,MAAM;AAC1B,QAAM,UAAU,MAAM;AACtB,QAAM,QAAQ,MAAM;AACpB,MAAI,OAAO,cAAc,YAAY,UAAU,WAAW,EAAG,QAAO;AACpE,MAAI,CAAC,SAAS,WAAW,EAAG,QAAO;AACnC,MAAI,CAAC,SAAS,OAAO,KAAK,CAAC,MAAM,QAAQ,QAAQ,MAAM,EAAG,QAAO;AACjE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,QAAM,kBACJ,OAAO,MAAM,qBAAqB,WAAW,MAAM,mBAAmB;AACxE,SAAO;AAAA,IACL,kBAAkB;AAAA,IAClB,YAAY;AAAA,IACZ,sBAAsB;AAAA,IACtB,UAAU,EAAE,QAAQ,QAAQ,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,EAAE;AAAA,IACrF,kBAAkB,MAAM,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AAAA,EAC1E;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;AC1UA,SAAS,WAAW,gBAAgB;AAiB7B,SAAS,qBAA+C;AAG7D,QAAM,CAAC,UAAU,WAAW,IAAI;AAAA,IAC9B,MAAM,gBAAgB,oBAAoB;AAAA,EAC5C;AACA,QAAM,CAAC,SAAS,UAAU,IAAI,SAAkB,aAAa,IAAI;AAEjE,YAAU,MAAM;AAEd,QAAI,SAAU;AAEd,QAAI,YAAY;AAChB,oBAAgB,KAAK,EAClB,KAAK,CAAC,MAAM;AACX,UAAI,UAAW;AACf,kBAAY,CAAC;AACb,iBAAW,KAAK;AAAA,IAClB,CAAC,EACA,MAAM,MAAM;AACX,UAAI,UAAW;AAGf,iBAAW,KAAK;AAAA,IAClB,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EAIF,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL;AAAA,IACA,SAAS,WAAW,SAAS,mBAAmB,IAAI;AAAA,IACpD;AAAA,IACA,QAAQ,WAAW,SAAS,UAAU,IAAI;AAAA,EAC5C;AACF;;;AC9BA,IAAI,OAAO,WAAW,aAAa;AAGjC,OAAK,gBAAwB,KAAK;AACpC;","names":[]}
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  resolveServerToken
3
- } from "../chunk-CQKYANAW.js";
3
+ } from "../chunk-4WGGFJPE.js";
4
4
  import {
5
5
  AuthContext
6
- } from "../chunk-QHB7LGCA.js";
7
- import "../chunk-HJ2EIZ4S.js";
6
+ } from "../chunk-NTA3RMYR.js";
7
+ import "../chunk-DU6N3HVQ.js";
8
8
  import {
9
9
  supabase
10
- } from "../chunk-I2YGB7Z6.js";
10
+ } from "../chunk-KKTY5NCR.js";
11
11
  import {
12
12
  env,
13
13
  features
14
- } from "../chunk-LIUE7M7K.js";
14
+ } from "../chunk-X4JP7DCK.js";
15
15
 
16
16
  // src/notifications/useNotifications.ts
17
17
  import { useState, useEffect, useCallback, useContext } from "react";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  AuthContext
3
- } from "../chunk-QHB7LGCA.js";
3
+ } from "../chunk-NTA3RMYR.js";
4
4
  import {
5
5
  ezcoder
6
- } from "../chunk-HJ2EIZ4S.js";
6
+ } from "../chunk-DU6N3HVQ.js";
7
7
  import {
8
8
  supabase
9
- } from "../chunk-I2YGB7Z6.js";
9
+ } from "../chunk-KKTY5NCR.js";
10
10
  import {
11
11
  features
12
- } from "../chunk-LIUE7M7K.js";
12
+ } from "../chunk-X4JP7DCK.js";
13
13
 
14
14
  // src/payments/useSubscription.ts
15
15
  import { useState, useEffect, useCallback, useContext } from "react";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  AuthContext
3
- } from "../chunk-QHB7LGCA.js";
4
- import "../chunk-HJ2EIZ4S.js";
3
+ } from "../chunk-NTA3RMYR.js";
4
+ import "../chunk-DU6N3HVQ.js";
5
5
  import {
6
6
  supabase
7
- } from "../chunk-I2YGB7Z6.js";
7
+ } from "../chunk-KKTY5NCR.js";
8
8
  import {
9
9
  features
10
- } from "../chunk-LIUE7M7K.js";
10
+ } from "../chunk-X4JP7DCK.js";
11
11
 
12
12
  // src/roles/useRoles.ts
13
13
  import { useState, useEffect, useCallback, useContext } from "react";
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  AuthContext
3
- } from "../chunk-QHB7LGCA.js";
4
- import "../chunk-HJ2EIZ4S.js";
3
+ } from "../chunk-NTA3RMYR.js";
4
+ import "../chunk-DU6N3HVQ.js";
5
5
  import {
6
6
  supabase
7
- } from "../chunk-I2YGB7Z6.js";
7
+ } from "../chunk-KKTY5NCR.js";
8
8
  import {
9
9
  env,
10
10
  features
11
- } from "../chunk-LIUE7M7K.js";
11
+ } from "../chunk-X4JP7DCK.js";
12
12
 
13
13
  // src/storage/useStorage.ts
14
14
  import { useState, useCallback, useContext } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ezcoder.dev/sdk",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "EzCoder Platform SDK — auth, payments, storage, analytics, and more with zero configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -122,10 +122,13 @@
122
122
  "typecheck": "tsc --noEmit",
123
123
  "clean": "rm -rf dist"
124
124
  },
125
+ "dependencies": {
126
+ "better-auth": "^1.6.19"
127
+ },
125
128
  "peerDependencies": {
129
+ "@supabase/supabase-js": "^2.0.0",
126
130
  "react": "^18.0.0",
127
- "react-dom": "^18.0.0",
128
- "@supabase/supabase-js": "^2.0.0"
131
+ "react-dom": "^18.0.0"
129
132
  },
130
133
  "peerDependenciesMeta": {
131
134
  "framer-motion": {
@@ -139,10 +142,10 @@
139
142
  }
140
143
  },
141
144
  "devDependencies": {
142
- "tsup": "^8.0.0",
143
- "typescript": "^5.5.0",
144
145
  "@types/react": "^18.0.0",
145
- "@types/react-dom": "^18.0.0"
146
+ "@types/react-dom": "^18.0.0",
147
+ "tsup": "^8.0.0",
148
+ "typescript": "^5.5.0"
146
149
  },
147
150
  "license": "MIT"
148
151
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/config.ts"],"sourcesContent":["const getEnv = (viteKey: string, nextKey?: string): string => {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n return (import.meta.env[viteKey] as string) || '';\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n try {\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[nextKey || viteKey] || '';\r\n }\r\n } catch {\r\n // process not available\r\n }\r\n return '';\r\n};\r\n\r\nexport const env = {\r\n SUPABASE_URL: getEnv('VITE_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_URL'),\r\n SUPABASE_ANON_KEY: getEnv('VITE_SUPABASE_ANON_KEY', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'),\r\n EZCODER_AUTH_URL: getEnv('VITE_EZCODER_AUTH_URL', 'NEXT_PUBLIC_EZCODER_AUTH_URL'),\r\n EZCODER_AUTH_ANON_KEY: getEnv('VITE_EZCODER_AUTH_ANON_KEY', 'NEXT_PUBLIC_EZCODER_AUTH_ANON_KEY'),\r\n STRIPE_PUBLISHABLE_KEY: getEnv('VITE_STRIPE_PUBLISHABLE_KEY', 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY'),\r\n EZC_PROJECT_ID: getEnv('VITE_EZC_PROJECT_ID', 'NEXT_PUBLIC_EZC_PROJECT_ID'),\r\n EZCODER_API_URL: getEnv('VITE_EZCODER_API_URL', 'NEXT_PUBLIC_EZCODER_API_URL'),\r\n // Server-only secret for Stripe + authenticated platform API calls. Deployment\r\n // injects this as `EZCODER_SECRET_KEY` (see lib/deployment/fetch-project-env-vars.js);\r\n // resolve that name first, then fall back to the legacy `EZC_SECRET_KEY` so older\r\n // deploys keep working. Both are non-VITE/non-NEXT_PUBLIC (never exposed to the browser).\r\n EZC_SECRET_KEY: getEnv('EZCODER_SECRET_KEY', 'EZCODER_SECRET_KEY') || getEnv('EZC_SECRET_KEY', 'EZC_SECRET_KEY'),\r\n // B1: two-class project token model.\r\n //\r\n // EZC_PROJECT_TOKEN_PUBLIC — browser-safe. Read-only endpoints (manifest,\r\n // list, etc). Available in both server and\r\n // browser bundles via NEXT_PUBLIC_/VITE_\r\n // prefixes.\r\n // EZC_PROJECT_TOKEN_SERVER — server-only. Authenticates write APIs\r\n // (email send, cron register, notifications\r\n // send). NEVER prefixed with NEXT_PUBLIC_ or\r\n // VITE_; accessing it from a browser bundle\r\n // returns '' and the calling SDK function\r\n // must error out with a clear message.\r\n EZC_PROJECT_TOKEN_PUBLIC: getEnv('VITE_EZC_PROJECT_TOKEN_PUBLIC', 'NEXT_PUBLIC_EZC_PROJECT_TOKEN_PUBLIC'),\r\n EZC_PROJECT_TOKEN_SERVER: getEnv('EZC_PROJECT_TOKEN_SERVER', 'EZC_PROJECT_TOKEN_SERVER'),\r\n // Legacy single-class token. Deprecated, kept for backwards compat during\r\n // Phase 1 of the rollout. New code MUST use the split pair above.\r\n EZC_PROJECT_TOKEN_LEGACY: getEnv('VITE_EZC_PROJECT_TOKEN', 'NEXT_PUBLIC_EZC_PROJECT_TOKEN'),\r\n S3_BUCKET: getEnv('VITE_S3_BUCKET', 'S3_BUCKET'),\r\n CLOUDINARY_URL: getEnv('VITE_CLOUDINARY_URL', 'CLOUDINARY_URL'),\r\n STORAGE_BUCKET_PUBLIC: getEnv('VITE_STORAGE_BUCKET_PUBLIC', 'NEXT_PUBLIC_STORAGE_BUCKET_PUBLIC'),\r\n STORAGE_BUCKET_PRIVATE: getEnv('VITE_STORAGE_BUCKET_PRIVATE', 'NEXT_PUBLIC_STORAGE_BUCKET_PRIVATE'),\r\n};\r\n\r\nexport const features = {\r\n auth: Boolean(\r\n (env.SUPABASE_URL && env.SUPABASE_ANON_KEY) ||\r\n (env.EZCODER_AUTH_URL && env.EZCODER_AUTH_ANON_KEY)\r\n ),\r\n payments: Boolean(env.STRIPE_PUBLISHABLE_KEY),\r\n analytics: Boolean(env.EZCODER_API_URL && env.EZC_PROJECT_ID),\r\n storage: Boolean(env.SUPABASE_URL || env.EZCODER_AUTH_URL || env.S3_BUCKET || env.CLOUDINARY_URL),\r\n database: Boolean(\r\n (env.EZCODER_AUTH_URL || env.SUPABASE_URL) && env.EZC_PROJECT_ID\r\n ),\r\n};\r\n\r\nexport function isFeatureConfigured(feature: keyof typeof features): boolean {\r\n return features[feature];\r\n}\r\n"],"mappings":";AAAA,IAAM,SAAS,CAAC,SAAiB,YAA6B;AAC5D,MAAI;AACF,QAAI,OAAO,gBAAgB,eAAe,YAAY,KAAK;AACzD,aAAQ,YAAY,IAAI,OAAO,KAAgB;AAAA,IACjD;AAAA,EACF,QAAQ;AAAA,EAER;AACA,MAAI;AACF,QAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,aAAO,QAAQ,IAAI,WAAW,OAAO,KAAK;AAAA,IAC5C;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEO,IAAM,MAAM;AAAA,EACjB,cAAc,OAAO,qBAAqB,0BAA0B;AAAA,EACpE,mBAAmB,OAAO,0BAA0B,+BAA+B;AAAA,EACnF,kBAAkB,OAAO,yBAAyB,8BAA8B;AAAA,EAChF,uBAAuB,OAAO,8BAA8B,mCAAmC;AAAA,EAC/F,wBAAwB,OAAO,+BAA+B,oCAAoC;AAAA,EAClG,gBAAgB,OAAO,uBAAuB,4BAA4B;AAAA,EAC1E,iBAAiB,OAAO,wBAAwB,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7E,gBAAgB,OAAO,sBAAsB,oBAAoB,KAAK,OAAO,kBAAkB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAa/G,0BAA0B,OAAO,iCAAiC,sCAAsC;AAAA,EACxG,0BAA0B,OAAO,4BAA4B,0BAA0B;AAAA;AAAA;AAAA,EAGvF,0BAA0B,OAAO,0BAA0B,+BAA+B;AAAA,EAC1F,WAAW,OAAO,kBAAkB,WAAW;AAAA,EAC/C,gBAAgB,OAAO,uBAAuB,gBAAgB;AAAA,EAC9D,uBAAuB,OAAO,8BAA8B,mCAAmC;AAAA,EAC/F,wBAAwB,OAAO,+BAA+B,oCAAoC;AACpG;AAEO,IAAM,WAAW;AAAA,EACtB,MAAM;AAAA,IACH,IAAI,gBAAgB,IAAI,qBACxB,IAAI,oBAAoB,IAAI;AAAA,EAC/B;AAAA,EACA,UAAU,QAAQ,IAAI,sBAAsB;AAAA,EAC5C,WAAW,QAAQ,IAAI,mBAAmB,IAAI,cAAc;AAAA,EAC5D,SAAS,QAAQ,IAAI,gBAAgB,IAAI,oBAAoB,IAAI,aAAa,IAAI,cAAc;AAAA,EAChG,UAAU;AAAA,KACP,IAAI,oBAAoB,IAAI,iBAAiB,IAAI;AAAA,EACpD;AACF;AAEO,SAAS,oBAAoB,SAAyC;AAC3E,SAAO,SAAS,OAAO;AACzB;","names":[]}