Utils
packages/utils/README.md
@repo/utils
Utilidades agnósticas y reutilizables del monorepo. Actualmente expone helpers de permisos para resolver los roles y permisos de un usuario dentro de una comunidad.
Uso del package
El package se consume mediante su export público:
import { hasPermission, getPermissionsByCommunity } from '@repo/utils';
Las utilidades de browser para localStorage también se consumen desde el export público:
import { getLocalStorageItem, LOCAL_STORAGE_DATA_TYPES, setLocalStorageItem } from '@repo/utils';
setLocalStorageItem('communityId', 'community-1', LOCAL_STORAGE_DATA_TYPES.STRING);
const communityId = getLocalStorageItem<string>('communityId', LOCAL_STORAGE_DATA_TYPES.STRING);
Estas funciones son seguras para SSR y devuelven null o false cuando el storage no está disponible,
está bloqueado o contiene datos inválidos.
No se deben importar archivos internos desde una app. Para agregar un módulo, crear una carpeta bajo src/, exportar su API desde el índice del módulo y después desde src/index.ts.
Comandos disponibles:
pnpm --filter @repo/utils test:run: ejecuta los tests.pnpm --filter @repo/utils check-types: valida TypeScript sin emitir archivos.pnpm --filter @repo/utils lint: ejecuta ESLint.pnpm --filter @repo/utils build: compila el package.
Tests obligatorios
Cada nuevo util debe tener tests junto al módulo (*.test.ts). Cubrir como mínimo:
- Caso exitoso.
- Comunidad inexistente o mapa
null/undefined. - Permiso o rol desconocido.
- Para helpers booleanos, combinaciones positivas y negativas.
- Para asserts, el error y su mensaje/código.
Utilidades de permisos
Los roles se resuelven exclusivamente con ROLE_PERMISSIONS de @repo/constants.
type RolesMap = Record<string, Array<IUserRolesList>>;
getUserRolesInCommunity(rolesMap, communityId);
getPermissionsByCommunity(rolesMap, communityId);
hasPermission(permission, communityId, rolesMap);
hasAnyPermission(permissions, communityId, rolesMap);
hasAllPermissions(permissions, communityId, rolesMap);
assertCallerPermission(permission, communityId, rolesMap);
assertUserPermission(permission, communityId, rolesMap);
hasPermission valida una autorización dentro de una comunidad concreta; nunca debe evaluarse solo contra el rol global del usuario. hasAnyPermission sirve para acciones alternativas y hasAllPermissions para acciones que requieren todos los permisos. Los helpers assert* lanzan PERMISSION_DENIED y son útiles en servicios internos.
Estándar para Cloud Functions onCall
Las funciones callable que requieren autorización deben seguir este orden:
- Validar
request.authsi la acción es privada. - Parsear
request.datacon el schema de@repo/schemas. - Extraer el
communityIddel payload validado. - Declarar el permiso o permisos necesarios de la acción.
- Obtener
request.auth.token.communitiesy verificar conhasPermission. - Ejecutar el servicio de dominio.
- Devolver el response schema del dominio y mapear errores de negocio.
Para reutilizar los primeros cinco pasos usar authorizedOnCall desde @/shared/on-call/authorized-on-call:
export const inviteUserOnCall = authorizedOnCall({
options: { secrets: SMTP_ARRAY },
schema: inviteUserOnCallInputSchema,
requiredPermissions: ROLES_PERMISSIONS.USERS.USERS_DASHBOARD.EDIT_USERS_INVITATION.key,
getCommunityId: (input) => input.communityId,
errors: {
unauthenticated: () => errorResponse(USERS_ERROR_CODES.PERMISSION_DENIED),
invalidArgument: () => errorResponse(USERS_ERROR_CODES.INVALID_ARGUMENT),
permissionDenied: () => errorResponse(USERS_ERROR_CODES.PERMISSION_DENIED),
},
handler: async (input, request) => service(input, request.auth.uid),
});
El wrapper acepta un permiso o una lista de permisos; una lista usa semántica any. Para dominios distintos se deben proporcionar sus propios constructores errors y response schema. La lógica de negocio, logging y mapeo de errores internos permanece en handler.
authorizeOnCallRequest también está exportada desde el módulo para probar de forma aislada autenticación, payload y permisos sin levantar Firebase. Cada nuevo wrapper especializado puede componerse sobre esta misma función, manteniendo los contratos de error del dominio.