UNPKG

@fine-dev/fine-js

Version:

Javascript client for Fine BaaS

164 lines (138 loc) 5.02 kB
import { Fetch } from "./types" export type EntityReference = { table: string id: string | number field: string } export type UploadMetadata = Record<string, string> export type FileDetails = { key: string size: number uploadedAt: string etag: string httpMetadata?: { contentType: string } customMetadata?: Record<string, string> } export type StorageClientOptions = { baseUrl: string fetch?: Fetch headers?: HeadersInit } export default class FineStorageClient { private fetch: Fetch private baseUrl: string constructor({ baseUrl, headers, fetch: customFetch }: StorageClientOptions) { this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl if (!customFetch) customFetch = fetch.bind(window) this.fetch = (input, init) => customFetch(input, { ...init, headers, credentials: "include" }) } /** * Builds the base URL path for an entity and file * @param entity The entity reference * @param filename The filename * @returns The formatted URL path */ private getEntityPath(entity: EntityReference, filename: string): string { const encode = encodeURIComponent return `${this.baseUrl}/${encode(entity.table)}/${encode(entity.id)}/${encode(entity.field)}/${encode( filename )}` } /** * Upload a file associated with an entity * @param entity The entity reference * @param file The file to upload * @param metadata Optional metadata for the file * @param isPublic Whether the file should be publicly accessible * @returns Promise with file details */ public async upload( entity: EntityReference, file: File, metadata?: UploadMetadata, isPublic: boolean = false ): Promise<FileDetails> { const formData = new FormData() formData.append("file", file) formData.append("entity", JSON.stringify(entity)) if (metadata) formData.append("metadata", JSON.stringify(metadata)) // Add the public flag to form data formData.append("public", String(isPublic)) const response = await this.fetch(`${this.baseUrl}/upload`, { method: "POST", body: formData }) if (!response.ok) { const errorText = await response.text() throw new Error(`Failed to upload file: ${errorText}`) } return response.json() } /** * Generate a download URL for a file * @param entity The entity reference * @param filename The filename * @returns The download URL */ public getDownloadUrl(entity: EntityReference, filename: string): string { return this.getEntityPath(entity, filename) } /** * Download a file associated with an entity * @param entity The entity reference * @param filename The filename * @returns Promise that resolves when the download starts */ public async download(entity: EntityReference, filename: string): Promise<void> { const url = this.getEntityPath(entity, filename) const response = await this.fetch(url) if (!response.ok) { const errorText = await response.text() throw new Error(`Failed to download file: ${errorText}`) } const blob = await response.blob() // Create a download link and trigger the download const downloadUrl = URL.createObjectURL(blob) const a = document.createElement("a") a.href = downloadUrl a.download = filename document.body.appendChild(a) a.click() // Clean up setTimeout(() => { document.body.removeChild(a) URL.revokeObjectURL(downloadUrl) }, 0) } /** * Get the file content as a Blob without triggering a download * @param entity The entity reference * @param filename The filename * @returns Promise with the file blob */ public async getFileBlob(entity: EntityReference, filename: string): Promise<Blob> { const url = this.getEntityPath(entity, filename) const response = await this.fetch(url) if (!response.ok) { const errorText = await response.text() throw new Error(`Failed to get file blob: ${errorText}`) } return response.blob() } /** * Delete a file associated with an entity * @param entity The entity reference * @param filename The filename * @returns Promise that resolves when the deletion is complete */ public async delete(entity: EntityReference, filename: string): Promise<void> { const url = this.getEntityPath(entity, filename) const response = await this.fetch(url, { method: "DELETE" }) if (!response.ok) { const errorText = await response.text() throw new Error(`Failed to delete file: ${errorText}`) } } }