@fine-dev/fine-js
Version:
Javascript client for Fine BaaS
164 lines (138 loc) • 5.02 kB
text/typescript
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}`)
}
}
}