0.2.1-preview.2 • Published 1 year ago

@ws-serenity/use-data-transfer v0.2.1-preview.2

Weekly downloads
-
License
ISC
Repository
gitlab
Last release
1 year ago

UseDataTransfer

Хук для предоставления единого api для удаленных и локальных файлов.

Валидация: поддерживает api для валидации файлов.
Гибкость: поддерживает любые представления для описаний файлов, ошибок валидации, и конфигураций для скачивания файлов.
Кэширование: кэширует файлы, полученные для предпросмотра, до момента размонтирования компонента.
Состояние загрузки: предоставляет переменную, отражающую текущий процент загрузки каждого из файлов на сервер.
Отложенная синхронизация: имеет возможность отложить синхронизацию состояния с сервером, до вызова соответствующего метода.
Асинхронность: методы хука не блокируют поток.


Быстрый страт

Пример

Обобщения

В начале, нужно определить типы данных, используемые хуком.

TDataImage

TDataImage (image от en - образ, а не от en - изображение) определяет представление, в котором хранится информация о файле (но не сам файл).
Тип обязан иметь поле id: string, его можно или указать вручную (допускается ⚠️), или расширить имеющийся тип DataImage (оптимально ✅). Рекомендуется разбивать тип на type Additions + type CustomData = DataImage + Additions, тогда, можно будет использовать типизированный Object.assign<DataImage, Additions>(...), для создания TDataImage.

import { UseDataTransfer } from "@ws-serenity/use-data-transfer"; 

// Пусть каждый образ хранит название вложения и его размер
export type CustomDataImageAdditions = {
    name: string
    size: string
}

export type CustomDataImage = CustomDataImageAdditions & UseDataTransfer.DataImage

Хук расширяет интерфейс TDataImage до HookDrivenDataImage<TDataImage>.

const {add, images} = useDataTransfer<CustomDataImage /*..*/>(/*..*/);

add(file);

const hookDrivenImage = images[0];

console.log(hookDrivenImage.state); // Состояние данных, может принимать значения local | remote | uploading
const blob = hookDrivenImage.preview().blob; // Метод для скачивания данных в ОЗУ
hookDrivenImage.download(); // Метод для скачивания данных в ПЗУ
hookDrivenImage.remove(); // Метод для удаления данных

Таким образом, TDataImage не должен содержать следующие зарезервированные под расширение поля или методы: state, remove, preview, download.

TError

TError определяет представление ошибок, используемых при валидации локальных файлов. Может быть представлен любым значением типа Record<any, any>. TError, аналогично, будет расширен до HookDrivenValidationError<TError>.

const {add, images, errors} = useDataTransfer<CustomDataImage, CustomError /*..*/>(/*..*/);

add(file);

const hookDrivenError = errors[0];
console.log(hookDrivenError.id); // Уникальный id ошибка
hookDrivenError.resolve(); // Уадлить ошибку из коллекции

TError не должен содержать поля или методы: id, resolve.

Работа с Id
Хук создает уникальный идентификатор для каждого из добавленных образов. Образы, полученные удаленно, должны иметь свой id, как часть контракта, на котором построена работа с сервером. Когда локальный файл будет успешно отправлен на сервер, его id будет подменен на id присвоенный сервером.
Ошибки валидации, в отличие от образов, не могут прийти с сервера, их id на протяжении всего жизненного цикла контролируется хуком. Поэтому для TDataImage id это необходимое поле, а для TError расширяемое хуком.

TPreviewConfig и TDownloadConfig

TPreviewConfig и TDownloadConfig определяют конфиги, с которыми могут быть запрошены удаленные файлы. Могут быть представлены чем угодно, в том числе и отсутствовать, те, иметь значение, установленное по умолчанию, undefined.

Например:

export type CustomPreviewConfig = 'original' | 'thumbnail'

export type CustomDownloadConfig = {
    width: number
    height: number
}

// Также, может использоваться один тип
export type AnotherDownloadConfig = CustomPreviewConfig

Вспомогательные обработчики

Для того чтобы хук мог работать с TDataImage, ему необходимо предоставить маппер с сигнатурой (file: File, image: DataImage) => TDataImage, называемый genericDataImageBuilder.

// Для определенного выше `CustomDataImage`, может быть представлен так
const customDataImageBuilder = (file: File, image: DataImage) => {
    return Object.assign<DataImage, CustomDataImageAdditions>(image, {
        name: file.name,
        size: file.size
    })
}

Хук использует кэширование для каждого hookDrivenImage.preview(previewConfig) запроса. Кэшируется не только файл, но и конфиг запроса. Так как хук ничего не знает об используемом TPreviewConfig, то ему нужен вспомогательный обработчик cacheWorker: CacheWorker.

export type CacheWorker<TPreviewConfig> = {
    search: (cache, config?) => CachedEntity | undefined // 1
    getLocalFileQualityConfig: (file: File) => TPreviewConfig // 2
}

Для разрешения случаев, когда вместо запроса на сервер, можно предоставить кэшированный файл от запроса с другим конфигом, используется search (1) метод. Например, такое поведение может быть полезно, когда кэширован оригинал изображения, и необходима версия с меньшим разрешением.
Если search находит подходящую сущность, то он возвращает её, в ином случае - undefined.

При добавлении локального файла, необходимо знать какому TPreviewConfig он соответствует. Для этого используется метод getLocalFileQualityConfig (2).

Для общего случая TPreviewConfig = undefined, cacheWorker может быть таким:

const cacheWorker: CacheWorker<undefined> = {
    getLocalFileQualityConfig: () => undefined,
    search: (entities) => entities[0]
}

Провайдер для удаленных файлов

Для работы с сервером хуку требуется реализация абстрактного класса RemoteDataProvider.

export abstract class RemoteDataProvider<
    TDataImage extends DataImage = DataImage,
    TPreviewConfig = undefined,
    TDownloadConfig = undefined
> {
    // Отменяет выполняющуюся загрузку файла на сервер
    abstract abort(imageId: string): Promise<void>
    
    // Возвращает описания всех файлов на сервере
    abstract getAllDataImages(): Promise<TDataImage[]>
    
    // Скачивание файлов
    abstract preview(imageId: string, config: TPreviewConfig): Promise<Blob>
    abstract download(imageId: string, config: TDownloadConfig): Promise<void>
    
    // Изменение состояния на сервере
    abstract commit(
        config: {
            // Commit конфиг содержит коллекцию из id файлов, которые необходимо удалить
            toDelete?: string[],
            // и коллекцию { локальный id (присвоенный хуком) -> файл }, которую необходимо загрузить
            toUpload?: Record<string, Blob>
        },
        // Событие для отслеживания состояния загрузки
        onProgress?: (event: ProgressEvent) => void
    // Обязано вернуть коллекцию { локальный id (присвоенный хуком) -> удаленный id (присвоенный сервером) }
    ): Promise<Record<string, string>>
}

Использование хука

Определив все обобщения, подготовив вспомогательные обработчики и реализацию RemoteDataProvider, можно использовать хук.

const {
    images, // Все образы
    errors, // Все ошибки
    resolve, // Удалить ошибку по id
    remove, // Удалить файл по id
    add, // Добавить коллекцию локальных файлов
    dataTransferProgress, // Коллекция { локальный id -> доля отправленных данных }
    isLoading, // true до обработки remoteDataProvider.getAllImages()
    commit // Синхронизирует изменения с сервером
} = useDataTransfer<CustomDataImage, CustomError>(
    remoteDataProvider,
    customDataImageBuilder,
    cacheWorker
)

Конфигурация

Необязательный четвертый параметр useDataTransfer - конфигурация, дополнительно настраивающая поведение хука.

ПараметрТипПо умолчаниюОписание
readonlybooleanfalseУстанавливает поведение "только для чтения".
strictReadonlybooleantrueЕсли true, то, при попытке изменения состояния хука, будет вызвано исключение.
dataTransferBehaviourDataTransferBehaviorexecute-on-commitЕсли execute-on-commit, то синхронизация с сервером будет произведена после вызова commit(). Если execute-on-action, то синхронизация с сервером будет происходить после каждого изменения, а вызов commit() приведет к исключению.
dataTransferAccumulationIntervalnumber500Период в миллисекундах, с которым будет обновляться состояние dataTransferProgress при наличии активных загрузок.
validatorsValidationFunction[]Коллекция правил для валидации файлов.
onErrorResolvedActionCallbackКоллбэк для resolve().
onImageRemovedActionCallbackКоллбэк для remove().

Валидация

Функция валидации имеет следующую сигнатуру:

export type Context = {
    images: HookDrivenDataImage[]
}

// В случае, если ошибок нет, то функция ничего не возвращает
export type ValidationFunction = (file: File, ctx: Context) => TError | void

Функции валидации будут вызваны в порядке объявления, как только валидатор вернет ошибку, проверка остановится. Например, если файл не удовлетворяет функциям валидации А и Б (объявленным в соответствующем порядке), то в errors попадет только ошибка из валидатора А.


Ограничения

Изменение параметров хука, отличных от config, в реальном времени может привести к некорректному поведению.