@flystorage/local-fs
Version:
<img src="https://raw.githubusercontent.com/duna-oss/flystorage/main/flystorage.svg" width="50px" height="50px" />
249 lines (248 loc) • 10.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalFileStorage = exports.LocalStorageAdapter = exports.FailingLocalTemporaryUrlGenerator = exports.BaseUrlLocalPublicUrlGenerator = void 0;
const file_storage_1 = require("@flystorage/file-storage");
const mime_types_1 = require("mime-types");
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const promises_2 = require("stream/promises");
const unix_visibility_js_1 = require("./unix-visibility.js");
const dynamic_import_1 = require("@flystorage/dynamic-import");
const file_storage_2 = require("@flystorage/file-storage");
class BaseUrlLocalPublicUrlGenerator {
async publicUrl(path, options) {
if (options.baseUrl === undefined) {
throw new Error('No base URL defined for public URL generation');
}
const base = options.baseUrl.endsWith('/') ? options.baseUrl : `${options.baseUrl}/`;
if (node_path_1.posix.sep === '\\' && path.includes(node_path_1.posix.sep)) {
path = path.replace(node_path_1.posix.sep, '/');
}
return `${base}${path}`;
}
}
exports.BaseUrlLocalPublicUrlGenerator = BaseUrlLocalPublicUrlGenerator;
class FailingLocalTemporaryUrlGenerator {
async temporaryUrl() {
throw new Error('No temporary URL generator provided');
}
}
exports.FailingLocalTemporaryUrlGenerator = FailingLocalTemporaryUrlGenerator;
let fileTypeImport;
let fileTypes = undefined;
class LocalStorageAdapter {
rootDir;
options;
visibilityConversion;
publicUrlGenerator;
temporaryUrlGenerator;
uploadPreparer;
prefixer;
constructor(rootDir, options = {}, visibilityConversion = new unix_visibility_js_1.PortableUnixVisibilityConversion(), publicUrlGenerator = new BaseUrlLocalPublicUrlGenerator(), temporaryUrlGenerator = new FailingLocalTemporaryUrlGenerator(), uploadPreparer = new file_storage_2.PreparedUploadsAreNotSupported()) {
this.rootDir = rootDir;
this.options = options;
this.visibilityConversion = visibilityConversion;
this.publicUrlGenerator = publicUrlGenerator;
this.temporaryUrlGenerator = temporaryUrlGenerator;
this.uploadPreparer = uploadPreparer;
this.rootDir = node_path_1.posix.join(this.rootDir, node_path_1.posix.sep);
this.prefixer = new file_storage_1.PathPrefixer(this.rootDir, node_path_1.posix.sep, node_path_1.posix.join);
}
async copyFile(from, to, options) {
await this.ensureRootDirectoryExists();
await this.ensureParentDirectoryExists(to, options);
await (0, promises_1.copyFile)(this.prefixer.prefixFilePath(from), this.prefixer.prefixFilePath(to));
}
async moveFile(from, to, options) {
await this.ensureRootDirectoryExists();
await this.ensureParentDirectoryExists(to, options);
await (0, promises_1.rename)(this.prefixer.prefixFilePath(from), this.prefixer.prefixFilePath(to));
}
prepareUpload(path, options) {
return this.uploadPreparer.prepareUpload(path, options);
}
temporaryUrl(path, options) {
return this.temporaryUrlGenerator.temporaryUrl(path, { ...this.options.temporaryUrlOptions, ...options });
}
publicUrl(path, options) {
return this.publicUrlGenerator.publicUrl(path, { ...this.options.publicUrlOptions, ...options });
}
async mimeType(path, options) {
if (fileTypeImport === undefined) {
fileTypeImport = (0, dynamic_import_1.dynamicallyImport)('file-type');
}
if (fileTypes === undefined) {
fileTypes = await fileTypeImport;
}
const { fileTypeFromFile, supportedExtensions } = fileTypes;
const extension = (0, node_path_1.extname)(path);
if (!supportedExtensions.has(extension)) {
const mimetype = (0, mime_types_1.lookup)(extension);
if (mimetype === false) {
throw new Error('Unable to resolve mime-type');
}
return mimetype;
}
const location = this.prefixer.prefixFilePath(path);
const result = await fileTypeFromFile(location);
if (result === undefined) {
throw new Error('Unable to resolve mime-type');
}
return result.mime;
}
async fileSize(path) {
const stat = await this.stat(path);
if (!stat.isFile) {
throw new Error(`Path ${path} is not a file.`);
}
if (stat.size === undefined) {
throw new Error('Stat unexpectedly did not return file size.');
}
return stat.size;
}
async lastModified(path) {
const stat = await this.stat(path);
if (!stat.isFile) {
throw new Error(`Path ${path} is not a file.`);
}
if (stat.lastModifiedMs === undefined) {
throw new Error('Stat unexpectedly did not return last modified.');
}
return stat.lastModifiedMs;
}
async *list(path, { deep }) {
let entries = await (0, promises_1.opendir)(this.prefixer.prefixDirectoryPath(path), {
recursive: deep,
});
for await (const item of entries) {
const itemPath = node_path_1.posix.join(item.parentPath, item.name);
yield this.mapStatToEntry(item, item.isFile()
? this.prefixer.stripFilePath(itemPath)
: this.prefixer.stripDirectoryPath(itemPath));
}
}
async read(path) {
return (0, node_fs_1.createReadStream)(this.prefixer.prefixFilePath(path));
}
async write(path, contents, options) {
await this.ensureRootDirectoryExists();
await this.ensureParentDirectoryExists(path, options);
const writeStream = (0, node_fs_1.createWriteStream)(this.prefixer.prefixFilePath(path), {
flags: 'w+',
mode: options.visibility
? this.visibilityConversion.visibilityToFilePermissions(options.visibility)
: undefined,
});
await (0, promises_2.pipeline)(contents, writeStream);
}
async deleteFile(path) {
await (0, promises_1.rm)(this.prefixer.prefixFilePath(path), {
force: true,
});
}
async createDirectory(path, options) {
await (0, promises_1.mkdir)(this.prefixer.prefixDirectoryPath(path), {
recursive: true,
mode: options.directoryVisibility
? this.visibilityConversion.visibilityToDirectoryPermissions(options.directoryVisibility)
: undefined,
});
}
async stat(path, type = 'file') {
return this.mapStatToEntry(await (0, promises_1.stat)(type === 'file'
? this.prefixer.prefixFilePath(path)
: this.prefixer.prefixDirectoryPath(path)), path);
}
async fileExists(path) {
try {
const stat = await this.stat(path);
return stat.isFile;
}
catch (e) {
if (typeof e === 'object' && e.code === 'ENOENT') {
return false;
}
throw e;
}
}
async deleteDirectory(path) {
await (0, promises_1.rm)(this.prefixer.prefixDirectoryPath(path), {
recursive: true,
force: true,
});
}
mapStatToEntry(info, path) {
if (!info.isFile() && !info.isDirectory()) {
throw new Error('Unsupported file entry encountered...');
}
const isDirent = info instanceof node_fs_1.Dirent;
return info.isFile() ? {
path,
type: 'file',
isFile: true,
isDirectory: false,
visibility: isDirent ? undefined : this.visibilityConversion.filePermissionsToVisibility(info.mode & 0o777),
lastModifiedMs: isDirent ? undefined : info.mtimeMs,
size: isDirent ? undefined : info.size,
} : {
path,
type: 'directory',
isFile: false,
isDirectory: true,
visibility: isDirent ? undefined : this.visibilityConversion.directoryPermissionsToVisibility(info.mode & 0o777),
lastModifiedMs: isDirent ? undefined : info.mtimeMs,
};
}
async changeVisibility(path, visibility) {
await (0, promises_1.chmod)(this.prefixer.prefixFilePath(path), this.visibilityConversion.visibilityToFilePermissions(visibility));
}
async visibility(path) {
const stat = await this.stat(path);
if (!stat.visibility) {
throw new Error('Unable to determine visibility');
}
return stat.visibility;
}
async directoryExists(path) {
try {
const stat = await this.stat(path, 'directory');
return stat.isDirectory;
}
catch (e) {
if (typeof e === 'object' && ['ENOTDIR', 'ENOENT'].includes(e.code)) {
return false;
}
throw e;
}
}
rootDirectoryCreation = undefined;
async ensureRootDirectoryExists() {
if (this.rootDirectoryCreation === undefined) {
this.rootDirectoryCreation = this.createDirectory('', {
directoryVisibility: this.options.rootDirectoryVisibility ?? this.visibilityConversion.defaultDirectoryVisibility,
});
}
return await this.rootDirectoryCreation;
}
async ensureParentDirectoryExists(path, options) {
const directoryName = node_path_1.posix.dirname(path);
if (directoryName !== '.' && directoryName !== '/') {
await this.createDirectory(directoryName, {
directoryVisibility: options.directoryVisibility,
});
}
}
async checksum(path, options) {
return (0, file_storage_1.checksumFromStream)(await this.read(path), options);
}
}
exports.LocalStorageAdapter = LocalStorageAdapter;
/**
* BC export
*
* @deprecated
*/
class LocalFileStorage extends LocalStorageAdapter {
}
exports.LocalFileStorage = LocalFileStorage;