@pol-studios/db 1.0.8 → 1.0.10
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.
- package/dist/auth/context.js +21 -12786
- package/dist/auth/context.js.map +1 -1
- package/dist/auth/guards.js +12 -7640
- package/dist/auth/guards.js.map +1 -1
- package/dist/auth/hooks.js +25 -10591
- package/dist/auth/hooks.js.map +1 -1
- package/dist/auth/index.js +43 -13008
- package/dist/auth/index.js.map +1 -1
- package/dist/canvas-75Y7XMF3.js +1541 -0
- package/dist/canvas-75Y7XMF3.js.map +1 -0
- package/dist/chunk-2IFGILT3.js +532 -0
- package/dist/chunk-2IFGILT3.js.map +1 -0
- package/dist/chunk-3M2U6TXH.js +928 -0
- package/dist/chunk-3M2U6TXH.js.map +1 -0
- package/dist/chunk-3PJTNH2L.js +2778 -0
- package/dist/chunk-3PJTNH2L.js.map +1 -0
- package/dist/chunk-5ZYAEGCJ.js +416 -0
- package/dist/chunk-5ZYAEGCJ.js.map +1 -0
- package/dist/chunk-7HG6G25H.js +710 -0
- package/dist/chunk-7HG6G25H.js.map +1 -0
- package/dist/chunk-7XT7K4QT.js +2687 -0
- package/dist/chunk-7XT7K4QT.js.map +1 -0
- package/dist/chunk-AWFMICFV.js +158 -0
- package/dist/chunk-AWFMICFV.js.map +1 -0
- package/dist/chunk-BRTW7CO5.js +1467 -0
- package/dist/chunk-BRTW7CO5.js.map +1 -0
- package/dist/chunk-EL45Z26M.js +4194 -0
- package/dist/chunk-EL45Z26M.js.map +1 -0
- package/dist/chunk-ERGF2FCE.js +903 -0
- package/dist/chunk-ERGF2FCE.js.map +1 -0
- package/dist/chunk-GK7B66LY.js +135 -0
- package/dist/chunk-GK7B66LY.js.map +1 -0
- package/dist/chunk-GQI6WJGI.js +172 -0
- package/dist/chunk-GQI6WJGI.js.map +1 -0
- package/dist/chunk-H6365JPC.js +1858 -0
- package/dist/chunk-H6365JPC.js.map +1 -0
- package/dist/chunk-J4ZVCXZ4.js +1 -0
- package/dist/chunk-J4ZVCXZ4.js.map +1 -0
- package/dist/chunk-JUVE3DWY.js +433 -0
- package/dist/chunk-JUVE3DWY.js.map +1 -0
- package/dist/chunk-O3K7R32P.js +7555 -0
- package/dist/chunk-O3K7R32P.js.map +1 -0
- package/dist/chunk-P4UZ7IXC.js +42 -0
- package/dist/chunk-P4UZ7IXC.js.map +1 -0
- package/dist/chunk-SEY5UO2T.js +89 -0
- package/dist/chunk-SEY5UO2T.js.map +1 -0
- package/dist/chunk-USJYMRUO.js +86 -0
- package/dist/chunk-USJYMRUO.js.map +1 -0
- package/dist/chunk-XX3IWSPM.js +189 -0
- package/dist/chunk-XX3IWSPM.js.map +1 -0
- package/dist/chunk-Y3INY2CS.js +14 -0
- package/dist/chunk-Y3INY2CS.js.map +1 -0
- package/dist/chunk-ZTSBF536.js +1927 -0
- package/dist/chunk-ZTSBF536.js.map +1 -0
- package/dist/client/index.js +13 -141
- package/dist/client/index.js.map +1 -1
- package/dist/dist-NDNRSNOG.js +521 -0
- package/dist/dist-NDNRSNOG.js.map +1 -0
- package/dist/gen/index.js +186 -1280
- package/dist/gen/index.js.map +1 -1
- package/dist/hooks/index.js +21 -8694
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.js +403 -47848
- package/dist/index.js.map +1 -1
- package/dist/index.native.js +400 -25048
- package/dist/index.native.js.map +1 -1
- package/dist/index.web.js +576 -43769
- package/dist/index.web.js.map +1 -1
- package/dist/mutation/index.js +44 -4675
- package/dist/mutation/index.js.map +1 -1
- package/dist/parser/index.js +45 -3697
- package/dist/parser/index.js.map +1 -1
- package/dist/pdf-3TIGQRLA.js +20336 -0
- package/dist/pdf-3TIGQRLA.js.map +1 -0
- package/dist/query/index.js +31 -13175
- package/dist/query/index.js.map +1 -1
- package/dist/realtime/index.js +45 -12431
- package/dist/realtime/index.js.map +1 -1
- package/dist/types/index.js +9 -0
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/context/setupAuthContext.tsx","../src/auth/context/PermissionContext.tsx","../src/auth/context/AuthProvider.tsx","../src/auth/context/UserMetadataContext.tsx"],"sourcesContent":["/**\n * SetupAuthContext - Shared context for auth state\n *\n * This file is separate to avoid circular dependencies between\n * AuthProvider and PermissionContext.\n */\n\nimport { createContext, ReactNode } from \"react\";\nimport {\n AuthTokenResponsePassword,\n SignUpWithPasswordCredentials,\n User,\n} from \"@supabase/supabase-js\";\n\nexport type ProfileStatus = \"active\" | \"archived\" | \"suspended\";\n\nexport interface SetupAuthContext {\n user?: User | null | undefined;\n isLoading: boolean;\n profile: Profile | null | undefined;\n access: string[];\n /** Whether the profile is archived */\n isArchived: boolean;\n /** Whether the profile is suspended */\n isSuspended: boolean;\n /** The profile status (active, archived, suspended) */\n profileStatus: ProfileStatus | undefined;\n registerAsync: (register: SignUpWithPasswordCredentials) => Promise<any>;\n signInAsync: (\n username: string,\n password: string,\n ) => Promise<AuthTokenResponsePassword>;\n signOutAsync: () => Promise<any>;\n refreshAsync: () => Promise<void>;\n onSignOut: (action: () => any) => string;\n removeOnSignOut: (id: string) => any;\n hasAccess?: (key: string) => boolean;\n}\n\n// Profile type - simplified version, the full type is in AuthProvider\nexport interface Profile {\n id: string;\n email?: string;\n fullName?: string;\n status?: ProfileStatus;\n UserAccess?: Array<{ accessKey: string }>;\n [key: string]: any;\n}\n\nexport const setupAuthContext = createContext({} as SetupAuthContext);\n\n/**\n * Props for SetupAuthContextProvider\n * A simpler provider that takes pre-computed auth state\n */\nexport interface SetupAuthContextProviderProps {\n children: ReactNode;\n auth: SetupAuthContext;\n}\n\n/**\n * Simple provider that takes auth state and provides it via context.\n * Use this when you already have the auth state and just need to provide it to children.\n *\n * @example\n * ```tsx\n * const auth = useSetupAuth();\n * return (\n * <SetupAuthContextProvider auth={auth}>\n * <MyComponent />\n * </SetupAuthContextProvider>\n * );\n * ```\n */\nexport function SetupAuthContextProvider({\n children,\n auth,\n}: SetupAuthContextProviderProps) {\n return (\n <setupAuthContext.Provider value={auth}>\n {children}\n </setupAuthContext.Provider>\n );\n}\n","import {\n createContext,\n ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport useSupabase from \"../../useSupabase\";\nimport { setupAuthContext } from \"./setupAuthContext\";\nimport {\n EntityType,\n EntityPermissionLevel,\n EntityAction,\n EntityPermissionCheck,\n} from \"../types/EntityPermissions\";\n\n// Cache entry with TTL\ninterface CacheEntry {\n permission: EntityPermissionCheck;\n expiresAt: number;\n}\n\n// Entity identifier for batch lookups\ninterface EntityIdentifier {\n entityType: EntityType;\n entityId: number;\n}\n\n// Cache key helper\nfunction getCacheKey(\n userId: string | undefined,\n entityType: EntityType,\n entityId: number,\n): string {\n return `${userId || \"anon\"}:${entityType}:${entityId}`;\n}\n\n// Default loading state\nconst loadingPermission: EntityPermissionCheck = {\n canView: false,\n canAdminView: false,\n canEdit: false,\n canCreate: false,\n canDelete: false,\n canShare: false,\n permissionLevel: null,\n isLoading: true,\n};\n\n// No permission state\nconst noPermission: EntityPermissionCheck = {\n canView: false,\n canAdminView: false,\n canEdit: false,\n canCreate: false,\n canDelete: false,\n canShare: false,\n permissionLevel: null,\n isLoading: false,\n isDenied: false,\n};\n\n// Denied permission state - explicitly blocked access\nconst deniedPermission: EntityPermissionCheck = {\n canView: false,\n canAdminView: false,\n canEdit: false,\n canCreate: false,\n canDelete: false,\n canShare: false,\n permissionLevel: null,\n isLoading: false,\n isDenied: true,\n};\n\n// Map permission level to permission check\n// If permission is 'denied', it means access was explicitly blocked\n// Handles both legacy formats (ReadOnly, AdminReadOnly, ReadWrite, Admin)\n// and new formats (view, edit, admin, denied)\nfunction mapPermissionLevel(\n level: EntityPermissionLevel | \"denied\" | string | null,\n): EntityPermissionCheck {\n if (!level) {\n return noPermission;\n }\n\n // Normalize to lowercase for comparison to handle both legacy and new formats\n const normalizedLevel = level.toLowerCase();\n\n switch (normalizedLevel) {\n // Legacy format: ReadOnly, New format: view\n case \"readonly\":\n case \"view\":\n return {\n canView: true,\n canAdminView: false,\n canEdit: false,\n canCreate: false,\n canDelete: false,\n canShare: false,\n permissionLevel: \"ReadOnly\",\n isLoading: false,\n isDenied: false,\n };\n // Legacy format: AdminReadOnly (no new equivalent, keep for backwards compatibility)\n case \"adminreadonly\":\n return {\n canView: true,\n canAdminView: true,\n canEdit: false,\n canCreate: false,\n canDelete: false,\n canShare: false,\n permissionLevel: \"AdminReadOnly\",\n isLoading: false,\n isDenied: false,\n };\n // Legacy format: ReadWrite, New format: edit\n case \"readwrite\":\n case \"edit\":\n return {\n canView: true,\n canAdminView: false,\n canEdit: true,\n canCreate: true,\n canDelete: false,\n canShare: false,\n permissionLevel: \"ReadWrite\",\n isLoading: false,\n isDenied: false,\n };\n // Legacy format: Admin, New format: admin\n case \"admin\":\n return {\n canView: true,\n canAdminView: true,\n canEdit: true,\n canCreate: true,\n canDelete: true,\n canShare: true,\n permissionLevel: \"Admin\",\n isLoading: false,\n isDenied: false,\n };\n // New format: denied - explicit access denial\n case \"denied\":\n return deniedPermission;\n default:\n console.warn(`Unknown permission level: ${level}`);\n return noPermission;\n }\n}\n\n// Context interface\nexport interface PermissionContextValue {\n getPermission: (\n entityType: EntityType,\n entityId: number,\n ) => EntityPermissionCheck;\n checkPermission: (\n entityType: EntityType,\n entityId: number,\n action: EntityAction,\n ) => boolean;\n prefetchPermissions: (entities: EntityIdentifier[]) => Promise<void>;\n invalidatePermission: (entityType: EntityType, entityId: number) => void;\n isLoading: boolean;\n}\n\n/**\n * @deprecated Use permissionContext instead\n */\nexport interface EntityPermissionContextValue extends PermissionContextValue {}\n\nexport const permissionContext = createContext<PermissionContextValue>(\n {} as PermissionContextValue,\n);\n\n/**\n * @deprecated Use permissionContext instead\n */\nexport const entityPermissionContext = permissionContext;\n\n// TTL for cache entries (5 minutes)\nconst CACHE_TTL_MS = 5 * 60 * 1000;\n\n// TTL for error cache entries (30 seconds) - shorter to allow quick retry\nconst ERROR_CACHE_TTL_MS = 30 * 1000;\n\n// Batch collection delay (50ms)\nconst BATCH_DELAY_MS = 50;\n\nexport function PermissionProvider({ children }: { children: ReactNode }) {\n const supabase = useSupabase();\n const setupAuth = useContext(setupAuthContext);\n const user = setupAuth?.user;\n\n // Permission cache\n const cacheRef = useRef<Map<string, CacheEntry>>(new Map());\n\n // Pending lookups for batching\n const pendingLookupsRef = useRef<Set<string>>(new Set());\n\n // In-flight lookups to prevent duplicate requests during async RPC calls\n const inFlightRef = useRef<Set<string>>(new Set());\n\n // Batch timer ref\n const batchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Loading state\n const [isLoading, setIsLoading] = useState(false);\n\n // Force re-render trigger\n const [, forceUpdate] = useState(0);\n\n // Clean up expired cache entries\n const cleanupExpiredEntries = useCallback(() => {\n const now = Date.now();\n const cache = cacheRef.current;\n let hasExpired = false;\n\n for (const [key, entry] of cache.entries()) {\n if (entry.expiresAt < now) {\n cache.delete(key);\n hasExpired = true;\n }\n }\n\n if (hasExpired) {\n forceUpdate((prev) => prev + 1);\n }\n }, []);\n\n // Periodic cleanup of expired entries\n useEffect(() => {\n const cleanupInterval = setInterval(cleanupExpiredEntries, 60 * 1000);\n return () => clearInterval(cleanupInterval);\n }, [cleanupExpiredEntries]);\n\n // Execute batch lookup\n const executeBatchLookup = useCallback(async () => {\n const pending = Array.from(pendingLookupsRef.current);\n pendingLookupsRef.current.clear();\n\n if (pending.length === 0 || !user?.id) {\n return;\n }\n\n // Move pending keys to in-flight to prevent duplicate requests\n pending.forEach((k) => inFlightRef.current.add(k));\n\n setIsLoading(true);\n\n try {\n // Parse pending keys back to entities (format: userId:entityType:entityId)\n const entities = pending.map((key) => {\n const parts = key.split(\":\");\n // Skip the userId part (index 0), use entityType (index 1) and entityId (index 2)\n const entityType = parts[1];\n const entityIdStr = parts[2];\n return {\n entity_type: entityType as EntityType,\n entity_id: parseInt(entityIdStr, 10),\n };\n });\n\n // Call RPC for batch lookup\n // Using 'as any' because the RPC function type isn't in the generated database types\n const { data, error } = await (supabase.rpc as any)(\n \"get_user_entity_permissions\",\n {\n p_user_id: user.id,\n p_entities: entities,\n },\n );\n\n if (error) {\n console.error(\"Failed to fetch entity permissions:\", error);\n // Mark all pending as no permission with shorter TTL to allow quick retry\n const cache = cacheRef.current;\n const now = Date.now();\n for (const key of pending) {\n cache.set(key, {\n permission: noPermission,\n expiresAt: now + ERROR_CACHE_TTL_MS,\n });\n }\n } else if (data) {\n // Update cache with results\n const cache = cacheRef.current;\n const now = Date.now();\n\n // Create a map of results for quick lookup\n // Permission can be a standard level or 'denied' for explicit deny\n const resultsMap = new Map<string, EntityPermissionLevel | \"denied\">();\n // Type the data as an array of permission results\n const results = data as Array<{\n entity_type: EntityType;\n entity_id: number;\n permission: EntityPermissionLevel | \"denied\";\n }>;\n for (const result of results) {\n const key = getCacheKey(\n user?.id,\n result.entity_type,\n result.entity_id,\n );\n resultsMap.set(key, result.permission);\n }\n\n // Update cache for all pending keys\n for (const key of pending) {\n const permissionLevel = resultsMap.get(key) || null;\n cache.set(key, {\n permission: mapPermissionLevel(permissionLevel),\n expiresAt: now + CACHE_TTL_MS,\n });\n }\n }\n\n forceUpdate((prev) => prev + 1);\n } catch (err) {\n console.error(\"Unexpected error fetching entity permissions:\", err);\n } finally {\n // Remove from in-flight after completion (success or error)\n pending.forEach((k) => inFlightRef.current.delete(k));\n setIsLoading(false);\n }\n }, [supabase, user?.id]);\n\n // Schedule batch lookup\n const scheduleBatchLookup = useCallback(() => {\n if (batchTimerRef.current) {\n clearTimeout(batchTimerRef.current);\n }\n\n batchTimerRef.current = setTimeout(() => {\n batchTimerRef.current = null;\n executeBatchLookup();\n }, BATCH_DELAY_MS);\n }, [executeBatchLookup]);\n\n // Get permission for an entity\n const getPermission = useCallback(\n (entityType: EntityType, entityId: number): EntityPermissionCheck => {\n const key = getCacheKey(user?.id, entityType, entityId);\n const cache = cacheRef.current;\n const cached = cache.get(key);\n const now = Date.now();\n\n // Return cached if valid\n if (cached && cached.expiresAt > now) {\n return cached.permission;\n }\n\n // Don't add if already pending or in-flight to prevent duplicate requests\n if (\n !pendingLookupsRef.current.has(key) &&\n !inFlightRef.current.has(key)\n ) {\n pendingLookupsRef.current.add(key);\n scheduleBatchLookup();\n }\n\n return loadingPermission;\n },\n [scheduleBatchLookup, user?.id],\n );\n\n // Check specific permission action\n const checkPermission = useCallback(\n (\n entityType: EntityType,\n entityId: number,\n action: EntityAction,\n ): boolean => {\n const permission = getPermission(entityType, entityId);\n\n if (permission.isLoading) {\n return false;\n }\n\n switch (action) {\n case \"view\":\n return permission.canView;\n case \"adminView\":\n return permission.canAdminView;\n case \"edit\":\n return permission.canEdit;\n case \"create\":\n return permission.canCreate;\n case \"delete\":\n return permission.canDelete;\n case \"share\":\n return permission.canShare;\n default:\n return false;\n }\n },\n [getPermission],\n );\n\n // Prefetch permissions for multiple entities\n const prefetchPermissions = useCallback(\n async (entities: EntityIdentifier[]): Promise<void> => {\n if (!user?.id || entities.length === 0) {\n return;\n }\n\n const cache = cacheRef.current;\n const now = Date.now();\n\n // Filter out already cached entries, items already pending, and items in-flight\n const toFetch = entities.filter((entity) => {\n const key = getCacheKey(user?.id, entity.entityType, entity.entityId);\n const cached = cache.get(key);\n const isPending = pendingLookupsRef.current.has(key);\n const isInFlight = inFlightRef.current.has(key);\n return (\n !isPending && !isInFlight && (!cached || cached.expiresAt <= now)\n );\n });\n\n if (toFetch.length === 0) {\n return;\n }\n\n setIsLoading(true);\n\n try {\n const entitiesParam = toFetch.map((e) => ({\n entity_type: e.entityType,\n entity_id: e.entityId,\n }));\n\n // Using 'as any' because the RPC function type isn't in the generated database types\n const { data, error } = await (supabase.rpc as any)(\n \"get_user_entity_permissions\",\n {\n p_user_id: user.id,\n p_entities: entitiesParam,\n },\n );\n\n if (error) {\n console.error(\"Failed to prefetch entity permissions:\", error);\n return;\n }\n\n if (data) {\n // Capture timestamp AFTER the RPC call completes\n const cacheTimestamp = Date.now();\n\n // Permission can be a standard level or 'denied' for explicit deny\n const resultsMap = new Map<\n string,\n EntityPermissionLevel | \"denied\"\n >();\n // Type the data as an array of permission results\n const results = data as Array<{\n entity_type: EntityType;\n entity_id: number;\n permission: EntityPermissionLevel | \"denied\";\n }>;\n for (const result of results) {\n const key = getCacheKey(\n user?.id,\n result.entity_type,\n result.entity_id,\n );\n resultsMap.set(key, result.permission);\n }\n\n for (const entity of toFetch) {\n const key = getCacheKey(\n user?.id,\n entity.entityType,\n entity.entityId,\n );\n const permissionLevel = resultsMap.get(key) || null;\n cache.set(key, {\n permission: mapPermissionLevel(permissionLevel),\n expiresAt: cacheTimestamp + CACHE_TTL_MS,\n });\n }\n\n forceUpdate((prev) => prev + 1);\n }\n } catch (err) {\n console.error(\"Unexpected error prefetching entity permissions:\", err);\n } finally {\n setIsLoading(false);\n }\n },\n [supabase, user?.id],\n );\n\n // Invalidate a specific permission\n const invalidatePermission = useCallback(\n (entityType: EntityType, entityId: number): void => {\n const key = getCacheKey(user?.id, entityType, entityId);\n cacheRef.current.delete(key);\n forceUpdate((prev) => prev + 1);\n },\n [user?.id],\n );\n\n // Parse scoped access key format: <entity_type>:<entity_id>:<permission_level>\n // Returns null if the key format is invalid\n const parseScopedAccessKey = useCallback(\n (key: string): { entityType: EntityType; entityId: number } | null => {\n if (!key || typeof key !== \"string\") {\n return null;\n }\n const parts = key.split(\":\");\n if (parts.length < 2) {\n return null;\n }\n const entityType = parts[0];\n const entityId = parseInt(parts[1], 10);\n if (isNaN(entityId)) {\n return null;\n }\n // Use explicit mapping instead of string manipulation\n // This correctly handles \"database\" -> \"ProjectDatabase\" case\n const entityTypeMap: Record<string, EntityType> = {\n client: \"Client\",\n project: \"Project\",\n database: \"ProjectDatabase\",\n projectdatabase: \"ProjectDatabase\",\n };\n const normalizedEntityType = entityTypeMap[entityType.toLowerCase()];\n if (!normalizedEntityType) {\n return null;\n }\n return { entityType: normalizedEntityType, entityId };\n },\n [],\n );\n\n // Real-time subscription for permission changes\n useEffect(() => {\n if (!user?.id) {\n return;\n }\n\n // Subscribe to changes on UserAccess table for the current user\n // Use unique channel name per user to avoid collisions\n const channel = supabase\n .channel(`entity-permissions-${user.id}`)\n .on(\n \"postgres_changes\",\n {\n event: \"*\",\n schema: \"core\",\n table: \"UserAccess\",\n filter: `userId=eq.${user.id}`,\n },\n (payload) => {\n // Parse the scoped access key from the new record\n if (\n payload.new &&\n typeof payload.new === \"object\" &&\n \"scopedAccessKey\" in payload.new &&\n typeof payload.new.scopedAccessKey === \"string\"\n ) {\n const parsed = parseScopedAccessKey(payload.new.scopedAccessKey);\n if (parsed) {\n invalidatePermission(parsed.entityType, parsed.entityId);\n }\n }\n // Parse the scoped access key from the old record (for deletes/updates)\n if (\n payload.old &&\n typeof payload.old === \"object\" &&\n \"scopedAccessKey\" in payload.old &&\n typeof payload.old.scopedAccessKey === \"string\"\n ) {\n const parsed = parseScopedAccessKey(payload.old.scopedAccessKey);\n if (parsed) {\n invalidatePermission(parsed.entityType, parsed.entityId);\n }\n }\n },\n )\n .subscribe();\n\n return () => {\n channel.unsubscribe();\n supabase.removeChannel(channel);\n };\n }, [supabase, user?.id, invalidatePermission, parseScopedAccessKey]);\n\n // Clear cache on user change\n useEffect(() => {\n cacheRef.current.clear();\n pendingLookupsRef.current.clear();\n inFlightRef.current.clear();\n if (batchTimerRef.current) {\n clearTimeout(batchTimerRef.current);\n batchTimerRef.current = null;\n }\n forceUpdate((prev) => prev + 1);\n }, [user?.id]);\n\n // Cleanup batch timer on unmount\n useEffect(() => {\n return () => {\n if (batchTimerRef.current) {\n clearTimeout(batchTimerRef.current);\n }\n };\n }, []);\n\n const value = useMemo(\n () => ({\n getPermission,\n checkPermission,\n prefetchPermissions,\n invalidatePermission,\n isLoading,\n }),\n [\n getPermission,\n checkPermission,\n prefetchPermissions,\n invalidatePermission,\n isLoading,\n ],\n );\n\n return (\n <permissionContext.Provider value={value}>\n {children}\n </permissionContext.Provider>\n );\n}\n\nexport function usePermissions(): PermissionContextValue {\n const context = useContext(permissionContext);\n if (!context || Object.keys(context).length === 0) {\n throw new Error(\"usePermissions must be used within a PermissionProvider\");\n }\n return context;\n}\n","import { useDbQuery as useQuery } from \"../../useDbQuery\";\nimport useSupabase, { typedSupabase } from \"../../useSupabase\";\nimport { isUsable, newUuid } from \"@pol-studios/utils\";\nimport {\n QueryData,\n SignUpWithPasswordCredentials,\n User,\n} from \"@supabase/supabase-js\";\nimport {\n ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { PermissionProvider } from \"./PermissionContext\";\n// Import from shared context to use internally\nimport {\n setupAuthContext,\n type ProfileStatus,\n} from \"./setupAuthContext\";\n// Re-export from shared context to maintain API compatibility\nexport {\n setupAuthContext,\n SetupAuthContextProvider,\n type SetupAuthContext,\n type SetupAuthContextProviderProps,\n type ProfileStatus,\n} from \"./setupAuthContext\";\n\nconst profileQuery = typedSupabase\n ?.schema(\"core\")\n .from(\"Profile\")\n .select(\"*, UserAccess(accessKey), status\")\n .single();\n\nexport type Profile = QueryData<typeof profileQuery>;\n\nexport interface AuthProviderProps {\n children: ReactNode;\n /**\n * Enable entity-level permissions system (Project, Client, ProjectDatabase access control).\n * When enabled, wraps children with PermissionProvider.\n * @default false\n */\n enableEntityPermissions?: boolean;\n}\n\nexport function AuthProvider({\n children,\n enableEntityPermissions = false,\n}: AuthProviderProps) {\n const supabase = useSupabase();\n\n const [currentUser, setCurrentUser] = useState<User | null | undefined>(\n undefined,\n );\n\n const [userNeedsChange, setUserNeedsChange] = useState(true);\n const [onSignOutCallbacks, setOnSignOutCallbacks] = useState(\n new Map<string, () => any>(),\n );\n\n async function registerAsync(register: SignUpWithPasswordCredentials) {\n const response = await supabase.auth.signUp(register);\n setCurrentUser(response.data.user);\n return response;\n }\n async function signInAsync(username: string, password: string) {\n const response = await supabase.auth.signInWithPassword({\n email: username,\n password,\n });\n\n if (response.data) {\n setCurrentUser(response.data.user);\n }\n return response;\n }\n async function signOutAsync() {\n const response = await supabase.auth.signOut();\n if (isUsable(response.error) === false) {\n Array.from(onSignOutCallbacks.values()).forEach((x) => {\n x();\n });\n }\n return response;\n }\n\n function onSignOut(action: () => any) {\n const id = newUuid();\n setOnSignOutCallbacks((x) => new Map(x).set(id, action));\n return id;\n }\n\n function removeOnSignOut(id: string) {\n setOnSignOutCallbacks((x) => {\n const map = new Map(x);\n map.delete(id);\n return map;\n });\n }\n\n async function refreshAsync() {}\n\n useEffect(() => {\n const request = supabase.auth.onAuthStateChange((event) => {\n if (event === \"SIGNED_IN\" || event === \"SIGNED_OUT\") {\n setUserNeedsChange(true);\n }\n });\n\n return () => {\n request.data.subscription.unsubscribe();\n };\n }, [supabase.auth]);\n\n useEffect(() => {\n if (userNeedsChange === false) return;\n supabase.auth.getSession().then((x) => {\n setCurrentUser(x?.data?.session?.user ?? null);\n setUserNeedsChange(false);\n });\n }, [userNeedsChange]);\n\n const profileRequest = useQuery(\n supabase\n .schema(\"core\")\n .from(\"Profile\")\n .select(\"*, UserAccess(accessKey), status\")\n .eq(\"id\", currentUser?.id!)\n .limit(1)\n .maybeSingle(),\n {\n enabled: isUsable(currentUser),\n crossOrganization: true,\n },\n );\n\n // Fetch all access keys (direct + group-based) using the database function\n const accessKeysRequest = useQuery(\n supabase.schema(\"core\").rpc(\"get_user_access_keys\", {\n user_id: currentUser?.id!,\n }),\n {\n enabled: isUsable(currentUser),\n crossOrganization: true,\n },\n );\n\n // Stable refetch callback for real-time subscriptions\n const refetchAccessKeys = useCallback(() => {\n accessKeysRequest.refetch();\n }, [accessKeysRequest.refetch]);\n\n // Track user's group IDs for filtering GroupAccessKey changes\n const userGroupIdsRef = useRef<Set<number>>(new Set());\n\n // Update group IDs when access keys change\n useEffect(() => {\n if (accessKeysRequest.data) {\n const groupIds = new Set<number>();\n for (const item of accessKeysRequest.data) {\n if (item.source === \"group\" && item.source_id) {\n groupIds.add(item.source_id);\n }\n }\n userGroupIdsRef.current = groupIds;\n }\n }, [accessKeysRequest.data]);\n\n // Real-time subscription for access key changes (direct + group-based)\n useEffect(() => {\n if (!currentUser?.id) return;\n\n const channel = supabase\n .channel(`user-access-keys-${currentUser.id}`)\n // Direct access changes\n .on(\n \"postgres_changes\",\n {\n event: \"*\",\n schema: \"core\",\n table: \"UserAccess\",\n filter: `userId=eq.${currentUser.id}`,\n },\n () => {\n refetchAccessKeys();\n },\n )\n // Group membership changes for this user\n .on(\n \"postgres_changes\",\n {\n event: \"*\",\n schema: \"core\",\n table: \"UserGroup\",\n filter: `userId=eq.${currentUser.id}`,\n },\n () => {\n refetchAccessKeys();\n },\n )\n // Group access key changes (check if user is in affected group)\n .on(\n \"postgres_changes\",\n {\n event: \"*\",\n schema: \"core\",\n table: \"GroupAccessKey\",\n },\n (payload) => {\n const groupId =\n (payload.new as { groupId?: number })?.groupId ||\n (payload.old as { groupId?: number })?.groupId;\n if (groupId && userGroupIdsRef.current.has(groupId)) {\n refetchAccessKeys();\n }\n },\n )\n // Group activation/deactivation\n .on(\n \"postgres_changes\",\n {\n event: \"UPDATE\",\n schema: \"core\",\n table: \"Group\",\n },\n (payload) => {\n const oldActive = (payload.old as { isActive?: boolean })?.isActive;\n const newActive = (payload.new as { isActive?: boolean })?.isActive;\n const groupId = (payload.new as { id?: number })?.id;\n // Refetch if activation status changed and user is in this group\n if (\n oldActive !== newActive &&\n groupId &&\n userGroupIdsRef.current.has(groupId)\n ) {\n refetchAccessKeys();\n }\n },\n )\n .subscribe();\n\n return () => {\n channel.unsubscribe();\n supabase.removeChannel(channel);\n };\n }, [supabase, currentUser?.id, refetchAccessKeys]);\n\n // Real-time subscription for profile status changes (force signout if archived/suspended)\n useEffect(() => {\n if (!currentUser?.id) return;\n\n const profileChannel = supabase\n .channel(`profile-status-${currentUser.id}`)\n .on(\n \"postgres_changes\",\n {\n event: \"UPDATE\",\n schema: \"core\",\n table: \"Profile\",\n filter: `id=eq.${currentUser.id}`,\n },\n (payload) => {\n const newStatus = (payload.new as { status?: string })?.status;\n const oldStatus = (payload.old as { status?: string })?.status;\n\n // If status changed to archived/suspended, force sign out\n if (\n oldStatus === \"active\" &&\n (newStatus === \"archived\" || newStatus === \"suspended\")\n ) {\n signOutAsync();\n }\n\n // Refetch profile data to update UI\n profileRequest.refetch();\n },\n )\n .subscribe();\n\n return () => {\n profileChannel.unsubscribe();\n supabase.removeChannel(profileChannel);\n };\n }, [supabase, currentUser?.id, profileRequest.refetch]);\n\n // Combine access keys from both sources, preferring the RPC result\n const combinedAccess: string[] = useMemo(() => {\n // If we have the RPC result, use it (includes both direct and group-based)\n if (accessKeysRequest.data) {\n const uniqueKeys = new Set<string>();\n for (const item of accessKeysRequest.data) {\n if (item.access_key) {\n uniqueKeys.add(item.access_key);\n }\n }\n return Array.from(uniqueKeys);\n }\n // Fallback to direct access only (for backwards compatibility during loading)\n return profileRequest.data?.UserAccess?.map((x) => x.accessKey) || [];\n }, [accessKeysRequest.data, profileRequest.data?.UserAccess]);\n\n // Compute profile status values\n const profileStatus = profileRequest.data?.status as\n | ProfileStatus\n | undefined;\n const isArchived = profileStatus === \"archived\";\n const isSuspended = profileStatus === \"suspended\";\n\n const hasAccess = useCallback(\n (key: string) => {\n // Archived/suspended users have no access\n if (isArchived || isSuspended) {\n return false;\n }\n\n const accessGiven = combinedAccess;\n if (isUsable(accessGiven) === false) return false;\n if (accessGiven.includes(\"owner\")) return true;\n if (accessGiven.includes(key)) return true;\n if (isUsable(key) === false) return true;\n\n return false;\n },\n [combinedAccess, isArchived, isSuspended],\n );\n\n const authStateWithLoading = useMemo(\n () => ({\n hasAccess,\n user: currentUser,\n profile: profileRequest.data,\n access: combinedAccess,\n profileStatus,\n isArchived,\n isSuspended,\n isLoading:\n currentUser === null\n ? false\n : profileRequest.isLoading ||\n accessKeysRequest.isLoading ||\n currentUser === undefined,\n signInAsync,\n signOutAsync,\n onSignOut,\n removeOnSignOut,\n registerAsync,\n refreshAsync,\n }),\n [\n profileRequest.data,\n profileRequest.isLoading,\n accessKeysRequest.data,\n accessKeysRequest.isLoading,\n currentUser,\n combinedAccess,\n profileStatus,\n isArchived,\n isSuspended,\n hasAccess,\n ],\n );\n\n const content = enableEntityPermissions ? (\n <PermissionProvider>{children}</PermissionProvider>\n ) : (\n children\n );\n\n return (\n <setupAuthContext.Provider value={authStateWithLoading}>\n {content}\n </setupAuthContext.Provider>\n );\n}\n","import {\n ReactNode,\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n useCallback,\n useRef,\n} from \"react\";\nimport { useDbQuery as useQuery } from \"../../useDbQuery\";\nimport { useDbUpsert as useUpsert } from \"../../useDbUpsert\";\nimport useSupabase, { Database } from \"../../useSupabase\";\nimport { isUsable } from \"@pol-studios/utils\";\nimport { setupAuthContext } from \"./AuthProvider\";\n\n// UserMetadata query constant\nconst UserMetadataQuery = {\n schema: \"core\",\n table: \"UserMetadata\",\n defaultQuery: \"key, userId, value\",\n} as const;\n\n// Type definitions for UserMetadata\nexport type UserMetadataRow = Database[\"core\"][\"Tables\"][\"UserMetadata\"][\"Row\"];\nexport type UserMetadataInsert =\n Database[\"core\"][\"Tables\"][\"UserMetadata\"][\"Insert\"];\nexport type UserMetadataUpdate =\n Database[\"core\"][\"Tables\"][\"UserMetadata\"][\"Update\"];\n\n// Context interface\nexport interface UserMetadataContextType {\n metadata: Record<string, string>;\n isLoading: boolean;\n error: Error | null;\n setMetadata: (key: string, value: string) => Promise<void>;\n getMetadata: (key: string) => string | undefined;\n removeMetadata: (key: string) => Promise<void>;\n refreshMetadata: () => Promise<void>;\n}\n\n// Create context\nexport const userMetadataContext =\n createContext<UserMetadataContextType | null>(null);\n\n// Provider component\nexport function UserMetadataProvider({ children }: { children: ReactNode }) {\n const supabase = useSupabase();\n const [metadata, setMetadataState] = useState<Record<string, string>>({});\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n // Get current user ID from auth context\n const setupAuth = useContext(setupAuthContext);\n const userId = setupAuth?.user?.id;\n\n // Query to fetch all user metadata\n const metadataQuery = useQuery(\n supabase\n .schema(\"core\")\n .from(\"UserMetadata\")\n .select(UserMetadataQuery.defaultQuery)\n .eq(\"userId\", userId!)\n .order(\"key\"),\n {\n enabled: isUsable(userId),\n crossOrganization: true,\n },\n );\n\n // Upsert mutation for updating metadata\n const upsertMutation = useUpsert(\n { table: \"UserMetadata\", schema: \"core\" },\n [\"userId\", \"key\"],\n UserMetadataQuery.defaultQuery,\n );\n\n // Ref to hold mutation to avoid infinite loops in useCallback dependencies\n const upsertMutationRef = useRef(upsertMutation);\n upsertMutationRef.current = upsertMutation;\n\n // Update local state when query data changes\n useEffect(() => {\n if (metadataQuery.data) {\n const metadataMap: Record<string, string> = {};\n metadataQuery.data.forEach((item: any) => {\n metadataMap[item.key] = item.value;\n });\n setMetadataState(metadataMap);\n setIsLoading(false);\n setError(null);\n } else if (metadataQuery.error) {\n setError(metadataQuery.error);\n setIsLoading(false);\n } else if (metadataQuery.isLoading) {\n setIsLoading(true);\n }\n }, [metadataQuery.data, metadataQuery.error, metadataQuery.isLoading]);\n\n // Set metadata function\n const setMetadata = useCallback(\n async (key: string, value: string) => {\n if (!userId) {\n throw new Error(\"User not authenticated\");\n }\n\n try {\n await upsertMutationRef.current.mutateAsync({\n userId,\n key,\n value,\n });\n\n // Update local state optimistically\n setMetadataState((prev) => ({\n ...prev,\n [key]: value,\n }));\n } catch (err) {\n setError(err as Error);\n throw err;\n }\n },\n [userId],\n );\n\n // Get metadata function\n const getMetadata = useCallback(\n (key: string): string | undefined => {\n return metadata[key];\n },\n [metadata],\n );\n\n // Remove metadata function\n const removeMetadata = useCallback(\n async (key: string) => {\n if (!userId) {\n throw new Error(\"User not authenticated\");\n }\n\n try {\n await supabase\n .schema(\"core\")\n .from(\"UserMetadata\")\n .delete()\n .eq(\"userId\", userId)\n .eq(\"key\", key);\n\n // Update local state\n setMetadataState((prev) => {\n const newState = { ...prev };\n delete newState[key];\n return newState;\n });\n } catch (err) {\n setError(err as Error);\n throw err;\n }\n },\n [userId, supabase],\n );\n\n // Refresh metadata function\n const refreshMetadata = useCallback(async () => {\n await metadataQuery.refetch();\n }, [metadataQuery]);\n\n // Context value\n const contextValue = useMemo(\n () => ({\n metadata,\n isLoading,\n error,\n setMetadata,\n getMetadata,\n removeMetadata,\n refreshMetadata,\n }),\n [\n metadata,\n isLoading,\n error,\n setMetadata,\n getMetadata,\n removeMetadata,\n refreshMetadata,\n ],\n );\n\n return (\n <userMetadataContext.Provider value={contextValue}>\n {children}\n </userMetadataContext.Provider>\n );\n}\n\n// Hook to use the context\nexport function useUserMetadata(): UserMetadataContextType {\n const context = useContext(userMetadataContext);\n if (!context) {\n throw new Error(\n \"useUserMetadata must be used within a UserMetadataProvider\",\n );\n }\n return context;\n}\n\n// Convenience hook for getting a specific metadata value\nexport function useUserMetadataValue(key: string): string | undefined {\n const { getMetadata } = useUserMetadata();\n return getMetadata(key);\n}\n\n// Convenience hook for setting a specific metadata value\nexport function useSetUserMetadata() {\n const { setMetadata, removeMetadata } = useUserMetadata();\n return { setMetadata, removeMetadata };\n}\n\n// Advanced state-like hook with JSON serialization support\nexport function useUserMetadataState<T>(\n key: string,\n defaultValue: T,\n options?: {\n serialize?: (value: T) => string;\n deserialize?: (value: string) => T;\n },\n): [T, (value: T) => Promise<void>, boolean] {\n const { metadata, setMetadata, isLoading } = useUserMetadata();\n\n const serialize = options?.serialize ?? ((value: T) => JSON.stringify(value));\n const deserialize =\n options?.deserialize ?? ((value: string) => JSON.parse(value));\n\n const currentValue = useMemo(() => {\n const rawValue = metadata[key];\n\n if (!rawValue) return defaultValue;\n\n try {\n return deserialize(rawValue);\n } catch (error) {\n console.warn(`Failed to deserialize metadata for key \"${key}\":`, error);\n return defaultValue;\n }\n }, [metadata, key, defaultValue, deserialize, isLoading]);\n\n const setValue = useCallback(\n async (value: T) => {\n const serializedValue = serialize(value);\n await setMetadata(key, serializedValue);\n },\n [key, setMetadata, serialize],\n );\n\n return [currentValue, setValue, isLoading];\n}\n"],"mappings":"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","names":["createContext","jsx","createContext","useCallback","useEffect","useMemo","useRef","useState","jsx","useState","useEffect","useCallback","useRef","useMemo","createContext","useContext","useEffect","useMemo","useState","useCallback","useRef","jsx","createContext","useState","useContext","useRef","useEffect","useCallback","useMemo"]}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isUsable,
|
|
3
|
+
omit
|
|
4
|
+
} from "./chunk-O3K7R32P.js";
|
|
5
|
+
import {
|
|
6
|
+
buildNormalizedQuery,
|
|
7
|
+
encode,
|
|
8
|
+
useQueriesForTableLoader,
|
|
9
|
+
useUpsertItem
|
|
10
|
+
} from "./chunk-H6365JPC.js";
|
|
11
|
+
import {
|
|
12
|
+
useSupabase
|
|
13
|
+
} from "./chunk-AWFMICFV.js";
|
|
14
|
+
|
|
15
|
+
// src/errors/TimeoutError.ts
|
|
16
|
+
var TIMEOUT_ERROR_MESSAGE = "Request timed out";
|
|
17
|
+
var DEFAULT_QUERY_TIMEOUT = 15e3;
|
|
18
|
+
function isTimeoutError(error) {
|
|
19
|
+
if (!error) return false;
|
|
20
|
+
return error.name === "AbortError" || error.message === TIMEOUT_ERROR_MESSAGE || error.message.toLowerCase().includes("timed out");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/useDbQuery.ts
|
|
24
|
+
import { useMemo, useRef } from "react";
|
|
25
|
+
import { useQuery } from "@tanstack/react-query";
|
|
26
|
+
import { useDelayedValue } from "@pol-studios/hooks/state";
|
|
27
|
+
function useDbQuery(query, config) {
|
|
28
|
+
const queryKey = encode(query, false);
|
|
29
|
+
const queryKeyString = queryKey.join("-");
|
|
30
|
+
const debouncedKeyString = useDelayedValue(queryKeyString, 50);
|
|
31
|
+
const isKeyStable = queryKeyString === debouncedKeyString;
|
|
32
|
+
const effectiveEnabled = config?.enabled !== false && isKeyStable;
|
|
33
|
+
const timeoutMs = config?.timeout ?? DEFAULT_QUERY_TIMEOUT;
|
|
34
|
+
const countRef = useRef(null);
|
|
35
|
+
const request = useQuery(
|
|
36
|
+
useMemo(() => ({
|
|
37
|
+
queryKey,
|
|
38
|
+
queryFn: async ({ signal }) => {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
signal.addEventListener("abort", () => controller.abort());
|
|
41
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
42
|
+
try {
|
|
43
|
+
const queryWithSignal = query.abortSignal?.(controller.signal) ?? query;
|
|
44
|
+
const result = await queryWithSignal;
|
|
45
|
+
if (result.error) throw result.error;
|
|
46
|
+
countRef.current = result.count ?? null;
|
|
47
|
+
return result.data;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err.name === "AbortError") {
|
|
50
|
+
throw new Error(TIMEOUT_ERROR_MESSAGE);
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timeoutId);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
...omit({ retry: 1, ...config, enabled: effectiveEnabled }, ["queryKey", "timeout"])
|
|
58
|
+
}), [queryKey, config, effectiveEnabled, timeoutMs, query])
|
|
59
|
+
);
|
|
60
|
+
return useMemo(() => ({
|
|
61
|
+
...request,
|
|
62
|
+
count: countRef.current
|
|
63
|
+
}), [request, countRef.current]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/useDbUpsert.ts
|
|
67
|
+
import { useMutation } from "@tanstack/react-query";
|
|
68
|
+
function useDbUpsert(relation, primaryKeys = ["id"], query, mutationOption) {
|
|
69
|
+
const tableName = typeof relation === "object" ? relation.table : relation;
|
|
70
|
+
const schemaName = typeof relation === "object" ? String(relation.schema) : "public";
|
|
71
|
+
const supabase = useSupabase();
|
|
72
|
+
const primaryKeysAsStrings = primaryKeys.map((k) => String(k));
|
|
73
|
+
const upsertItem = useUpsertItem({
|
|
74
|
+
primaryKeys: primaryKeysAsStrings,
|
|
75
|
+
table: relation,
|
|
76
|
+
schema: "public"
|
|
77
|
+
});
|
|
78
|
+
const queriesForTable = useQueriesForTableLoader(tableName);
|
|
79
|
+
const mutation = useMutation({
|
|
80
|
+
mutationFn: async (item) => {
|
|
81
|
+
let result = null;
|
|
82
|
+
const selectQuery = buildNormalizedQuery({
|
|
83
|
+
queriesForTable,
|
|
84
|
+
query
|
|
85
|
+
});
|
|
86
|
+
async function insert() {
|
|
87
|
+
const keysToFilter = primaryKeys.filter(
|
|
88
|
+
(x) => isUsable(item[x]) === false
|
|
89
|
+
);
|
|
90
|
+
const keysToFilterAsStrings = keysToFilter.map((k) => String(k));
|
|
91
|
+
const response = await supabase.schema(schemaName).from(tableName).insert(omit(item, keysToFilterAsStrings)).select(query).single();
|
|
92
|
+
if (response.error) throw response.error;
|
|
93
|
+
result = response.data;
|
|
94
|
+
if (result) {
|
|
95
|
+
upsertItem(result);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const isUpsertable = primaryKeys.every(
|
|
99
|
+
(x) => x in item && isUsable(item[x])
|
|
100
|
+
);
|
|
101
|
+
if (isUpsertable) {
|
|
102
|
+
const query2 = supabase.schema(schemaName).from(tableName).update(omit(item, primaryKeysAsStrings));
|
|
103
|
+
primaryKeys.forEach((x) => {
|
|
104
|
+
query2.eq(String(x), item[x]);
|
|
105
|
+
});
|
|
106
|
+
const queryResponse = await query2.select(selectQuery?.selectQuery).single();
|
|
107
|
+
console.log("queryResponse", queryResponse, tableName);
|
|
108
|
+
if (queryResponse.status === 406) {
|
|
109
|
+
await insert();
|
|
110
|
+
} else {
|
|
111
|
+
if (queryResponse.error) throw queryResponse.error;
|
|
112
|
+
if (queryResponse.data) {
|
|
113
|
+
result = queryResponse.data;
|
|
114
|
+
if (result) {
|
|
115
|
+
upsertItem(result);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
await insert();
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
return mutation;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
TIMEOUT_ERROR_MESSAGE,
|
|
130
|
+
DEFAULT_QUERY_TIMEOUT,
|
|
131
|
+
isTimeoutError,
|
|
132
|
+
useDbQuery,
|
|
133
|
+
useDbUpsert
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=chunk-GK7B66LY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors/TimeoutError.ts","../src/useDbQuery.ts","../src/useDbUpsert.ts"],"sourcesContent":["export const TIMEOUT_ERROR_MESSAGE = \"Request timed out\";\nexport const DEFAULT_QUERY_TIMEOUT = 15_000; // 15 seconds\n\nexport function isTimeoutError(error: Error | null | undefined): boolean {\n if (!error) return false;\n return (\n error.name === \"AbortError\" ||\n error.message === TIMEOUT_ERROR_MESSAGE ||\n error.message.toLowerCase().includes(\"timed out\")\n );\n}\n","import { PostgrestError, PostgrestSingleResponse } from \"@supabase/supabase-js\";\nimport { useMemo, useRef } from \"react\";\nimport { ItemType } from \"@pol-studios/utils\";\nimport {\n encode,\n UseQuerySingleReturn,\n} from \"@supabase-cache-helpers/postgrest-react-query\";\nimport { omit } from \"@pol-studios/utils\";\nimport { useQuery, UseQueryOptions } from \"@tanstack/react-query\";\nimport { useDelayedValue } from \"@pol-studios/hooks/state\";\nimport { DEFAULT_QUERY_TIMEOUT, TIMEOUT_ERROR_MESSAGE } from \"./errors/TimeoutError\";\n\ntype ConfigurationOptions<T> = {\n crossOrganization?: boolean;\n filter?: (item: ItemType<T>) => boolean;\n timeout?: number;\n};\n\nexport type UseDbQuerySingleReturn<T> = UseQuerySingleReturn<T> & {\n data: T | null | undefined;\n count?: number | null;\n};\n\nexport function useDbQuery<Result>(\n query: PromiseLike<PostgrestSingleResponse<Result>> & { abortSignal?: (signal: AbortSignal) => PromiseLike<PostgrestSingleResponse<Result>> },\n config?:\n & Omit<\n UseQueryOptions<Result, PostgrestError>,\n \"queryKey\" | \"queryFn\"\n >\n & ConfigurationOptions<Result>,\n) {\n // Debounce query key to prevent rapid query churn during fast navigation\n const queryKey = encode(query, false);\n const queryKeyString = queryKey.join(\"-\");\n const debouncedKeyString = useDelayedValue(queryKeyString, 50);\n\n // Only enable query when key has stabilized (debounced matches current)\n const isKeyStable = queryKeyString === debouncedKeyString;\n const effectiveEnabled = config?.enabled !== false && isKeyStable;\n\n const timeoutMs = config?.timeout ?? DEFAULT_QUERY_TIMEOUT;\n\n // Track count from Supabase response (for queries with count: \"exact\")\n const countRef = useRef<number | null>(null);\n\n const request = useQuery(\n useMemo(() => ({\n queryKey,\n queryFn: async ({ signal }) => {\n const controller = new AbortController();\n signal.addEventListener(\"abort\", () => controller.abort());\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n // Execute query with abort signal\n const queryWithSignal = query.abortSignal?.(controller.signal) ?? query;\n const result = await queryWithSignal;\n if (result.error) throw result.error;\n // Store count if available (from queries with count: \"exact\")\n countRef.current = result.count ?? null;\n return result.data;\n } catch (err: any) {\n if (err.name === \"AbortError\") {\n throw new Error(TIMEOUT_ERROR_MESSAGE);\n }\n throw err;\n } finally {\n clearTimeout(timeoutId);\n }\n },\n ...omit({ retry: 1, ...config, enabled: effectiveEnabled }, [\"queryKey\", \"timeout\"]),\n }), [queryKey, config, effectiveEnabled, timeoutMs, query]),\n );\n\n // Return request with count property added\n return useMemo(() => ({\n ...request,\n count: countRef.current,\n }), [request, countRef.current]) as UseDbQuerySingleReturn<Result>;\n}\n","import { useMutation } from \"@tanstack/react-query\";\nimport useSupabase, { Database } from \"./useSupabase\";\nimport {\n useQueriesForTableLoader,\n useUpdateMutation,\n useUpsertItem,\n} from \"@supabase-cache-helpers/postgrest-react-query\";\nimport { GetResult } from \"./parser/select-query-parser\";\nimport { GenericSchema } from \"./parser/types\";\nimport { isUsable } from \"@pol-studios/utils\";\nimport { omit } from \"@pol-studios/utils\";\nimport { buildNormalizedQuery } from \"@supabase-cache-helpers/postgrest-core\";\n\ntype ItemType<T> = T extends Array<infer U> ? U : T;\nexport type MutationOption = Parameters<typeof useUpdateMutation>[3];\nexport type useDbUpsertResponse = ReturnType<typeof useDbUpsert>;\n\nexport function useDbUpsert<\n TableName extends string & keyof PublicSchema[\"Tables\"],\n Table extends PublicSchema[\"Tables\"][TableName],\n Schema extends keyof Database = \"public\",\n ReturnQuery extends string | undefined = undefined,\n PublicSchema extends GenericSchema = Database[\n Extract<\n keyof Database,\n Schema\n >\n ],\n RelationName = unknown,\n Relationships = Table extends { Relationships: infer R } ? R : unknown,\n RowResult = ReturnQuery extends undefined ? null\n : GetResult<\n PublicSchema,\n Table[\"Row\"],\n RelationName,\n Relationships,\n Extract<ReturnQuery, string>\n >,\n ResultInsert extends object = Omit<\n GetResult<PublicSchema, Table[\"Insert\"], RelationName, Relationships, \"*\">,\n \"id\"\n >,\n ResultUpdate extends object =\n & Omit<\n GetResult<\n PublicSchema,\n Table[\"Update\"],\n RelationName,\n Relationships,\n \"*\"\n >,\n \"id\"\n >\n & { id?: any },\n>(\n relation: TableName | { table: TableName; schema: Schema },\n primaryKeys: (keyof (ItemType<RowResult> | any) & string)[] = [\"id\"],\n query?: ReturnQuery,\n mutationOption?: MutationOption & { crossOrganization?: boolean },\n) {\n const tableName = typeof relation === \"object\" ? relation.table : relation;\n const schemaName: string = typeof relation === \"object\" ? String(relation.schema) : \"public\";\n const supabase = useSupabase();\n const primaryKeysAsStrings = primaryKeys.map((k) => String(k)) as string[];\n const upsertItem = useUpsertItem({\n primaryKeys: primaryKeysAsStrings as any,\n table: relation as any,\n schema: \"public\",\n });\n\n const queriesForTable = useQueriesForTableLoader(tableName);\n\n const mutation = useMutation<RowResult, Error, ResultInsert | ResultUpdate>({\n mutationFn: async (item) => {\n let result: RowResult | null = null;\n const selectQuery = buildNormalizedQuery({\n queriesForTable,\n query,\n });\n\n async function insert() {\n const keysToFilter = primaryKeys.filter(\n (x) => isUsable((item as any)[x]) === false,\n );\n const keysToFilterAsStrings = keysToFilter.map((k) => String(k)) as string[];\n const response = await (supabase as any)\n .schema(schemaName as \"public\" | \"core\")\n .from(tableName)\n .insert(omit(item as any, keysToFilterAsStrings as any) as any)\n .select(query)\n .single();\n if (response.error) throw response.error;\n result = response.data as any;\n if (result) {\n upsertItem(result as any);\n }\n }\n\n const isUpsertable = primaryKeys.every(\n (x) => x in item && isUsable((item as any)[x]),\n );\n\n if (isUpsertable) {\n const query = (supabase as any)\n .schema(schemaName as \"public\" | \"core\")\n .from(tableName)\n .update(omit(item as any, primaryKeysAsStrings as any) as any);\n primaryKeys.forEach((x) => {\n query.eq(String(x), (item as any)[x]);\n });\n const queryResponse = await query\n .select(selectQuery?.selectQuery)\n .single();\n console.log(\"queryResponse\", queryResponse, tableName);\n if (queryResponse.status === 406) {\n await insert();\n } else {\n if (queryResponse.error) throw queryResponse.error;\n\n if (queryResponse.data) {\n result = queryResponse.data as any;\n if (result) {\n upsertItem(result as any);\n }\n }\n }\n } else {\n await insert();\n }\n\n return result as any;\n },\n });\n\n return mutation;\n}\n"],"mappings":"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","names":["query"]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
permissionContext,
|
|
3
|
+
setupAuthContext
|
|
4
|
+
} from "./chunk-ERGF2FCE.js";
|
|
5
|
+
import {
|
|
6
|
+
isUsable
|
|
7
|
+
} from "./chunk-O3K7R32P.js";
|
|
8
|
+
|
|
9
|
+
// src/auth/hooks/useAuth.ts
|
|
10
|
+
import { useCallback, useContext, useMemo } from "react";
|
|
11
|
+
function useAuth() {
|
|
12
|
+
const auth = useContext(setupAuthContext);
|
|
13
|
+
if (isUsable(auth.user) === false) {
|
|
14
|
+
console.log({ auth });
|
|
15
|
+
throw new Error(
|
|
16
|
+
"User must not be null, use useSetupAuth() to use a nullable user."
|
|
17
|
+
);
|
|
18
|
+
} else {
|
|
19
|
+
return auth;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function useSetupAuth() {
|
|
23
|
+
const auth = useContext(setupAuthContext);
|
|
24
|
+
const entityPermissions = useContext(permissionContext);
|
|
25
|
+
const hasAccess = useCallback(
|
|
26
|
+
(key) => {
|
|
27
|
+
if (auth.isArchived || auth.isSuspended) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const accessGiven = auth.access;
|
|
31
|
+
if (isUsable(accessGiven) === false) return false;
|
|
32
|
+
if (accessGiven.includes("owner")) return true;
|
|
33
|
+
if (accessGiven.includes(key)) return true;
|
|
34
|
+
if (isUsable(key) === false) return true;
|
|
35
|
+
return false;
|
|
36
|
+
},
|
|
37
|
+
[auth.access, auth.isArchived, auth.isSuspended]
|
|
38
|
+
);
|
|
39
|
+
const hasEntityAccess = useCallback(
|
|
40
|
+
(entityType, entityId, action) => {
|
|
41
|
+
if (auth.isArchived || auth.isSuspended) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (!entityId || entityId <= 0 || !Number.isInteger(entityId)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const accessGiven = auth.access;
|
|
48
|
+
if (isUsable(accessGiven) && accessGiven.includes("owner")) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (!entityPermissions || typeof entityPermissions.checkPermission !== "function") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return entityPermissions.checkPermission(entityType, entityId, action);
|
|
55
|
+
},
|
|
56
|
+
[auth.access, auth.isArchived, auth.isSuspended, entityPermissions]
|
|
57
|
+
);
|
|
58
|
+
return useMemo(
|
|
59
|
+
() => ({
|
|
60
|
+
hasAccess,
|
|
61
|
+
hasEntityAccess,
|
|
62
|
+
...auth,
|
|
63
|
+
user: auth.user
|
|
64
|
+
}),
|
|
65
|
+
[hasAccess, hasEntityAccess, auth]
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/auth/hooks/usePermission.ts
|
|
70
|
+
import { useContext as useContext2, useEffect, useMemo as useMemo2 } from "react";
|
|
71
|
+
function usePermissionContext() {
|
|
72
|
+
const context = useContext2(permissionContext);
|
|
73
|
+
if (!context || Object.keys(context).length === 0) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"usePermission hooks must be used within a PermissionProvider"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return context;
|
|
79
|
+
}
|
|
80
|
+
function usePermission(entityType, entityId) {
|
|
81
|
+
const { getPermission } = usePermissionContext();
|
|
82
|
+
return useMemo2(() => {
|
|
83
|
+
if (entityId === void 0) {
|
|
84
|
+
return {
|
|
85
|
+
canView: false,
|
|
86
|
+
canAdminView: false,
|
|
87
|
+
canEdit: false,
|
|
88
|
+
canCreate: false,
|
|
89
|
+
canDelete: false,
|
|
90
|
+
canShare: false,
|
|
91
|
+
permissionLevel: null,
|
|
92
|
+
isLoading: true
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return getPermission(entityType, entityId);
|
|
96
|
+
}, [entityType, entityId, getPermission]);
|
|
97
|
+
}
|
|
98
|
+
function usePermissionCheck(entityType, entityId, action) {
|
|
99
|
+
const { checkPermission } = usePermissionContext();
|
|
100
|
+
return useMemo2(() => {
|
|
101
|
+
if (entityId === void 0) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return checkPermission(entityType, entityId, action);
|
|
105
|
+
}, [entityType, entityId, action, checkPermission]);
|
|
106
|
+
}
|
|
107
|
+
function usePermissionsBatch(entities) {
|
|
108
|
+
const { getPermission, prefetchPermissions } = usePermissionContext();
|
|
109
|
+
const entitiesKey = useMemo2(
|
|
110
|
+
() => entities.map((e) => `${e.type}:${e.id}`).sort().join(","),
|
|
111
|
+
[entities]
|
|
112
|
+
);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (entities.length === 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const entitiesToPrefetch = entities.map((e) => ({
|
|
118
|
+
entityType: e.type,
|
|
119
|
+
entityId: e.id
|
|
120
|
+
}));
|
|
121
|
+
prefetchPermissions(entitiesToPrefetch);
|
|
122
|
+
}, [entities, entitiesKey, prefetchPermissions]);
|
|
123
|
+
return useMemo2(() => {
|
|
124
|
+
if (entities.length === 0) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
return entities.map((entity) => ({
|
|
128
|
+
type: entity.type,
|
|
129
|
+
id: entity.id,
|
|
130
|
+
permission: getPermission(entity.type, entity.id)
|
|
131
|
+
}));
|
|
132
|
+
}, [getPermission, entitiesKey]);
|
|
133
|
+
}
|
|
134
|
+
function useCanView(entityType, entityId) {
|
|
135
|
+
return usePermissionCheck(entityType, entityId, "view");
|
|
136
|
+
}
|
|
137
|
+
function useCanEdit(entityType, entityId) {
|
|
138
|
+
return usePermissionCheck(entityType, entityId, "edit");
|
|
139
|
+
}
|
|
140
|
+
function useCanDelete(entityType, entityId) {
|
|
141
|
+
return usePermissionCheck(entityType, entityId, "delete");
|
|
142
|
+
}
|
|
143
|
+
function useCanShare(entityType, entityId) {
|
|
144
|
+
return usePermissionCheck(entityType, entityId, "share");
|
|
145
|
+
}
|
|
146
|
+
function useCanCreate(entityType, entityId) {
|
|
147
|
+
return usePermissionCheck(entityType, entityId, "create");
|
|
148
|
+
}
|
|
149
|
+
function useInvalidatePermission() {
|
|
150
|
+
const { invalidatePermission } = usePermissionContext();
|
|
151
|
+
return invalidatePermission;
|
|
152
|
+
}
|
|
153
|
+
function usePermissionLoading() {
|
|
154
|
+
const { isLoading } = usePermissionContext();
|
|
155
|
+
return isLoading;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
useAuth,
|
|
160
|
+
useSetupAuth,
|
|
161
|
+
usePermission,
|
|
162
|
+
usePermissionCheck,
|
|
163
|
+
usePermissionsBatch,
|
|
164
|
+
useCanView,
|
|
165
|
+
useCanEdit,
|
|
166
|
+
useCanDelete,
|
|
167
|
+
useCanShare,
|
|
168
|
+
useCanCreate,
|
|
169
|
+
useInvalidatePermission,
|
|
170
|
+
usePermissionLoading
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=chunk-GQI6WJGI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/hooks/useAuth.ts","../src/auth/hooks/usePermission.ts"],"sourcesContent":["import { isUsable } from \"@pol-studios/utils\";\nimport { useCallback, useContext, useMemo } from \"react\";\nimport { User } from \"@supabase/supabase-js\";\nimport { SetupAuthContext, setupAuthContext } from \"../context/AuthProvider\";\nimport { permissionContext } from \"../context/PermissionContext\";\nimport { EntityAction, EntityType } from \"../types/EntityPermissions\";\n\n/**\n * Hook for authenticated users only.\n * Throws an error if the user is not authenticated.\n * Use useSetupAuth() if you need to handle unauthenticated users.\n *\n * @returns Auth context with guaranteed non-null user and hasAccess helper\n * @throws Error if user is not authenticated\n *\n * @example\n * ```tsx\n * function AuthenticatedComponent() {\n * const { user, hasAccess, profile } = useAuth();\n *\n * // user is guaranteed to be non-null\n * console.log(user.id);\n *\n * if (hasAccess('admin')) {\n * return <AdminDashboard />;\n * }\n *\n * return <UserDashboard />;\n * }\n * ```\n */\nexport function useAuth(): Omit<SetupAuthContext, \"user\"> & {\n hasAccess: (access: string) => boolean;\n user: User;\n} {\n const auth = useContext(setupAuthContext);\n\n if (isUsable(auth.user) === false) {\n console.log({ auth });\n throw new Error(\n \"User must not be null, use useSetupAuth() to use a nullable user.\",\n );\n } else {\n return auth as any;\n }\n}\n\n/**\n * Hook for handling both authenticated and unauthenticated states.\n * Does not throw if user is not authenticated.\n *\n * @returns Auth context with possibly null user, hasAccess, and hasEntityAccess helpers\n *\n * @example\n * ```tsx\n * function FlexibleComponent() {\n * const { user, hasAccess, hasEntityAccess, isLoading } = useSetupAuth();\n *\n * if (isLoading) {\n * return <Spinner />;\n * }\n *\n * if (!user) {\n * return <LoginPrompt />;\n * }\n *\n * // Check global access\n * const isAdmin = hasAccess('admin');\n *\n * // Check entity-level access\n * const canEditProject = hasEntityAccess('Project', 123, 'edit');\n *\n * return <Dashboard />;\n * }\n * ```\n */\nexport function useSetupAuth(): SetupAuthContext & {\n hasAccess: (access: string) => boolean;\n hasEntityAccess: (\n entityType: EntityType,\n entityId: number,\n action: EntityAction,\n ) => boolean;\n} {\n const auth = useContext(setupAuthContext);\n const entityPermissions = useContext(permissionContext);\n\n const hasAccess = useCallback(\n (key: string) => {\n // Archived/suspended users have no access\n if (auth.isArchived || auth.isSuspended) {\n return false;\n }\n\n const accessGiven = auth.access;\n if (isUsable(accessGiven) === false) return false;\n if (accessGiven.includes(\"owner\")) return true;\n if (accessGiven.includes(key)) return true;\n if (isUsable(key) === false) return true;\n\n return false;\n },\n [auth.access, auth.isArchived, auth.isSuspended],\n );\n\n /**\n * Check if the current user has access to a specific entity.\n * Owners (users with 'owner' access key) bypass entity permissions entirely.\n * Otherwise, delegates to the PermissionContext for granular permission checks.\n *\n * @param entityType - 'Project' | 'Client' | 'ProjectDatabase'\n * @param entityId - The ID of the entity to check\n * @param action - 'view' | 'adminView' | 'edit' | 'create' | 'delete' | 'share'\n * @returns boolean - true if user has permission for the action on the entity\n */\n const hasEntityAccess = useCallback(\n (\n entityType: EntityType,\n entityId: number,\n action: EntityAction,\n ): boolean => {\n // Archived/suspended users have no entity access\n if (auth.isArchived || auth.isSuspended) {\n return false;\n }\n\n // Validate entityId is a valid positive integer\n if (!entityId || entityId <= 0 || !Number.isInteger(entityId)) {\n return false;\n }\n\n // Owners bypass all entity permission checks\n const accessGiven = auth.access;\n if (isUsable(accessGiven) && accessGiven.includes(\"owner\")) {\n return true;\n }\n\n // If PermissionContext is not available, deny access\n if (\n !entityPermissions ||\n typeof entityPermissions.checkPermission !== \"function\"\n ) {\n return false;\n }\n\n // Delegate to PermissionContext\n return entityPermissions.checkPermission(entityType, entityId, action);\n },\n [auth.access, auth.isArchived, auth.isSuspended, entityPermissions],\n );\n\n return useMemo(\n () => ({\n hasAccess,\n hasEntityAccess,\n ...auth,\n user: auth.user,\n }),\n [hasAccess, hasEntityAccess, auth],\n );\n}\n","import { useContext, useEffect, useMemo } from \"react\";\nimport { permissionContext } from \"../context/PermissionContext\";\nimport {\n EntityType,\n EntityAction,\n EntityPermissionCheck,\n} from \"../types/EntityPermissions\";\n\n// Re-export types for convenience\nexport type { EntityType, EntityAction, EntityPermissionCheck };\n\n/**\n * Hook to get the full PermissionContext\n * @throws Error if used outside of PermissionProvider\n */\nfunction usePermissionContext() {\n const context = useContext(permissionContext);\n if (!context || Object.keys(context).length === 0) {\n throw new Error(\n \"usePermission hooks must be used within a PermissionProvider\"\n );\n }\n return context;\n}\n\n/**\n * Hook to get full permission info for a single entity\n * @param entityType - 'Project' | 'Client' | 'ProjectDatabase'\n * @param entityId - The ID of the entity (can be undefined for loading states)\n * @returns EntityPermissionCheck with canView, canEdit, etc. and isLoading\n *\n * @example\n * ```tsx\n * function ProjectHeader({ projectId }: { projectId: number }) {\n * const permission = usePermission('Project', projectId);\n *\n * if (permission.isLoading) {\n * return <Spinner />;\n * }\n *\n * return (\n * <div>\n * {permission.canEdit && <EditButton />}\n * {permission.canDelete && <DeleteButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermission(\n entityType: EntityType,\n entityId: number | undefined\n): EntityPermissionCheck {\n const { getPermission } = usePermissionContext();\n\n return useMemo(() => {\n // Return a loading state if entityId is undefined\n if (entityId === undefined) {\n return {\n canView: false,\n canAdminView: false,\n canEdit: false,\n canCreate: false,\n canDelete: false,\n canShare: false,\n permissionLevel: null,\n isLoading: true,\n };\n }\n return getPermission(entityType, entityId);\n }, [entityType, entityId, getPermission]);\n}\n\n/**\n * Hook to check a single action permission\n * @param entityType - Entity type\n * @param entityId - Entity ID\n * @param action - 'view' | 'adminView' | 'edit' | 'create' | 'delete' | 'share'\n * @returns boolean - true if user has permission for action\n *\n * @example\n * ```tsx\n * function ProjectActions({ projectId }: { projectId: number }) {\n * const canEdit = usePermissionCheck('Project', projectId, 'edit');\n * const canDelete = usePermissionCheck('Project', projectId, 'delete');\n *\n * return (\n * <div>\n * <Button disabled={!canEdit}>Edit</Button>\n * <Button disabled={!canDelete}>Delete</Button>\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermissionCheck(\n entityType: EntityType,\n entityId: number | undefined,\n action: EntityAction\n): boolean {\n const { checkPermission } = usePermissionContext();\n\n return useMemo(() => {\n // Return false if entityId is undefined\n if (entityId === undefined) {\n return false;\n }\n return checkPermission(entityType, entityId, action);\n }, [entityType, entityId, action, checkPermission]);\n}\n\n/**\n * Result type for usePermissions batch hook\n */\nexport interface EntityWithPermission {\n type: EntityType;\n id: number;\n permission: EntityPermissionCheck;\n}\n\n/**\n * Hook for batch permission lookup - useful for lists\n * @param entities - Array of { type, id } objects\n * @returns Array of entities with their permissions attached\n *\n * This hook:\n * - Calls prefetchPermissions on mount and when entities change\n * - Returns memoized results\n * - Handles empty arrays gracefully\n *\n * @example\n * ```tsx\n * function ProjectList({ projects }: { projects: Array<{ id: number; name: string }> }) {\n * const entities = projects.map(p => ({ type: 'Project' as const, id: p.id }));\n * const entitiesWithPermissions = usePermissionsBatch(entities);\n *\n * return (\n * <ul>\n * {entitiesWithPermissions.map(({ id, permission }) => {\n * const project = projects.find(p => p.id === id);\n * return (\n * <li key={id}>\n * {project?.name}\n * {permission.canEdit && <EditIcon />}\n * </li>\n * );\n * })}\n * </ul>\n * );\n * }\n * ```\n */\nexport function usePermissionsBatch(\n entities: Array<{ type: EntityType; id: number }>\n): EntityWithPermission[] {\n const { getPermission, prefetchPermissions } = usePermissionContext();\n\n // Memoize the entities array to prevent unnecessary re-renders\n const entitiesKey = useMemo(\n () =>\n entities\n .map((e) => `${e.type}:${e.id}`)\n .sort()\n .join(\",\"),\n [entities]\n );\n\n // Prefetch permissions when entities change\n useEffect(() => {\n if (entities.length === 0) {\n return;\n }\n\n // Convert to the format expected by prefetchPermissions\n const entitiesToPrefetch = entities.map((e) => ({\n entityType: e.type,\n entityId: e.id,\n }));\n\n prefetchPermissions(entitiesToPrefetch);\n }, [entities, entitiesKey, prefetchPermissions]);\n\n // Return memoized results\n return useMemo(() => {\n if (entities.length === 0) {\n return [];\n }\n\n return entities.map((entity) => ({\n type: entity.type,\n id: entity.id,\n permission: getPermission(entity.type, entity.id),\n }));\n }, [getPermission, entitiesKey]);\n}\n\n/**\n * Simple boolean hook to check if user can view an entity\n * @param entityType - Entity type\n * @param entityId - Entity ID (can be undefined for loading states)\n * @returns boolean - true if user can view the entity\n *\n * @example\n * ```tsx\n * function ProjectPage({ projectId }: { projectId: number }) {\n * const canView = useCanView('Project', projectId);\n *\n * if (!canView) {\n * return <AccessDenied />;\n * }\n *\n * return <ProjectContent projectId={projectId} />;\n * }\n * ```\n */\nexport function useCanView(\n entityType: EntityType,\n entityId: number | undefined\n): boolean {\n return usePermissionCheck(entityType, entityId, \"view\");\n}\n\n/**\n * Simple boolean hook to check if user can edit an entity\n * @param entityType - Entity type\n * @param entityId - Entity ID (can be undefined for loading states)\n * @returns boolean - true if user can edit the entity\n *\n * @example\n * ```tsx\n * function ProjectEditor({ projectId }: { projectId: number }) {\n * const canEdit = useCanEdit('Project', projectId);\n *\n * return (\n * <form>\n * <input disabled={!canEdit} />\n * <button disabled={!canEdit}>Save</button>\n * </form>\n * );\n * }\n * ```\n */\nexport function useCanEdit(\n entityType: EntityType,\n entityId: number | undefined\n): boolean {\n return usePermissionCheck(entityType, entityId, \"edit\");\n}\n\n/**\n * Simple boolean hook to check if user can delete an entity\n * @param entityType - Entity type\n * @param entityId - Entity ID (can be undefined for loading states)\n * @returns boolean - true if user can delete the entity\n *\n * @example\n * ```tsx\n * function ProjectActions({ projectId }: { projectId: number }) {\n * const canDelete = useCanDelete('Project', projectId);\n *\n * return (\n * <Button\n * variant=\"destructive\"\n * disabled={!canDelete}\n * onClick={handleDelete}\n * >\n * Delete Project\n * </Button>\n * );\n * }\n * ```\n */\nexport function useCanDelete(\n entityType: EntityType,\n entityId: number | undefined\n): boolean {\n return usePermissionCheck(entityType, entityId, \"delete\");\n}\n\n/**\n * Simple boolean hook to check if user can share an entity\n * @param entityType - Entity type\n * @param entityId - Entity ID (can be undefined for loading states)\n * @returns boolean - true if user can share the entity\n *\n * @example\n * ```tsx\n * function ShareButton({ projectId }: { projectId: number }) {\n * const canShare = useCanShare('Project', projectId);\n *\n * if (!canShare) {\n * return null;\n * }\n *\n * return <Button onClick={openShareModal}>Share</Button>;\n * }\n * ```\n */\nexport function useCanShare(\n entityType: EntityType,\n entityId: number | undefined\n): boolean {\n return usePermissionCheck(entityType, entityId, \"share\");\n}\n\n/**\n * Simple boolean hook to check if user can create entities of a type\n * Note: For create permission, the entityId should be the parent entity ID\n * (e.g., for creating a ProjectDatabase, pass the Project ID)\n *\n * @param entityType - Entity type\n * @param entityId - Parent entity ID (can be undefined for loading states)\n * @returns boolean - true if user can create entities\n *\n * @example\n * ```tsx\n * function AddDatabaseButton({ projectId }: { projectId: number }) {\n * const canCreate = useCanCreate('ProjectDatabase', projectId);\n *\n * return (\n * <Button disabled={!canCreate} onClick={handleCreate}>\n * Add Database\n * </Button>\n * );\n * }\n * ```\n */\nexport function useCanCreate(\n entityType: EntityType,\n entityId: number | undefined\n): boolean {\n return usePermissionCheck(entityType, entityId, \"create\");\n}\n\n/**\n * Hook to invalidate permission cache for a specific entity\n * Useful after permission changes (e.g., after sharing an entity)\n *\n * @returns Function to invalidate permission for an entity\n *\n * @example\n * ```tsx\n * function ShareModal({ projectId }: { projectId: number }) {\n * const invalidatePermission = useInvalidatePermission();\n *\n * const handleShare = async (userId: string) => {\n * await shareProject(projectId, userId);\n * invalidatePermission('Project', projectId);\n * };\n *\n * return <ShareForm onShare={handleShare} />;\n * }\n * ```\n */\nexport function useInvalidatePermission(): (\n entityType: EntityType,\n entityId: number\n) => void {\n const { invalidatePermission } = usePermissionContext();\n return invalidatePermission;\n}\n\n/**\n * Hook to get the loading state of the permission context\n * @returns boolean - true if permissions are being fetched\n *\n * @example\n * ```tsx\n * function PermissionAwareComponent() {\n * const isLoadingPermissions = usePermissionLoading();\n *\n * if (isLoadingPermissions) {\n * return <LoadingSpinner />;\n * }\n *\n * return <Content />;\n * }\n * ```\n */\nexport function usePermissionLoading(): boolean {\n const { isLoading } = usePermissionContext();\n return isLoading;\n}\n\n"],"mappings":"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","names":["useContext","useMemo","useContext","useMemo"]}
|