UNPKG

@sanity/asset-utils

Version:

<!-- This file is AUTO-GENERATED, edit _README.template.md or tweak scripts/generateDocs.ts -->

445 lines (402 loc) 13 kB
import { isAssetIdStub, isAssetObjectStub, isAssetPathStub, isAssetUrlStub, isCdnUrl, isReference, isSanityFileAsset, isSanityImageAsset, } from './asserters.js' import { cdnUrl, dummyProject, fileAssetFilenamePattern, idPattern, imageAssetFilenamePattern, pathPattern, } from './constants.js' import {UnresolvableError} from './errors.js' import {getDefaultCrop, getDefaultHotspot} from './hotspotCrop.js' import {parseFileAssetId, parseImageAssetId} from './parse.js' import { buildFilePath, buildFileUrl, buildImagePath, buildImageUrl, getUrlPath, tryGetAssetPath, } from './paths.js' import type { PathBuilderOptions, ResolvedSanityFile, ResolvedSanityImage, SanityAssetSource, SanityFileAsset, SanityFileObjectStub, SanityFileSource, SanityImageAsset, SanityImageDimensions, SanityImageObjectStub, SanityImageSource, SanityProjectDetails, } from './types.js' import {getForgivingResolver} from './utils.js' /** * Returns the width, height and aspect ratio of a passed image asset, from any * inferrable structure (id, url, path, asset document, image object etc) * * @param src - Input source (image object, asset, reference, id, url, path) * @returns Object with width, height and aspect ratio properties * * @throws {@link UnresolvableError} * Throws if passed image source could not be resolved to an asset ID * @public */ export function getImageDimensions(src: SanityImageSource): SanityImageDimensions { const imageId = getAssetDocumentId(src) const {width, height} = parseImageAssetId(imageId) const aspectRatio = width / height return {width, height, aspectRatio} } /** * {@inheritDoc getImageDimensions} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetImageDimensions = getForgivingResolver(getImageDimensions) /** * Returns the file extension for a given asset * * @param src - Input source (file/image object, asset, reference, id, url, path) * @returns The file extension, if resolvable (no `.` included) * * @throws {@link UnresolvableError} * Throws if passed asset source could not be resolved to an asset ID * @public */ export function getExtension(src: SanityAssetSource): string { return isFileSource(src) ? getFile(src, dummyProject).asset.extension : getImage(src, dummyProject).asset.extension } /** * {@inheritDoc getExtension} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetExtension = getForgivingResolver(getExtension) /** * Tries to resolve an image object with as much information as possible, * from any inferrable structure (id, url, path, image object etc) * * @param src - Input source (image object, asset, reference, id, url, path) * @param project - Project ID and dataset the image belongs to * @returns Image object * * @throws {@link UnresolvableError} * Throws if passed image source could not be resolved to an asset ID * @public */ export function getImage( src: SanityImageSource, project?: SanityProjectDetails, ): ResolvedSanityImage { const projectDetails = project || tryGetProject(src) const asset = getImageAsset(src, projectDetails) const img = src as SanityImageObjectStub return { asset, crop: img.crop || getDefaultCrop(), hotspot: img.hotspot || getDefaultHotspot(), } } /** * {@inheritDoc getImage} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetImage = getForgivingResolver(getImage) /** * Tries to resolve a (partial) image asset document with as much information as possible, * from any inferrable structure (id, url, path, image object etc) * * @param src - Input source (image object, asset, reference, id, url, path) * @param project - Project ID and dataset the image belongs to * @returns Image asset document * * @throws {@link UnresolvableError} * Throws if passed image source could not be resolved to an asset ID * @public */ export function getImageAsset( src: SanityImageSource, project?: SanityProjectDetails, ): SanityImageAsset { const projectDetails = project || getProject(src) const pathOptions: PathBuilderOptions = {...projectDetails, useVanityName: false} const _id = getAssetDocumentId(src) const sourceObj = src as SanityImageObjectStub const source = (sourceObj.asset || src) as SanityImageAsset const metadata = source.metadata || {} const {assetId, width, height, extension} = parseImageAssetId(_id) const aspectRatio = width / height const baseAsset: SanityImageAsset = { ...(isSanityImageAsset(src) ? src : {}), _id, _type: 'sanity.imageAsset', assetId, extension, metadata: { ...metadata, dimensions: {width, height, aspectRatio}, }, // Placeholders, overwritten below url: '', path: '', } return { ...baseAsset, path: buildImagePath(baseAsset, pathOptions), url: buildImageUrl(baseAsset, pathOptions), } } /** * {@inheritDoc getImageAsset} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetImageAsset = getForgivingResolver(getImageAsset) /** * Tries to resolve an file object with as much information as possible, * from any inferrable structure (id, url, path, file object etc) * * @param src - Input source (file object, asset, reference, id, url, path) * @param project - Project ID and dataset the file belongs to * @returns File object * * @throws {@link UnresolvableError} * Throws if passed file source could not be resolved to an asset ID * @public */ export function getFile(src: SanityFileSource, project?: SanityProjectDetails): ResolvedSanityFile { const projectDetails = project || tryGetProject(src) const asset = getFileAsset(src, projectDetails) return {asset} } /** * {@inheritDoc getFile} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetFile = getForgivingResolver(getFile) /** * Tries to resolve a (partial) file asset document with as much information as possible, * from any inferrable structure (id, url, path, file object etc) * * @param src - Input source (file object, asset, reference, id, url, path) * @param options - Project ID and dataset the file belongs to, along with other options * @returns File asset document * * @throws {@link UnresolvableError} * Throws if passed file source could not be resolved to an asset ID * @public */ export function getFileAsset(src: SanityFileSource, options?: PathBuilderOptions): SanityFileAsset { const projectDetails: PathBuilderOptions = {...(options || getProject(src)), useVanityName: false} const _id = getAssetDocumentId(src) const sourceObj = src as SanityFileObjectStub const source = (sourceObj.asset || src) as SanityFileAsset const {assetId, extension} = parseFileAssetId(_id) const baseAsset: SanityFileAsset = { ...(isSanityFileAsset(src) ? src : {}), _id, _type: 'sanity.fileAsset', assetId, extension, metadata: source.metadata || {}, // Placeholders, overwritten below url: '', path: '', } return { ...baseAsset, path: buildFilePath(baseAsset, projectDetails), url: buildFileUrl(baseAsset, projectDetails), } } /** * {@inheritDoc getFileAsset} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetFileAsset = getForgivingResolver(getFileAsset) /** * Tries to resolve the asset document ID from any inferrable structure * * @param src - Input source (image/file object, asset, reference, id, url, path) * @returns The asset document ID * * @throws {@link UnresolvableError} * Throws if passed asset source could not be resolved to an asset document ID * @public */ export function getAssetDocumentId(src: unknown): string { const source = isAssetObjectStub(src) ? src.asset : src let id = '' if (typeof source === 'string') { id = getIdFromString(source) } else if (isReference(source)) { id = source._ref } else if (isAssetIdStub(source)) { id = source._id } else if (isAssetPathStub(source)) { id = idFromUrl(`${cdnUrl}/${source.path}`) } else if (isAssetUrlStub(source)) { id = idFromUrl(source.url) } const hasId = id && idPattern.test(id) if (!hasId) { throw new UnresolvableError(src) } return id } /** * {@inheritDoc getAssetDocumentId} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetAssetDocumentId = getForgivingResolver(getAssetDocumentId) /** * Tries to cooerce a string (ID, URL or path) to an image asset ID * * @param str - Input string (ID, URL or path) * @returns string * * * @throws {@link UnresolvableError} * Throws if passed image source could not be resolved to an asset ID * @public */ export function getIdFromString(str: string): string { if (idPattern.test(str)) { // Already an ID return str } const isAbsoluteUrl = isCdnUrl(str) const path = isAbsoluteUrl ? new URL(str).pathname : str if (path.indexOf('/images') === 0 || path.indexOf('/files') === 0) { // Full URL return idFromUrl(str) } if (pathPattern.test(str)) { // Path return idFromUrl(`${cdnUrl}/${str}`) } if (isFileAssetFilename(str)) { // Just a filename (projectId/dataset irrelevant: just need asset ID) return idFromUrl(`${cdnUrl}/files/a/b/${str}`) } if (isImageAssetFilename(str)) { // Just a filename (projectId/dataset irrelevant: just need asset ID) return idFromUrl(`${cdnUrl}/images/a/b/${str}`) } throw new UnresolvableError(str) } /** * {@inheritDoc getIdFromString} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetIdFromString = getForgivingResolver(getIdFromString) /** * Converts from a full asset URL to just the asset document ID * * @param url - A full asset URL to convert * @returns string * @public */ function idFromUrl(url: string): string { const path = getUrlPath(url) const [type, , , fileName] = path.split('/') const prefix = type.replace(/s$/, '') return `${prefix}-${fileName.replace(/\./g, '-')}` } /** * Resolves project ID and dataset the image belongs to, based on full URL or path * @param src - Image URL or path * @returns object | undefined * * @throws {@link UnresolvableError} * Throws if passed image source could not be resolved to an asset ID * @public */ export function getProject(src: SanityImageSource): SanityProjectDetails { const path = tryGetAssetPath(src) if (!path) { throw new UnresolvableError(src, 'Failed to resolve project ID and dataset from source') } const [, , projectId, dataset] = path.match(pathPattern) || [] if (!projectId || !dataset) { throw new UnresolvableError(src, 'Failed to resolve project ID and dataset from source') } return {projectId, dataset} } /** * {@inheritDoc getProject} * @returns Returns `undefined` instead of throwing if a value cannot be resolved * @public */ export const tryGetProject = getForgivingResolver(getProject) /** * Returns whether or not the passed filename is a valid image asset filename * * @param filename - Filename to validate * @returns Whether or not the filename is an image asset filename * @public */ export function isImageAssetFilename(filename: string): boolean { return imageAssetFilenamePattern.test(filename) } /** * Returns whether or not the passed filename is a valid file asset filename * * @param filename - Filename to validate * @returns Whether or not the filename is a file asset filename * @public */ export function isFileAssetFilename(filename: string): boolean { return fileAssetFilenamePattern.test(filename) } /** * Returns whether or not the passed filename is a valid file or image asset filename * * @param filename - Filename to validate * @returns Whether or not the filename is an asset filename * @public */ export function isAssetFilename(filename: string): boolean { return isImageAssetFilename(filename) || isFileAssetFilename(filename) } /** * Return whether or not the passed source is a file source * * @param src - Source to check * @returns Whether or not the given source is a file source * @public */ export function isFileSource(src: unknown): src is SanityFileSource { const assetId = tryGetAssetDocumentId(src) return assetId ? assetId.startsWith('file-') : false } /** * Return whether or not the passed source is an image source * * @param src - Source to check * @returns Whether or not the given source is an image source * @public */ export function isImageSource(src: unknown): src is SanityImageSource { const assetId = tryGetAssetDocumentId(src) return assetId ? assetId.startsWith('image-') : false }