@ngx-file-upload/core
Version:
Angular 19 file upload core package for async file uploads in angular containing validation, upload queue and async uploading.
917 lines (900 loc) • 30.6 kB
JavaScript
import * as i0 from '@angular/core';
import { NgModule, InjectionToken, inject } from '@angular/core';
import { provideHttpClient, withInterceptorsFromDi, HttpErrorResponse, HttpHeaders, HttpEventType, HttpClient } from '@angular/common/http';
import { BehaviorSubject, map, Subject, of, isObservable, from, concat, merge, ReplaySubject, timer } from 'rxjs';
import { takeUntil, distinctUntilChanged, filter, bufferCount, map as map$1, tap, take, distinctUntilKeyChanged, switchMap } from 'rxjs/operators';
class NgxFileUploadCoreModule {
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgxFileUploadCoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.5", ngImport: i0, type: NgxFileUploadCoreModule }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgxFileUploadCoreModule, providers: [provideHttpClient(withInterceptorsFromDi())] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgxFileUploadCoreModule, decorators: [{
type: NgModule,
args: [{ imports: [], providers: [provideHttpClient(withInterceptorsFromDi())] }]
}] });
var NgxFileUploadState;
(function (NgxFileUploadState) {
NgxFileUploadState[NgxFileUploadState["INVALID"] = 0] = "INVALID";
NgxFileUploadState[NgxFileUploadState["IDLE"] = 1] = "IDLE";
NgxFileUploadState[NgxFileUploadState["START"] = 2] = "START";
NgxFileUploadState[NgxFileUploadState["PENDING"] = 3] = "PENDING";
NgxFileUploadState[NgxFileUploadState["CONNECT"] = 4] = "CONNECT";
NgxFileUploadState[NgxFileUploadState["PROGRESS"] = 5] = "PROGRESS";
NgxFileUploadState[NgxFileUploadState["CANCELED"] = 6] = "CANCELED";
NgxFileUploadState[NgxFileUploadState["COMPLETED"] = 7] = "COMPLETED";
})(NgxFileUploadState || (NgxFileUploadState = {}));
class NgxFileUploadForm {
get errors() {
return this.formErrors.getValue();
}
constructor(request) {
this.request = request;
this.formControls = new Map();
this.formErrors = new BehaviorSubject(null);
this.validationState = this.formErrors.pipe(map((errors) => (errors === null ? 'VALID' : 'INVALID')));
}
/**
* @description
*/
addControl(name, ctrl) {
if (this.request.state > NgxFileUploadState.IDLE) {
return;
}
const control = {
errors: null,
valid: true,
dirty: true,
send: true,
...ctrl
};
this.formControls.set(name, control);
this.validate();
}
controls() {
return this.formControls.entries();
}
/**
* @description
*/
getControl(ctrl) {
return this.formControls.get(ctrl);
}
/**
* @description
*/
getRawValue() {
const raw = {};
for (const [name, ctrl] of this.formControls.entries()) {
raw[name] = ctrl.value;
}
return raw;
}
/**
* @description
*/
removeControl(key) {
if (this.request.state > NgxFileUploadState.IDLE) {
return;
}
this.formControls.delete(key);
this.validate();
}
/**
* @description
* @param formCtrlName
* @param validator
* @returns
*/
addValidator(formCtrlName, validator) {
const control = this.formControls.get(formCtrlName);
if (!control) {
return;
}
const updated = { ...control, dirty: true, validator };
this.formControls.set(formCtrlName, updated);
this.validate();
}
// removeValidator(formCtrlName: string, validator: NgxFileUploadValidation<NgxFileuploadFormControl>) {}
/**
* @description
*/
setValue(ctrl, value) {
const control = this.formControls.get(ctrl);
if (!control) {
return;
}
const updated = { ...control, dirty: true, value };
this.formControls.set(ctrl, updated);
this.validate();
}
/**
* @description
*/
validate() {
let formErrors = {};
for (const [name, ctrl] of this.formControls.entries()) {
const validator = ctrl.validator;
if (!validator)
continue;
let errors = ctrl.errors;
if (ctrl.dirty === true) {
errors = 'validate' in validator ? validator.validate(ctrl) : validator(ctrl);
this.formControls.set(name, { ...ctrl, dirty: false, errors });
}
if (errors !== null) {
formErrors = {
...formErrors,
...(errors !== null ? { [name]: errors } : {}),
};
}
}
this.formErrors.next(Object.keys(formErrors).length > 0 ? formErrors : null);
}
}
/**
* Represents an upload request, and store the data inside
*/
class NgxFileUploadRequestModel {
constructor(filesToUpload) {
this.filesToUpload = filesToUpload;
this.errors = null;
this.response = null;
this.state = NgxFileUploadState.IDLE;
this.uploaded = 0;
this.progress = 0;
this.hasError = false;
}
get files() {
return Array.isArray(this.filesToUpload) ? this.filesToUpload : [this.filesToUpload];
}
get name() {
return this.files.map((file) => file.name);
}
get size() {
return this.files.reduce((size, file) => size + file.size, 0);
}
get validationErrors() {
return this.errors;
}
toJson() {
return {
files: this.files,
hasError: this.hasError,
name: this.name,
progress: this.progress,
response: this.response,
size: this.size,
state: this.state,
uploaded: this.uploaded,
validationErrors: this.validationErrors,
};
}
}
class NgxFileUploadRequest {
get change() {
return this.change$.asObservable();
}
get destroyed() {
return this.destroyed$.asObservable();
}
get data() {
return this.model.toJson();
}
set state(state) {
const prevState = this.state;
this.model.state = state;
if (prevState !== state) {
this.notifyObservers();
}
}
get state() {
return this.model.state;
}
get form() {
return this.uploadForm;
}
set url(url) {
if (this.state <= NgxFileUploadState.IDLE) {
this.uploadUrl = url;
}
}
get url() {
return this.uploadUrl;
}
constructor(http, files, options) {
this.http = http;
this.cancel$ = new Subject();
this.change$ = new Subject();
this.destroyed$ = new Subject();
this.totalSize = -1;
this.options = {
url: '',
withCredentials: false,
transferMethod: 'formdata',
formControlNameToKebabCase: false,
};
this.hooks = { beforeStart: [] };
this.headers = {};
this.uploadUrl = '';
this.requestId = '';
// additional form data hinzufuegen muessen
this.options = { ...this.options, ...options };
this.model = new NgxFileUploadRequestModel(files);
this.uploadForm = this.createForm();
this.state = this.isInvalid() ? NgxFileUploadState.INVALID : NgxFileUploadState.IDLE;
this.headers = structuredClone(this.options.headers ?? {});
this.url = this.options.url;
/**
* subscribe to validation state change for form, if state changed update validility for uploda request
*/
this.uploadForm.validationState.pipe(takeUntil(this.destroyed$), distinctUntilChanged()).subscribe(() => this.updateValidility());
}
getHeader(name) {
return this.headers[name];
}
/**
* @description add additional headers to request
*/
addHeader(name, value) {
if (this.state !== NgxFileUploadState.IDLE) {
return;
}
this.headers[name] = value;
}
/**
* @description remove additional headers from request
*/
removeHeader(name) {
if (this.state !== NgxFileUploadState.IDLE) {
return;
}
delete this.headers[name];
}
beforeStart(hook) {
this.hooks.beforeStart = [...this.hooks.beforeStart, hook];
}
/**
* cancel current file upload, this will complete change subject
*/
cancel() {
if (this.isProgress() || this.isPending()) {
this.model.state = NgxFileUploadState.CANCELED;
this.notifyObservers();
this.cancel$.next(true);
}
}
destroy() {
this.finalizeUpload();
this.destroyed$.next(true);
this.destroyed$.complete();
}
/**
* return true if upload was not completed since the server
* sends back an error response
*/
hasError() {
return this.model.response instanceof HttpErrorResponse;
}
isCompleted(ignoreError = false) {
let isCompleted = this.isRequestCompleted();
isCompleted = isCompleted && (ignoreError || !this.hasError());
isCompleted = isCompleted || this.model.state === NgxFileUploadState.CANCELED;
return isCompleted;
}
isCanceled() {
return this.model.state === NgxFileUploadState.CANCELED;
}
isInvalid() {
return this.state === NgxFileUploadState.INVALID || this.model.validationErrors !== null;
}
isProgress() {
return this.state === NgxFileUploadState.PROGRESS || this.state === NgxFileUploadState.START;
}
isPending() {
return this.state === NgxFileUploadState.PENDING;
}
isIdle() {
return this.state === NgxFileUploadState.IDLE;
}
isRequestCompleted() {
return this.state === NgxFileUploadState.COMPLETED;
}
/**
* restart download again
* reset state, and reset errors
*/
retry() {
if ((this.isRequestCompleted() && this.hasError()) || this.isCanceled()) {
this.model = new NgxFileUploadRequestModel(this.model.files);
this.start();
}
}
/**
* start file upload
*/
start() {
if (!this.isIdle() && !this.isPending()) {
return;
}
this.beforeStartHook$.pipe(filter((isAllowedToStart) => isAllowedToStart)).subscribe(() => {
this.state = NgxFileUploadState.START;
});
}
/**
* @description internal use only, before start hooks can become async if they return simple
* boolean value or a promise. A boolean Value will turned into a Promise into an observable.
*/
run() {
this.startUploadRequest().subscribe({
next: (event) => this.handleHttpEvent(event),
error: (error) => this.handleError(error),
});
}
removeInvalidFiles() {
if (this.state !== NgxFileUploadState.INVALID) {
return;
}
const files = this.data.files.filter((file) => file.validationErrors === null);
if (files.length) {
this.model = new NgxFileUploadRequestModel(files);
this.state = NgxFileUploadState.IDLE;
}
else {
this.destroy();
}
}
/**
* @description updates validility for upload request
*/
updateValidility() {
if (this.state > NgxFileUploadState.IDLE) {
return;
}
let validationErrors = {};
// validation errors file
validationErrors = this.model.files.reduce((errors, file) => {
if (file.validationErrors) {
return {
...errors,
[file.name]: { ...file.validationErrors },
};
}
return errors;
}, {});
// validation errors form
if (this.form.errors !== null) {
validationErrors = { ...validationErrors, ...this.form.errors };
}
const errors = Object.keys(validationErrors).length ? validationErrors : null;
if (errors === null) {
this.state = NgxFileUploadState.IDLE;
return;
}
this.state = NgxFileUploadState.INVALID;
}
/**
* call hooks in order, see playground
* @see https://rxviz.com/v/58GkkYv8
*/
get beforeStartHook$() {
const initialState = this.model.state;
let hook$ = of(true);
if (this.hooks.beforeStart.length) {
const hooks = this.hooks.beforeStart.map((beforeStartFn) => {
let hook = beforeStartFn(this);
if (!isObservable(hook)) {
hook = from(Promise.resolve(hook));
}
return hook;
});
// push hooks into a stream so everyone is called
hook$ = concat(...hooks).pipe(bufferCount(this.hooks.beforeStart.length), map$1((result) => result.every((isAllowed) => isAllowed)), tap(() => (this.model.state !== initialState ? this.notifyObservers() : void 0)));
}
return hook$;
}
createForm() {
const form = new NgxFileUploadForm(this);
for (const [name, ctrlOptions] of Object.entries(this.options.formControls ?? {})) {
form.addControl(name, ctrlOptions);
}
return form;
}
/**
* build form data and send request to server
*/
startUploadRequest() {
const uploadBody = this.createUploadBody();
const headers = this.createUploadHeaders();
/**
* save size on start so we do not call it every time
* since this running a reduce loop, and the size will not change
* anymore after we start it
*/
this.totalSize = this.model.size;
return this.http
.post(this.url, uploadBody, {
reportProgress: true,
withCredentials: this.options.withCredentials,
observe: 'events',
headers,
})
.pipe(takeUntil(merge(this.cancel$, this.destroyed$)));
}
/**
* create upload body which will should be send
*/
createUploadBody() {
if (this.options.transferMethod === 'formdata') {
const formData = new FormData();
this.model.files.forEach((file) => {
formData.append('files', file.raw, file.name);
});
// add form controls
for (const [name, ctrl] of this.form.controls()) {
if (ctrl.send === false) {
continue;
}
formData.append(name, JSON.stringify(ctrl.value));
}
return formData;
}
// we have to add header how the file is named
// since transfer method is body we can only upload 1 file
return this.model.files[0].raw;
}
/**
* create upload request headers
*/
createUploadHeaders() {
let headers = new HttpHeaders();
/** if transfer method is body send all form data through headers */
if (this.options.transferMethod === 'body') {
for (const [name, ctrl] of this.form.controls()) {
if (ctrl.send === false) {
continue;
}
let headerName = name;
if (this.options.formControlNameToKebabCase) {
headerName = name
// transform fooBar to Foo-Bar-
.replace(/(?:(\w)([^A-Z]+))/g, (_full, first, rest) => {
return first.toUpperCase().concat(rest, '-');
})
// remove trailing -
.slice(0, -1);
}
headers = headers.append(headerName, JSON.stringify(ctrl.value));
}
}
if (this.headers.authorization) {
headers = this.createAuthroizationHeader(headers);
}
/** add additional headers which should send */
Object.keys(this.headers)
.filter((header) => header !== 'authorization')
.forEach((header) => (headers = headers.append(header, this.headers[header])));
return headers;
}
/**
* create authorization header which will send
*/
createAuthroizationHeader(headers) {
const authHeader = this.headers.authorization;
if (authHeader) {
if (typeof authHeader === 'string') {
headers = headers.append('Authorization', `Bearer ${authHeader}`);
}
else {
headers = headers.append('Authorization', `${authHeader.key ?? 'Bearer'} ${authHeader.token}`);
}
}
return headers;
}
/**
* request responds with an error
*/
handleError(response) {
this.model.state = NgxFileUploadState.COMPLETED;
this.model.response = response;
this.model.hasError = true;
this.notifyObservers();
}
/**
* handle all http events
*/
handleHttpEvent(event) {
switch (event.type) {
case HttpEventType.UploadProgress:
this.handleProgress(event);
break;
case HttpEventType.Response:
this.handleResponse(event);
break;
}
}
/**
* handle http progress event
*/
handleProgress(event) {
const loaded = event.loaded;
const progress = (loaded * 100) / this.totalSize;
this.model.state = NgxFileUploadState.PROGRESS;
/**
* for some reason the upload is sometimes a bit bigger then the files,
* pretty sure this happens because of headers which are send makes the request a bit
* bigger
*/
this.model.uploaded = Math.min(loaded, this.totalSize);
this.model.progress = Math.min(Math.round(progress), 100);
this.notifyObservers();
}
/**
* upload completed with an success
*/
handleResponse(res) {
this.model.response = res;
this.model.state = NgxFileUploadState.COMPLETED;
this.notifyObservers();
this.finalizeUpload();
}
/**
* send notification to observers
*/
notifyObservers() {
this.change$.next(this.data);
}
/**
* upload has been completed, canceled or destroyed
*/
finalizeUpload() {
this.change$.complete();
this.cancel$.complete();
}
}
class NgxFileUploadFile {
constructor(file) {
this.validationErrors = null;
this.raw = file;
this.size = file.size;
this.type = file.type;
this.name = file.name;
}
}
/**
* Factory to create upload requests
*/
class Factory {
/**
* construct upload factory
*/
constructor(httpClient) {
this.httpClient = httpClient;
}
createUploadRequest(file, options, validator) {
const files = Array.isArray(file) ? file : [file];
if (files.length) {
const fileModels = files.map((file) => {
const model = new NgxFileUploadFile(file);
if (validator) {
model.validationErrors = 'validate' in validator ? validator.validate(file) : validator(file);
}
return model;
});
// * create one requests which holds all files
return new NgxFileUploadRequest(this.httpClient, fileModels, options);
}
return null;
}
}
/**
* InjectionToken for NgxFileuploadFactory
*/
const NgxFileUploadFactory = new InjectionToken('Ngx Fileupload Factory', {
providedIn: 'root',
factory: () => new Factory(inject(HttpClient)),
});
class NgxFileUploadQueue {
constructor() {
this.active = 0;
this.queuedUploads = [];
this.concurrentCount = -1;
this.observedUploads = new WeakSet();
this.queue$ = new Subject();
this.queue$.subscribe((request) => {
request.state = NgxFileUploadState.PENDING;
this.writeToQueue(request);
});
}
set concurrent(count) {
this.concurrentCount = count;
}
register(upload) {
this.registerUploadChange(upload);
}
/**
* register to upload change
*/
registerUploadChange(request) {
if (!this.observedUploads.has(request)) {
this.observedUploads.add(request);
const change$ = request.change;
/** register for changes which make request complete */
const uploadComplete$ = change$.pipe(filter(() => request.isCompleted(true)), take(1));
change$
.pipe(takeUntil(merge(request.destroyed, uploadComplete$)), distinctUntilChanged(), filter((data) => data.state === NgxFileUploadState.START))
.subscribe({
next: () => this.queue$.next(request),
complete: () => this.requestCompleted(request),
});
}
}
writeToQueue(request) {
if (this.active < this.concurrentCount || this.concurrentCount === -1) {
this.runRequest(request);
return;
}
this.queuedUploads = [...this.queuedUploads, request];
}
runRequest(request) {
this.active += 1;
request.state = NgxFileUploadState.CONNECT;
request.run();
}
/**
* requests gets completed, this means request is pending or was progressing and the user
* cancel request, remove it or even destroys them
*/
requestCompleted(request) {
if (this.isInUploadQueue(request)) {
this.removeFromQueue(request);
}
else {
this.startNextInQueue();
}
this.observedUploads.delete(request);
}
/**
* checks upload is in queue
*/
isInUploadQueue(request) {
return this.queuedUploads.indexOf(request) > -1;
}
/**
* remove upload request from queued uploads
*/
removeFromQueue(request) {
this.queuedUploads = this.queuedUploads.filter((upload) => upload !== request);
}
startNextInQueue() {
this.active = Math.max(this.active - 1, 0);
if (this.queuedUploads.length > 0) {
const nextUpload = this.queuedUploads.shift();
this.runRequest(nextUpload);
}
}
}
const defaultStoreConfig = {
concurrentUploads: 5,
autoStart: false,
};
class NgxFileUploadStorage {
constructor(config) {
this.uploads = new Map();
this.destroyed$ = new Subject();
this.bulkProcess = [];
this.change$ = new ReplaySubject(1);
this.uploadQueue = new NgxFileUploadQueue();
this.storeConfig = { ...defaultStoreConfig, ...(config || {}) };
this.uploadQueue.concurrent = this.storeConfig.concurrentUploads;
}
/**
* submits if any upload changes his state, uploads
* gets removed or added
*/
change() {
return this.change$;
}
/**
* add new upload to store
*/
add(upload) {
const requests = Array.isArray(upload) ? upload : [upload];
requests.forEach((request) => {
if (request.requestId && this.uploads.has(request.requestId)) {
return;
}
request.requestId = request.requestId || this.generateUniqeRequestId();
this.uploads.set(request.requestId, request);
// @todo better interface we only want run method but we do not want publish it
// better strategy for this ?
this.registerUploadEvents(request);
});
this.afterUploadsAdd(requests);
this.notifyObserver();
}
destroy() {
/** remove from all subscriptions */
this.destroyed$.next(true);
/** stop all downloads */
this.stopAll();
/** destroy change stream */
this.destroyed$.complete();
this.change$.complete();
}
remove(upload) {
const id = typeof upload === 'string' ? upload : upload.requestId;
const request = this.uploads.get(id);
request?.destroy();
}
purge() {
let notify = false;
this.uploads.forEach((request) => {
if (request.isCompleted() || request.isInvalid()) {
this.bulkProcess.push(request.requestId);
notify = true;
request.destroy();
}
});
if (notify) {
this.notifyObserver();
}
}
startAll() {
this.uploads.forEach((upload) => {
// zu schnell
if (upload.isIdle()) {
this.bulkProcess.push(upload.requestId);
upload.start();
}
});
this.notifyObserver();
}
stopAll() {
this.uploads.forEach((upload) => {
this.bulkProcess.push(upload.requestId);
upload.destroy();
});
this.notifyObserver();
}
removeInvalid() {
this.uploads.forEach((upload) => {
if (upload.isInvalid()) {
this.bulkProcess.push(upload.requestId);
upload.destroy();
}
});
this.notifyObserver();
}
registerUploadEvents(request) {
if (!request.isInvalid()) {
this.queueRequest(request);
}
else {
request.change
.pipe(filter((data) => data.state === NgxFileUploadState.IDLE), take(1), takeUntil(request.destroyed))
.subscribe(() => {
this.queueRequest(request);
this.notifyObserver();
});
}
request.destroyed
.pipe(tap(() => this.uploads.delete(request.requestId)), take(1))
.subscribe(() => (this.isBulkProcess(request) ? this.removeBulkProcess(request) : this.notifyObserver()));
}
queueRequest(request) {
this.uploadQueue.register(request);
this.handleRequestChange(request);
}
/**
* @description register to request change events, this will notify all observers
* if state from upload state has been changed, this will not notify
* if amount of uploaded size has been changed
*/
handleRequestChange(request) {
const isAutoRemove = !!(this.storeConfig.removeCompleted ?? 0);
request.change
.pipe(distinctUntilKeyChanged('state'), tap(() => /** do not notify if bulk process */ {
this.isBulkProcess(request) ? this.removeBulkProcess(request) : this.notifyObserver();
}),
/* only continue if completed with no errors and autoremove is enabled */
filter(() => request.data.state === NgxFileUploadState.COMPLETED && !request.hasError() && isAutoRemove),
/** wait for given amount of time before we remove item */
switchMap(() => timer(this.storeConfig.removeCompleted ?? 0)),
/* automatically unsubscribe if request gets destroyed */
takeUntil(request.destroyed))
.subscribe(() => this.remove(request));
}
afterUploadsAdd(requests) {
if (this.storeConfig.autoStart) {
requests.forEach((uploadRequest) => uploadRequest.start());
}
}
generateUniqeRequestId() {
let reqId;
do {
reqId = Array.from({ length: 4 }, () => Math.random().toString(32).slice(2)).join('-');
} while (this.uploads.has(reqId));
return reqId;
}
notifyObserver() {
this.change$.next(Array.from(this.uploads.values()));
}
removeBulkProcess(request) {
this.bulkProcess = this.bulkProcess.filter((id) => request.requestId !== id);
}
isBulkProcess(request) {
return this.bulkProcess.indexOf(request.requestId) > -1;
}
}
class NgxFileUploadGroupedvalidator {
constructor(validators) {
this.validators = Array.isArray(validators) ? validators : [];
}
/**
* add validators
*/
add(...validators) {
this.validators = this.validators.concat(validators);
}
/**
* clean up all validators
*/
clean() {
this.validators = [];
}
/**
* executes validator and returns validation result
*/
execValidator(validator, file) {
/** we handle a validator class directly */
if ("validate" in validator) {
return validator.validate(file);
}
/** we handle a validation function */
return validator(file);
}
}
class NgxFileUploadAndValidator extends NgxFileUploadGroupedvalidator {
validate(file) {
const validationResult = {};
let hasErrors = false;
for (const validator of this.validators) {
const result = this.execValidator(validator, file);
if (result !== null) {
Object.assign(validationResult, result);
hasErrors = true;
}
}
return hasErrors ? validationResult : null;
}
}
class NgxFileUploadOrValidator extends NgxFileUploadGroupedvalidator {
validate(file) {
let validationResult = {};
for (const validator of this.validators) {
const result = this.execValidator(validator, file);
if (result === null) {
validationResult = null;
break;
}
Object.assign(validationResult, result);
}
return validationResult;
}
}
class NgxFileUploadValidationBuilder {
static and(...validators) {
return new NgxFileUploadAndValidator(validators);
}
static or(...validators) {
return new NgxFileUploadOrValidator(validators);
}
}
/*
* Public API Surface of core
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxFileUploadAndValidator, NgxFileUploadCoreModule, NgxFileUploadFactory, NgxFileUploadFile, NgxFileUploadForm, NgxFileUploadGroupedvalidator, NgxFileUploadOrValidator, NgxFileUploadQueue, NgxFileUploadRequest, NgxFileUploadState, NgxFileUploadStorage, NgxFileUploadValidationBuilder };
//# sourceMappingURL=ngx-file-upload-core.mjs.map