Compare commits

..

4 Commits

8 changed files with 108 additions and 21 deletions

View File

@ -1,5 +1,18 @@
# Changelog
## 2024-11-24 - 3.3.0 - feat(core)
Enhanced directory handling and file restoration from trash
- Refined getSubDirectoryByName to handle file paths treated as directories.
- Introduced file restoration function from trash to original or specified paths.
## 2024-11-24 - 3.2.2 - fix(core)
Refactor Bucket class for improved error handling
- Ensured safe access using non-null assertions when finding a bucket.
- Enhanced fastPut method by adding fastPutStrict for safer operations.
- Added explicit error handling and type checking in fastExists method.
## 2024-11-24 - 3.2.1 - fix(metadata)
Fix metadata handling for deleted files

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@push.rocks/smartbucket",
"version": "3.2.1",
"version": "3.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@push.rocks/smartbucket",
"version": "3.2.1",
"version": "3.3.0",
"license": "UNLICENSED",
"dependencies": {
"@push.rocks/smartpath": "^5.0.18",

View File

@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartbucket",
"version": "3.2.1",
"version": "3.3.0",
"description": "A TypeScript library offering simple and cloud-agnostic object storage with advanced features like bucket creation, file and directory management, and data streaming.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",

View File

@ -30,7 +30,7 @@ tap.test('should clean all contents', async () => {
tap.test('should delete a file into the normally', async () => {
const path = 'trashtest/trashme.txt';
const file = await myBucket.fastPut({
const file = await myBucket.fastPutStrict({
path,
contents: 'I\'m in the trash test content!',
});
@ -44,7 +44,7 @@ tap.test('should delete a file into the normally', async () => {
tap.test('should put a file into the trash', async () => {
const path = 'trashtest/trashme.txt';
const file = await myBucket.fastPut({
const file = await myBucket.fastPutStrict({
path,
contents: 'I\'m in the trash test content!',
});
@ -60,4 +60,19 @@ tap.test('should put a file into the trash', async () => {
});
});
tap.test('should restore a file from trash', async () => {
const baseDirectory = await myBucket.getBaseDirectory();
const file = await baseDirectory.getFileStrict({
path: 'trashtest/trashme.txt',
getFromTrash: true
});
const trashFileMeta = await file.getMetaData();
const data = await trashFileMeta.getCustomMetaData({
key: 'recycle'
});
expect(file).toBeInstanceOf(smartbucket.File);
await file.restore();
});
export default tap.start();

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartbucket',
version: '3.2.1',
version: '3.3.0',
description: 'A TypeScript library offering simple and cloud-agnostic object storage with advanced features like bucket creation, file and directory management, and data streaming.'
}

View File

@ -17,7 +17,7 @@ export class Bucket {
public static async getBucketByName(smartbucketRef: SmartBucket, bucketNameArg: string) {
const command = new plugins.s3.ListBucketsCommand({});
const buckets = await smartbucketRef.s3Client.send(command);
const foundBucket = buckets.Buckets.find((bucket) => bucket.Name === bucketNameArg);
const foundBucket = buckets.Buckets!.find((bucket) => bucket.Name === bucketNameArg);
if (foundBucket) {
console.log(`bucket with name ${bucketNameArg} exists.`);
@ -88,14 +88,15 @@ export class Bucket {
contents: string | Buffer;
overwrite?: boolean;
}
): Promise<File> {
): Promise<File | null> {
try {
const reducedPath = await helpers.reducePathDescriptorToPath(optionsArg);
const exists = await this.fastExists({ path: reducedPath });
if (exists && !optionsArg.overwrite) {
console.error(`Object already exists at path '${reducedPath}' in bucket '${this.name}'.`);
return;
const errorText = `Object already exists at path '${reducedPath}' in bucket '${this.name}'.`;
console.error(errorText);
return null;
} else if (exists && optionsArg.overwrite) {
console.log(
`Overwriting existing object at path '${reducedPath}' in bucket '${this.name}'.`
@ -128,6 +129,14 @@ export class Bucket {
}
}
public async fastPutStrict(...args: Parameters<Bucket['fastPut']>) {
const file = await this.fastPut(...args);
if (!file) {
throw new Error(`File not stored at path '${args[0].path}'`);
}
return file;
}
/**
* get file
*/
@ -152,7 +161,7 @@ export class Bucket {
},
});
await done.promise;
return completeFile;
return completeFile!;
}
/**
@ -220,7 +229,7 @@ export class Bucket {
return chunk;
},
finalFunction: async (cb) => {
return null;
return null!;
},
});
@ -392,8 +401,8 @@ export class Bucket {
await this.smartbucketRef.s3Client.send(command);
console.log(`Object '${optionsArg.path}' exists in bucket '${this.name}'.`);
return true;
} catch (error) {
if (error.name === 'NotFound') {
} catch (error: any) {
if (error?.name === 'NotFound') {
console.log(`Object '${optionsArg.path}' does not exist in bucket '${this.name}'.`);
return false;
} else {

View File

@ -10,9 +10,9 @@ export class Directory {
public parentDirectoryRef: Directory;
public name: string;
public tree: string[];
public files: string[];
public folders: string[];
public tree!: string[];
public files!: string[];
public folders!: string[];
constructor(bucketRefArg: Bucket, parentDirectory: Directory, name: string) {
this.bucketRef = bucketRefArg;
@ -192,9 +192,22 @@ export class Directory {
* gets a sub directory by name
*/
public async getSubDirectoryByName(dirNameArg: string, optionsArg: {
/**
* in s3 a directory does not exist if it is empty
* this option returns a directory even if it is empty
*/
getEmptyDirectory?: boolean;
/**
* in s3 a directory does not exist if it is empty
* this option creates a directory even if it is empty using a initializer file
*/
createWithInitializerFile?: boolean;
/**
* if the path is a file path, it will be treated as a file and the parent directory will be returned
*/
couldBeFilePath?: boolean;
} = {}): Promise<Directory | null> {
const dirNameArray = dirNameArg.split('/').filter(str => str.trim() !== "");
optionsArg = {
@ -221,8 +234,19 @@ export class Directory {
return returnDirectory || null;
};
if (optionsArg.couldBeFilePath) {
const baseDirectory = await this.bucketRef.getBaseDirectory();
const existingFile = await baseDirectory.getFile({
path: dirNameArg,
});
if (existingFile) {
const adjustedPath = dirNameArg.substring(0, dirNameArg.lastIndexOf('/'));
return this.getSubDirectoryByName(adjustedPath);
}
}
let wantedDirectory: Directory | null = null;
let counter = 0;
let counter = 0;
for (const dirNameToSearch of dirNameArray) {
counter++;
const directoryToSearchIn = wantedDirectory ? wantedDirectory : this;
@ -336,7 +360,7 @@ export class Directory {
*/
mode?: 'permanent' | 'trash';
}) {
const file = await this.getFile({
const file = await this.getFileStrict({
path: optionsArg.path,
});
await file.delete({

View File

@ -130,6 +130,29 @@ export class File {
await this.parentDirectoryRef.listFiles();
}
/**
* restores
*/
public async restore(optionsArg: {
useOriginalPath?: boolean;
toPath?: string;
overwrite?: boolean;
} = {}) {
optionsArg = {
useOriginalPath: (() => {
return optionsArg.toPath ? false : true;
})(),
overwrite: false,
...optionsArg,
};
const moveToPath = optionsArg.toPath || (await (await this.getMetaData()).getCustomMetaData({
key: 'recycle'
})).originalPath;
await this.move({
path: moveToPath,
})
}
/**
* allows locking the file
* @param optionsArg
@ -154,7 +177,7 @@ export class File {
}) {
const metadata = await this.getMetaData();
await metadata.removeLock({
force: optionsArg?.force,
force: optionsArg?.force || false,
});
}
@ -219,7 +242,10 @@ export class File {
// lets update references of this
const baseDirectory = await this.parentDirectoryRef.bucketRef.getBaseDirectory();
this.parentDirectoryRef = await baseDirectory.getSubDirectoryByNameStrict(
pathDescriptorArg.directory?.getBasePath()!
await helpers.reducePathDescriptorToPath(pathDescriptorArg),
{
couldBeFilePath: true,
}
);
this.name = pathDescriptorArg.path!;
}