Compare commits

...

19 Commits

Author SHA1 Message Date
9629a04da6 3.0.13 2024-06-09 16:32:33 +02:00
963463d40d fix(core): update 2024-06-09 16:32:32 +02:00
ce58b99fc7 3.0.12 2024-06-09 16:02:34 +02:00
591c99736d fix(core): update 2024-06-09 16:02:33 +02:00
559e3da47b 3.0.11 2024-06-08 19:13:25 +02:00
a7ac870e05 fix(core): update 2024-06-08 19:13:24 +02:00
d48c5e229a 3.0.10 2024-06-03 21:35:08 +02:00
b9c384dd08 fix(core): update 2024-06-03 21:35:08 +02:00
91c04b2364 update description 2024-05-29 14:11:54 +02:00
b5dcc131e2 3.0.9 2024-05-27 17:34:27 +02:00
cb0ab2c9db fix(core): update 2024-05-27 17:34:26 +02:00
2a17ee542e 3.0.8 2024-05-27 14:34:13 +02:00
95e9d2f0ff fix(core): update 2024-05-27 14:34:12 +02:00
1a71c76da3 3.0.7 2024-05-27 12:56:26 +02:00
e924511147 fix(s3 paths): pathing differences now correctly handled in a reducePath method. 2024-05-27 12:56:25 +02:00
645ebbdd4d 3.0.6 2024-05-21 18:47:00 +02:00
168148b2c9 fix(core): update 2024-05-21 18:46:59 +02:00
1293fc4ca6 3.0.5 2024-05-21 18:42:55 +02:00
b040120813 fix(core): update 2024-05-21 18:42:55 +02:00
14 changed files with 492 additions and 181 deletions

View File

@ -8,25 +8,28 @@
"githost": "code.foss.global",
"gitscope": "push.rocks",
"gitrepo": "smartbucket",
"description": "A TypeScript library that offers simple, cloud-independent object storage with features like bucket creation, file management, and directory management.",
"description": "A TypeScript library offering simple and cloud-agnostic object storage with advanced features like bucket creation, file and directory management, and data streaming.",
"npmPackagename": "@push.rocks/smartbucket",
"license": "MIT",
"keywords": [
"TypeScript",
"cloud storage",
"object storage",
"TypeScript",
"S3",
"minio",
"bucket creation",
"file management",
"directory management",
"bucket creation",
"data streaming",
"multi-cloud",
"API",
"unified storage",
"S3",
"minio",
"file locking",
"metadata",
"buffer handling",
"access key",
"secret key"
"secret key",
"cloud agnostic"
]
}
},

4
package-lock.json generated
View File

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

View File

@ -1,7 +1,7 @@
{
"name": "@push.rocks/smartbucket",
"version": "3.0.4",
"description": "A TypeScript library that offers simple, cloud-independent object storage with features like bucket creation, file management, and directory management.",
"version": "3.0.13",
"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",
"type": "module",
@ -19,12 +19,13 @@
"@push.rocks/tapbundle": "^5.0.23"
},
"dependencies": {
"@push.rocks/smartmime": "^2.0.0",
"@push.rocks/smartmime": "^2.0.2",
"@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.0.3",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartstream": "^3.0.38",
"@tsclass/tsclass": "^4.0.54",
"@push.rocks/smartstream": "^3.0.44",
"@push.rocks/smartunique": "^3.0.9",
"@tsclass/tsclass": "^4.0.55",
"minio": "^8.0.0"
},
"private": false,
@ -44,20 +45,28 @@
"last 1 chrome versions"
],
"keywords": [
"TypeScript",
"cloud storage",
"object storage",
"TypeScript",
"S3",
"minio",
"bucket creation",
"file management",
"directory management",
"bucket creation",
"data streaming",
"multi-cloud",
"API",
"unified storage",
"S3",
"minio",
"file locking",
"metadata",
"buffer handling",
"access key",
"secret key"
]
"secret key",
"cloud agnostic"
],
"homepage": "https://code.foss.global/push.rocks/smartbucket",
"repository": {
"type": "git",
"url": "https://code.foss.global/push.rocks/smartbucket.git"
}
}

78
pnpm-lock.yaml generated
View File

@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@push.rocks/smartmime':
specifier: ^2.0.0
version: 2.0.0
specifier: ^2.0.2
version: 2.0.2
'@push.rocks/smartpath':
specifier: ^5.0.18
version: 5.0.18
@ -21,11 +21,14 @@ importers:
specifier: ^3.0.7
version: 3.0.7
'@push.rocks/smartstream':
specifier: ^3.0.38
version: 3.0.38
specifier: ^3.0.44
version: 3.0.44
'@push.rocks/smartunique':
specifier: ^3.0.9
version: 3.0.9
'@tsclass/tsclass':
specifier: ^4.0.54
version: 4.0.54
specifier: ^4.0.55
version: 4.0.55
minio:
specifier: ^8.0.0
version: 8.0.0
@ -35,10 +38,10 @@ importers:
version: 2.1.80
'@git.zone/tsrun':
specifier: ^1.2.46
version: 1.2.46(@types/node@20.12.12)
version: 1.2.46(@types/node@20.14.2)
'@git.zone/tstest':
specifier: ^1.0.90
version: 1.0.90(@types/node@20.12.12)
version: 1.0.90(@types/node@20.14.2)
'@push.rocks/qenv':
specifier: ^6.0.5
version: 6.0.5
@ -412,8 +415,8 @@ packages:
'@push.rocks/smartmime@1.0.6':
resolution: {integrity: sha512-PHd+I4UcsnOATNg8wjDsSAmmJ4CwQFrQCNzd0HSJMs4ZpiK3Ya91almd6GLpDPU370U4HFh4FaPF4eEAI6vkJQ==}
'@push.rocks/smartmime@2.0.0':
resolution: {integrity: sha512-yNEYrQzWjxwinCT8djw9eFumpCIvIQQS9KXWLH0LT9COlFoaP/ruk7pogrGYKCo20tFITJyO6NmMCa24402rvA==}
'@push.rocks/smartmime@2.0.2':
resolution: {integrity: sha512-aXH1sFD73q9cEwPdeSeN7Zxd2aoVt9wE97ILFCW5gORylvm85Hgfq7SYkqykjQzEL8IDJKJF3G78+xcL2rALTg==}
'@push.rocks/smartnetwork@3.0.2':
resolution: {integrity: sha512-s6CNGzQ1n/d/6cOKXbxeW6/tO//dr1woLqI01g7XhqTriw0nsm2G2kWaZh2J0VOguGNWBgQVCIpR0LjdRNWb3g==}
@ -467,8 +470,8 @@ packages:
'@push.rocks/smartstream@2.0.8':
resolution: {integrity: sha512-GlF/9cCkvBHwKa3DK4DO5wjfSgqkj6gAS4TrY9uD5NMHu9RQv4WiNrElTYj7iCEpnZgUnLO3tzw1JA3NRIMnnA==}
'@push.rocks/smartstream@3.0.38':
resolution: {integrity: sha512-Sk9esPURWXldS0ZvgClCtrEyvELjvFnbQgUAelwoXWMfM8pXuB9BX1tE+Z1iBkB9Xyw2p1d9jYelO6waSXg0BQ==}
'@push.rocks/smartstream@3.0.44':
resolution: {integrity: sha512-BZwSUmhVD/CEzRdG/e2UachlyNjshiknqBnwHzIFPIjVKC66UgWX5lYCs2LOwJtuUEpfL1vTMOAYUFNcmPjW9A==}
'@push.rocks/smartstring@4.0.15':
resolution: {integrity: sha512-NTNeOjWyg+aHtBTiQEyXamr7oTvYZ3wS1fudHo9ua7CLrykpK+i+RxFyJaLg1zB5x9xQF3NLEQecB14HPFX8Cg==}
@ -621,8 +624,8 @@ packages:
'@tsclass/tsclass@3.0.48':
resolution: {integrity: sha512-hC65UvDlp9qvsl6OcIZXz0JNiWZ0gyzsTzbXpg215sGxopgbkOLCr6E0s4qCTnweYm95gt2AdY95uP7M7kExaQ==}
'@tsclass/tsclass@4.0.54':
resolution: {integrity: sha512-v+Xc7M0BKNT79/kx7S5Jsc17zj+acUuMDxJtKbQgdU4H8ke3aHAHJr2KicXJeXDTcn41ZEbwJPQ1cc+bjy8bZw==}
'@tsclass/tsclass@4.0.55':
resolution: {integrity: sha512-zg774JF90/3/rJ7xk4LyGgxcUzxdKIQcwtBVxez4LhvegESxvHiFmX42WL105iBpE53ISJ8sctLWlwG1JQZdlA==}
'@tsconfig/node10@1.0.11':
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@ -765,6 +768,9 @@ packages:
'@types/node@20.12.12':
resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
'@types/node@20.14.2':
resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==}
'@types/parse5@6.0.3':
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
@ -2821,8 +2827,8 @@ packages:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
type-fest@4.18.2:
resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==}
type-fest@4.20.0:
resolution: {integrity: sha512-MBh+PHUHHisjXf4tlx0CFWoMdjx8zCMLJHOjnV1prABYZFHqtFOyauCIK2/7w4oIfwkF8iNhLtnJEfVY2vn3iw==}
engines: {node: '>=16'}
type-is@1.6.18:
@ -3127,12 +3133,12 @@ snapshots:
'@push.rocks/smartrequest': 2.0.22
'@push.rocks/smartrx': 3.0.7
'@push.rocks/smartsitemap': 2.0.3
'@push.rocks/smartstream': 3.0.38
'@push.rocks/smartstream': 3.0.44
'@push.rocks/smarttime': 4.0.6
'@push.rocks/taskbuffer': 3.1.7
'@push.rocks/webrequest': 3.0.37
'@push.rocks/webstore': 2.0.17
'@tsclass/tsclass': 4.0.54
'@tsclass/tsclass': 4.0.55
'@types/express': 4.17.21
body-parser: 1.20.2
cors: 2.8.5
@ -3333,22 +3339,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@git.zone/tsrun@1.2.46(@types/node@20.12.12)':
'@git.zone/tsrun@1.2.46(@types/node@20.14.2)':
dependencies:
'@push.rocks/smartfile': 10.0.41
'@push.rocks/smartshell': 3.0.5
ts-node: 10.9.2(@types/node@20.12.12)(typescript@5.1.6)
ts-node: 10.9.2(@types/node@20.14.2)(typescript@5.1.6)
typescript: 5.1.6
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
'@git.zone/tstest@1.0.90(@types/node@20.12.12)':
'@git.zone/tstest@1.0.90(@types/node@20.14.2)':
dependencies:
'@api.global/typedserver': 3.0.37
'@git.zone/tsbundle': 2.0.15
'@git.zone/tsrun': 1.2.46(@types/node@20.12.12)
'@git.zone/tsrun': 1.2.46(@types/node@20.14.2)
'@push.rocks/consolecolor': 2.0.2
'@push.rocks/smartbrowser': 2.0.6
'@push.rocks/smartdelay': 3.0.5
@ -3597,7 +3603,7 @@ snapshots:
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartpromise': 4.0.3
'@push.rocks/smartrequest': 2.0.22
'@push.rocks/smartstream': 3.0.38
'@push.rocks/smartstream': 3.0.44
'@types/fs-extra': 11.0.4
'@types/glob': 8.1.0
'@types/js-yaml': 4.0.9
@ -3664,7 +3670,7 @@ snapshots:
'@types/mime-types': 2.1.4
mime-types: 2.1.35
'@push.rocks/smartmime@2.0.0':
'@push.rocks/smartmime@2.0.2':
dependencies:
'@types/mime-types': 2.1.4
file-type: 19.0.0
@ -3711,7 +3717,7 @@ snapshots:
'@push.rocks/smartpromise': 4.0.3
'@push.rocks/smartpuppeteer': 2.0.2
'@push.rocks/smartunique': 3.0.9
'@tsclass/tsclass': 4.0.54
'@tsclass/tsclass': 4.0.55
'@types/express': 4.17.21
express: 4.19.2
pdf-lib: 1.17.1
@ -3769,7 +3775,7 @@ snapshots:
'@push.rocks/smartxml': 1.0.8
'@push.rocks/smartyaml': 2.0.5
'@push.rocks/webrequest': 3.0.37
'@tsclass/tsclass': 4.0.54
'@tsclass/tsclass': 4.0.55
'@push.rocks/smartsocket@2.0.27':
dependencies:
@ -3821,12 +3827,12 @@ snapshots:
from2: 2.3.0
through2: 4.0.2
'@push.rocks/smartstream@3.0.38':
'@push.rocks/smartstream@3.0.44':
dependencies:
'@push.rocks/lik': 6.0.15
'@push.rocks/smartenv': 5.0.12
'@push.rocks/smartpromise': 4.0.3
'@push.rocks/smartrx': 3.0.7
'@push.rocks/webstream': 1.0.8
'@push.rocks/smartstring@4.0.15':
dependencies:
@ -3903,7 +3909,7 @@ snapshots:
dependencies:
'@pushrocks/smartdelay': 3.0.1
'@pushrocks/smartpromise': 4.0.2
'@tsclass/tsclass': 4.0.54
'@tsclass/tsclass': 4.0.55
'@push.rocks/webstore@2.0.17':
dependencies:
@ -4083,9 +4089,9 @@ snapshots:
dependencies:
type-fest: 2.19.0
'@tsclass/tsclass@4.0.54':
'@tsclass/tsclass@4.0.55':
dependencies:
type-fest: 4.18.2
type-fest: 4.20.0
'@tsconfig/node10@1.0.11': {}
@ -4250,6 +4256,10 @@ snapshots:
dependencies:
undici-types: 5.26.5
'@types/node@20.14.2':
dependencies:
undici-types: 5.26.5
'@types/parse5@6.0.3': {}
'@types/ping@0.4.4': {}
@ -6666,14 +6676,14 @@ snapshots:
trough@2.2.0: {}
ts-node@10.9.2(@types/node@20.12.12)(typescript@5.1.6):
ts-node@10.9.2(@types/node@20.14.2)(typescript@5.1.6):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.12.12
'@types/node': 20.14.2
acorn: 8.11.3
acorn-walk: 8.3.2
arg: 4.1.3
@ -6700,7 +6710,7 @@ snapshots:
type-fest@2.19.0: {}
type-fest@4.18.2: {}
type-fest@4.20.0: {}
type-is@1.6.18:
dependencies:

View File

@ -1,9 +1,10 @@
# @push.rocks/smartbucket
A TypeScript library for simple cloud independent object storage with support for buckets, directories, and files.
A TypeScript library for cloud-independent object storage, providing features like bucket creation, file and directory management, and data streaming.
## Install
To install `@push.rocks/smartbucket`, you need to have Node.js and npm (Node Package Manager) installed on your system. If you have them installed, you can add `@push.rocks/smartbucket` to your project by running the following command in your project's root directory:
To install `@push.rocks/smartbucket`, you need to have Node.js and npm (Node Package Manager) installed. If they are installed, you can add `@push.rocks/smartbucket` to your project by running the following command in your project's root directory:
```bash
npm install @push.rocks/smartbucket --save
@ -13,7 +14,7 @@ This command will download and install `@push.rocks/smartbucket` along with its
## Usage
`@push.rocks/smartbucket` is a TypeScript module designed to provide simple cloud-independent object storage functionality. It wraps various cloud storage providers such as AWS S3, Google Cloud Storage, and others, offering a unified API to manage storage buckets and objects within those buckets.
`@push.rocks/smartbucket` is a TypeScript module designed to provide simple cloud-independent object storage functionality. It wraps various cloud storage providers such as AWS S3, Google Cloud Storage, and others, offering a unified API to manage storage buckets and objects within those buckets.
In this guide, we will delve into the usage of SmartBucket, covering its full range of features from setting up the library to advanced usage scenarios.
@ -48,7 +49,7 @@ const mySmartBucket = new SmartBucket({
accessKey: "yourAccessKey",
accessSecret: "yourSecretKey",
endpoint: "yourEndpointURL",
port: 443, // Default is 443, could be customized for specific endpoint
port: 443, // Default is 443, can be customized for specific endpoint
useSsl: true // Defaults to true
});
```
@ -189,7 +190,7 @@ async function writeFileStream(bucketName: string, filePath: string, readableStr
// Create a readable stream from a string
const readable = new Readable();
readable.push('Hello world streamed as a file!');
readable.push(null); // Indicates end of the stream
readable.push(null); // End of stream
// Use the function
writeFileStream("exampleBucket", "path/to/streamedObject.txt", readable);
@ -197,7 +198,9 @@ writeFileStream("exampleBucket", "path/to/streamedObject.txt", readable);
### Working with Directories
`@push.rocks/smartbucket` abstracts directories within buckets for easier object management. You can create, list, and delete directories using the `Directory` class.
`@push.rocks/smartbucket` offers abstractions for directories within buckets for easier object management. You can create, list, and delete directories using the `Directory` class.
To list the contents of a directory:
```typescript
async function listDirectoryContents(bucketName: string, directoryPath: string) {
@ -205,6 +208,7 @@ async function listDirectoryContents(bucketName: string, directoryPath: string)
if (myBucket) {
const baseDirectory: Directory = await myBucket.getBaseDirectory();
const targetDirectory: Directory = await baseDirectory.getSubDirectoryByName(directoryPath);
console.log('Listing directories:');
const directories = await targetDirectory.listDirectories();
directories.forEach(dir => {
@ -250,11 +254,11 @@ createFileInDirectory("exampleBucket", "some/directory", "newfile.txt", "Hello,
#### Bucket Policies
Manage bucket policies to control access permissions. This feature is dependent on the policies provided by the storage service (e.g., AWS S3, MinIO).
Manage bucket policies to control access permissions. This feature depends on the policies provided by the storage service (e.g., AWS S3, MinIO).
#### Object Metadata
You can retrieve and modify object metadata. Metadata can be useful for storing additional information about an object.
Retrieve and modify object metadata. Metadata can be useful for storing additional information about an object.
To retrieve metadata:

View File

@ -45,7 +45,7 @@ tap.test('should get data in bucket', async () => {
});
const fileStringStream = await myBucket.fastGetStream({
path: 'hithere/socool.txt',
});
}, 'nodestream');
console.log(fileString);
});
@ -79,7 +79,7 @@ tap.test('prepare for directory style tests', async () => {
contents: 'dir3/dir4/file1.txt content',
});
await myBucket.fastPut({
path: 'file1.txt',
path: '/file1.txt',
contents: 'file1 content',
});
});

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartbucket',
version: '3.0.4',
description: 'A TypeScript library that offers simple, cloud-independent object storage with features like bucket creation, file management, and directory management.'
version: '3.0.13',
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

@ -1,6 +1,9 @@
import * as plugins from './plugins.js';
import * as helpers from './helpers.js';
import * as interfaces from './interfaces.js';
import { SmartBucket } from './classes.smartbucket.js';
import { Directory } from './classes.directory.js';
import { File } from './classes.file.js';
export class Bucket {
public static async getBucketByName(smartbucketRef: SmartBucket, bucketNameArg: string) {
@ -38,10 +41,19 @@ export class Bucket {
/**
* gets the base directory of the bucket
*/
public async getBaseDirectory() {
public async getBaseDirectory(): Promise<Directory> {
return new Directory(this, null, '');
}
public async getDirectoryFromPath(pathDescriptorArg: interfaces.IPathDecriptor): Promise<Directory> {
if (!pathDescriptorArg.path && !pathDescriptorArg.directory) {
return this.getBaseDirectory();
}
let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptorArg);
const baseDirectory = await this.getBaseDirectory();
return await baseDirectory.getSubDirectoryByName(checkPath);
}
// ===============
// Fast Operations
// ===============
@ -53,28 +65,38 @@ export class Bucket {
path: string;
contents: string | Buffer;
overwrite?: boolean;
}): Promise<void> {
}): Promise<File> {
try {
const reducedPath = await helpers.reducePathDescriptorToPath({
path: optionsArg.path,
})
// Check if the object already exists
const exists = await this.fastExists({ path: optionsArg.path });
const exists = await this.fastExists({ path: reducedPath });
if (exists && !optionsArg.overwrite) {
console.error(`Object already exists at path '${optionsArg.path}' in bucket '${this.name}'.`);
console.error(`Object already exists at path '${reducedPath}' in bucket '${this.name}'.`);
return;
} else if (exists && optionsArg.overwrite) {
console.log(`Overwriting existing object at path '${optionsArg.path}' in bucket '${this.name}'.`);
console.log(`Overwriting existing object at path '${reducedPath}' in bucket '${this.name}'.`);
} else {
console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`);
console.log(`Creating new object at path '${reducedPath}' in bucket '${this.name}'.`);
}
// Proceed with putting the object
const streamIntake = new plugins.smartstream.StreamIntake();
const putPromise = this.smartbucketRef.minioClient.putObject(this.name, optionsArg.path, streamIntake);
const putPromise = this.smartbucketRef.minioClient.putObject(this.name, reducedPath, streamIntake);
streamIntake.pushData(optionsArg.contents);
streamIntake.signalEnd();
await putPromise;
console.log(`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`);
console.log(`Object '${reducedPath}' has been successfully stored in bucket '${this.name}'.`);
const parsedPath = plugins.path.parse(reducedPath);
return new File({
directoryRefArg: await this.getDirectoryFromPath({
path: parsedPath.dir,
}),
fileName: parsedPath.base,
});
} catch (error) {
console.error(`Error storing object at path '${optionsArg.path}' in bucket '${this.name}':`, error);
throw error;
@ -85,10 +107,12 @@ export class Bucket {
/**
* get file
*/
public async fastGet(optionsArg: Parameters<typeof this.fastGetStream>[0]): Promise<Buffer> {
public async fastGet(optionsArg: {
path: string
}): Promise<Buffer> {
const done = plugins.smartpromise.defer();
let completeFile: Buffer;
const replaySubject = await this.fastGetStream(optionsArg);
const replaySubject = await this.fastGetReplaySubject(optionsArg);
const subscription = replaySubject.subscribe({
next: (chunk) => {
if (completeFile) {
@ -109,7 +133,13 @@ export class Bucket {
return completeFile;
}
public async fastGetStream(optionsArg: {
/**
* good when time to first byte is important
* and multiple subscribers are expected
* @param optionsArg
* @returns
*/
public async fastGetReplaySubject(optionsArg: {
path: string;
}): Promise<plugins.smartrx.rxjs.ReplaySubject<Buffer>> {
const fileStream = await this.smartbucketRef.minioClient
@ -139,12 +169,54 @@ export class Bucket {
return replaySubject;
}
public fastGetStream(optionsArg: {
path: string;
}, typeArg: 'webstream'): Promise<ReadableStream>
public async fastGetStream(optionsArg: {
path: string;
}, typeArg: 'nodestream'): Promise<plugins.stream.Readable>
/**
* fastGetStream
* @param optionsArg
* @returns
*/
public async fastGetStream(optionsArg: { path: string; }, typeArg: 'webstream' | 'nodestream' = 'nodestream'): Promise<ReadableStream | plugins.stream.Readable>{
const fileStream = await this.smartbucketRef.minioClient
.getObject(this.name, optionsArg.path)
.catch((e) => console.log(e));
const duplexStream = new plugins.smartstream.SmartDuplex<Buffer, Buffer>({
writeFunction: async (chunk) => {
return chunk;
},
finalFunction: async (cb) => {
return null;
}
});
if (!fileStream) {
return null;
}
const smartstream = new plugins.smartstream.StreamWrapper([
fileStream,
duplexStream,
]);
smartstream.run();
if (typeArg === 'nodestream') {
return duplexStream;
};
if (typeArg === 'webstream') {
return (await duplexStream.getWebStreams()).readable;
}
}
/**
* store file as stream
*/
public async fastPutStream(optionsArg: {
path: string;
dataStream: plugins.stream.Readable;
readableStream: plugins.stream.Readable | ReadableStream;
nativeMetadata?: { [key: string]: string };
overwrite?: boolean;
}): Promise<void> {
@ -160,19 +232,16 @@ export class Bucket {
} else {
console.log(`Creating new object at path '${optionsArg.path}' in bucket '${this.name}'.`);
}
const streamIntake = await plugins.smartstream.StreamIntake.fromStream<Uint8Array>(optionsArg.readableStream);
// Proceed with putting the object
await this.smartbucketRef.minioClient.putObject(
this.name,
optionsArg.path,
optionsArg.dataStream,
streamIntake,
null,
...(optionsArg.nativeMetadata
? (() => {
const returnObject: any = {};
return returnObject;
})()
: {})
null, // TODO: Add support for custom metadata once proper support is in minio.
);
console.log(`Object '${optionsArg.path}' has been successfully stored in bucket '${this.name}'.`);
@ -183,19 +252,10 @@ export class Bucket {
}
public async copyObject(optionsArg: {
/**
* the
*/
objectKey: string;
/**
* in case you want to copy to another bucket specify it here
*/
public async fastCopy(optionsArg: {
sourcePath: string;
destinationPath?: string;
targetBucket?: Bucket;
targetBucketKey?: string;
/**
* metadata will be merged with existing metadata
*/
nativeMetadata?: { [key: string]: string };
deleteExistingNativeMetadata?: boolean;
}): Promise<void> {
@ -205,7 +265,7 @@ export class Bucket {
// Retrieve current object information to use in copy conditions
const currentObjInfo = await this.smartbucketRef.minioClient.statObject(
targetBucketName,
optionsArg.objectKey
optionsArg.sourcePath
);
// Setting up copy conditions
@ -221,8 +281,8 @@ export class Bucket {
// TODO: check on issue here: https://github.com/minio/minio-js/issues/1286
await this.smartbucketRef.minioClient.copyObject(
this.name,
optionsArg.objectKey,
`/${targetBucketName}/${optionsArg.objectKey}`,
optionsArg.sourcePath,
`/${targetBucketName}/${optionsArg.destinationPath || optionsArg.sourcePath}`,
copyConditions
);
} catch (err) {
@ -231,6 +291,43 @@ export class Bucket {
}
}
/**
* Move object from one path to another within the same bucket or to another bucket
*/
public async fastMove(optionsArg: {
sourcePath: string;
destinationPath: string;
targetBucket?: Bucket;
overwrite?: boolean;
}): Promise<void> {
try {
// Check if the destination object already exists
const destinationBucket = optionsArg.targetBucket || this;
const exists = await destinationBucket.fastExists({ path: optionsArg.destinationPath });
if (exists && !optionsArg.overwrite) {
console.error(`Object already exists at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
return;
} else if (exists && optionsArg.overwrite) {
console.log(`Overwriting existing object at destination path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
} else {
console.log(`Moving object to path '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
}
// Proceed with copying the object to the new path
await this.fastCopy(optionsArg);
// Remove the original object after successful copy
await this.fastRemove({ path: optionsArg.sourcePath });
console.log(`Object '${optionsArg.sourcePath}' has been successfully moved to '${optionsArg.destinationPath}' in bucket '${destinationBucket.name}'.`);
} catch (error) {
console.error(`Error moving object from '${optionsArg.sourcePath}' to '${optionsArg.destinationPath}':`, error);
throw error;
}
}
/**
* removeObject
*/
@ -263,9 +360,63 @@ export class Bucket {
}
}
public async fastStat(optionsArg: {
path: string;
}) {
return this.smartbucketRef.minioClient.statObject(this.name, optionsArg.path);
/**
* deletes this bucket
*/
public async delete() {
await this.smartbucketRef.minioClient.removeBucket(this.name);
}
public async fastStat(pathDescriptor: interfaces.IPathDecriptor) {
let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
return this.smartbucketRef.minioClient.statObject(this.name, checkPath);
}
public async isDirectory(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> {
let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
// lets check if the checkPath is a directory
const stream = this.smartbucketRef.minioClient.listObjectsV2(this.name, checkPath, true);
const done = plugins.smartpromise.defer<boolean>();
stream.on('data', (dataArg) => {
stream.destroy(); // Stop the stream early if we find at least one object
if (dataArg.prefix.startsWith(checkPath + '/')) {
done.resolve(true);
}
});
stream.on('end', () => {
done.resolve(false);
});
stream.on('error', (err) => {
done.reject(err);
});
return done.promise;
};
public async isFile(pathDescriptor: interfaces.IPathDecriptor): Promise<boolean> {
let checkPath = await helpers.reducePathDescriptorToPath(pathDescriptor);
// lets check if the checkPath is a directory
const stream = this.smartbucketRef.minioClient.listObjectsV2(this.name, checkPath, true);
const done = plugins.smartpromise.defer<boolean>();
stream.on('data', (dataArg) => {
stream.destroy(); // Stop the stream early if we find at least one object
if (dataArg.prefix === checkPath) {
done.resolve(true);
}
});
stream.on('end', () => {
done.resolve(false);
});
stream.on('error', (err) => {
done.reject(err);
});
return done.promise;
}
}

View File

@ -74,8 +74,9 @@ export class Directory {
return null;
}
if (!exists && optionsArg.createWithContents) {
await this.fastPut({
path: optionsArg.name,
await File.create({
directory: this,
name: optionsArg.name,
contents: optionsArg.createWithContents,
});
}
@ -234,14 +235,44 @@ export class Directory {
return result;
}
public async fastGetStream(pathArg: string): Promise<plugins.smartrx.rxjs.ReplaySubject<Buffer>> {
const path = plugins.path.join(this.getBasePath(), pathArg);
public fastGetStream(optionsArg: {
path: string;
}, typeArg: 'webstream'): Promise<ReadableStream>
public async fastGetStream(optionsArg: {
path: string;
}, typeArg: 'nodestream'): Promise<plugins.stream.Readable>
/**
* fastGetStream
* @param optionsArg
* @returns
*/
public async fastGetStream(optionsArg: { path: string; }, typeArg: 'webstream' | 'nodestream'): Promise<ReadableStream | plugins.stream.Readable>{
const path = plugins.path.join(this.getBasePath(), optionsArg.path);
const result = await this.bucketRef.fastGetStream({
path,
});
path
}, typeArg as any);
return result;
}
/**
* fast put stream
*/
public async fastPutStream(optionsArg: {
path: string;
stream: plugins.stream.Readable;
}): Promise<void> {
const path = plugins.path.join(this.getBasePath(), optionsArg.path);
await this.bucketRef.fastPutStream({
path,
readableStream: optionsArg.stream,
});
}
/**
* removes a file within the directory
* @param optionsArg
*/
public async fastRemove(optionsArg: { path: string }) {
const path = plugins.path.join(this.getBasePath(), optionsArg.path);
await this.bucketRef.fastRemove({

View File

@ -1,8 +1,9 @@
import * as plugins from './plugins.js';
import * as helpers from './helpers.js';
import * as interfaces from './interfaces.js';
import { Directory } from './classes.directory.js';
import { MetaData } from './classes.metadata.js';
/**
* represents a file in a directory
*/
@ -31,7 +32,12 @@ export class File {
directoryRefArg: optionsArg.directory,
fileName: optionsArg.name,
});
if (contents instanceof plugins.stream.Readable) {} else {
if (contents instanceof plugins.stream.Readable) {
await optionsArg.directory.fastPutStream({
path: optionsArg.name,
stream: contents,
});
} else {
await optionsArg.directory.fastPut({
path: optionsArg.name,
contents: contents,
@ -46,7 +52,7 @@ export class File {
public getBasePath(): string {
return plugins.path.join(this.parentDirectoryRef.getBasePath(), this.name);
};
}
constructor(optionsArg: { directoryRefArg: Directory; fileName: string }) {
this.parentDirectoryRef = optionsArg.directoryRefArg;
@ -65,35 +71,61 @@ export class File {
return resultBuffer;
}
public async getReadStream() {
const readStream = this.parentDirectoryRef.bucketRef.fastGetStream({
path: this.getBasePath(),
});
public async getReadStream(typeArg: 'webstream'): Promise<ReadableStream>;
public async getReadStream(typeArg: 'nodestream'): Promise<plugins.stream.Readable>;
public async getReadStream(
typeArg: 'nodestream' | 'webstream'
): Promise<ReadableStream | plugins.stream.Readable> {
const readStream = this.parentDirectoryRef.bucketRef.fastGetStream(
{
path: this.getBasePath(),
},
typeArg as any
);
return readStream;
}
/**
* removes this file
* for using recycling mechanics use .delete()
* deletes this file
*/
public async remove() {
await this.parentDirectoryRef.bucketRef.fastRemove({
path: this.getBasePath(),
});
if (!this.name.endsWith('.metadata')) {
public async delete(optionsArg?: {
mode: 'trash' | 'permanent';
}) {
optionsArg = {
... {
mode: 'permanent',
},
...optionsArg,
}
if (optionsArg.mode === 'permanent') {
await this.parentDirectoryRef.bucketRef.fastRemove({
path: this.getBasePath() + '.metadata',
path: this.getBasePath(),
});
if (!this.name.endsWith('.metadata')) {
const metadata = await this.getMetaData();
await metadata.metadataFile.delete(optionsArg);
}
} else if (optionsArg.mode === 'trash') {
const metadata = await this.getMetaData();
await metadata.storeCustomMetaData({
key: 'recycle',
value: {
deletedAt: Date.now(),
originalPath: this.getBasePath(),
},
});
const trashName = plugins.smartunique.uuid4();
await this.move({
directory: await this.parentDirectoryRef.bucketRef.getBaseDirectory(),
path: plugins.path.join('trash', trashName),
});
}
await this.parentDirectoryRef.listFiles();
}
/**
* deletes the file with recycling mechanics
*/
public async delete() {
await this.remove();
}
/**
* allows locking the file
* @param optionsArg
@ -102,7 +134,7 @@ export class File {
const metadata = await this.getMetaData();
await metadata.setLock({
lock: 'locked',
expires: new Date(Date.now() + (optionsArg?.timeoutMillis || 1000)),
expires: Date.now() + (optionsArg?.timeoutMillis || 1000),
});
}
@ -116,17 +148,23 @@ export class File {
*/
force?: boolean;
}) {
const metadata = await this.getMetaData();
await metadata.removeLock({
force: optionsArg?.force,
});
}
public async updateWithContents(optionsArg: {
contents: Buffer | string | plugins.stream.Readable;
contents: Buffer | string | plugins.stream.Readable | ReadableStream;
encoding?: 'utf8' | 'binary';
}) {
if (optionsArg.contents instanceof plugins.stream.Readable) {
if (
optionsArg.contents instanceof plugins.stream.Readable ||
optionsArg.contents instanceof ReadableStream
) {
await this.parentDirectoryRef.bucketRef.fastPutStream({
path: this.getBasePath(),
dataStream: optionsArg.contents,
readableStream: optionsArg.contents,
});
} else if (Buffer.isBuffer(optionsArg.contents)) {
await this.parentDirectoryRef.bucketRef.fastPut({
@ -141,14 +179,55 @@ export class File {
}
}
/**
* moves the file to another directory
*/
public async move(pathDescriptorArg: interfaces.IPathDecriptor) {
let moveToPath = '';
const isDirectory = await this.parentDirectoryRef.bucketRef.isDirectory(pathDescriptorArg);
if (isDirectory) {
moveToPath = await helpers.reducePathDescriptorToPath({
...pathDescriptorArg,
path: plugins.path.join(pathDescriptorArg.path, this.name),
});
}
// lets move the file
await this.parentDirectoryRef.bucketRef.fastMove({
sourcePath: this.getBasePath(),
destinationPath: moveToPath,
});
// lets move the metadatafile
const metadata = await this.getMetaData();
await metadata.metadataFile.move(pathDescriptorArg);
}
/**
* allows updating the metadata of a file
* @param updatedMetadata
*/
public async getMetaData() {
if (this.name.endsWith('.metadata')) {
throw new Error('metadata files cannot have metadata');
}
const metadata = await MetaData.createForFile({
file: this,
});
return metadata;
}
/**
* gets the contents as json
*/
public async getJsonData() {
const json = await this.getContentsAsString();
const parsed = await JSON.parse(json);
return parsed;
}
public async writeJsonData(dataArg: any) {
await this.updateWithContents({
contents: JSON.stringify(dataArg),
});
}
}

View File

@ -4,9 +4,7 @@ import { File } from './classes.file.js';
export class MetaData {
// static
public static async createForFile(optionsArg: {
file: File;
}) {
public static async createForFile(optionsArg: { file: File }) {
const metaData = new MetaData();
metaData.fileRef = optionsArg.file;
@ -29,16 +27,16 @@ export class MetaData {
* the file that the metadata is for
*/
fileRef: File;
public async getFileType(optionsArg?: {
useFileExtension?: boolean;
useMagicBytes?: boolean;
}): Promise<string> {
if (optionsArg && optionsArg.useFileExtension || optionsArg.useFileExtension === undefined) {
if ((optionsArg && optionsArg.useFileExtension) || optionsArg.useFileExtension === undefined) {
return plugins.path.extname(this.fileRef.name);
}
};
}
/**
* gets the size of the fileRef
*/
@ -47,59 +45,56 @@ export class MetaData {
path: this.fileRef.getBasePath(),
});
return stat.size;
};
}
private prefixCustomMetaData = 'custom_';
public async storeCustomMetaData<T = any>(optionsArg: {
key: string;
value: T;
}) {
const json = await this.metadataFile.getContentsAsString();
const parsed = await JSON.parse(json);
parsed[this.prefixCustomMetaData + optionsArg.key] = optionsArg.value;
await this.metadataFile.updateWithContents({
contents: JSON.stringify(parsed),
});
public async storeCustomMetaData<T = any>(optionsArg: { key: string; value: T }) {
const data = await this.metadataFile.getContentsAsString();
data[this.prefixCustomMetaData + optionsArg.key] = optionsArg.value;
await this.metadataFile.writeJsonData(data);
}
public async getCustomMetaData<T = any>(optionsArg: {
key: string;
}): Promise<T> {
const json = await this.metadataFile.getContentsAsString();
const parsed = await JSON.parse(json);
return parsed[this.prefixCustomMetaData + optionsArg.key];
public async getCustomMetaData<T = any>(optionsArg: { key: string }): Promise<T> {
const data = await this.metadataFile.getJsonData();
return data[this.prefixCustomMetaData + optionsArg.key];
}
public async deleteCustomMetaData(optionsArg: {
key: string;
}) {
const json = await this.metadataFile.getContentsAsString();
const parsed = await JSON.parse(json);
delete parsed[this.prefixCustomMetaData + optionsArg.key];
await this.metadataFile.updateWithContents({
contents: JSON.stringify(parsed),
});
public async deleteCustomMetaData(optionsArg: { key: string }) {
const data = await this.metadataFile.getJsonData();
delete data[this.prefixCustomMetaData + optionsArg.key];
await this.metadataFile.writeJsonData(data);
}
/**
* set a lock on the ref file
* @param optionsArg
*/
public async setLock(optionsArg: {
lock: string;
expires: Date;
}) {
public async setLock(optionsArg: { lock: string; expires: number }) {
const data = await this.metadataFile.getJsonData();
data.lock = optionsArg.lock;
data.lockExpires = optionsArg.expires;
await this.metadataFile.writeJsonData(data);
}
/**
* remove the lock on the ref file
* @param optionsArg
* @param optionsArg
*/
public async removeLock(optionsArg: {
force: boolean;
}) {
public async removeLock(optionsArg: { force: boolean }) {
const data = await this.metadataFile.getJsonData();
delete data.lock;
delete data.lockExpires;
await this.metadataFile.writeJsonData(data);
}
}
public async checkLocked(): Promise<boolean> {
const data = await this.metadataFile.getJsonData();
return data.lock && data.lockExpires > Date.now();
}
public async getLockInfo(): Promise<{ lock: string; expires: number }> {
const data = await this.metadataFile.getJsonData();
return { lock: data.lock, expires: data.lockExpires };
}
}

22
ts/helpers.ts Normal file
View File

@ -0,0 +1,22 @@
import * as plugins from './plugins.js';
import * as interfaces from './interfaces.js';
export const reducePathDescriptorToPath = async (pathDescriptorArg: interfaces.IPathDecriptor): Promise<string> => {
let returnPath = ``
if (pathDescriptorArg.directory) {
if (pathDescriptorArg.path && plugins.path.isAbsolute(pathDescriptorArg.path)) {
console.warn('Directory is being ignored when path is absolute.');
returnPath = pathDescriptorArg.path;
} else if (pathDescriptorArg.path) {
returnPath = plugins.path.join(pathDescriptorArg.directory.getBasePath(), pathDescriptorArg.path);
}
} else if (pathDescriptorArg.path) {
returnPath = pathDescriptorArg.path;
} else {
throw new Error('You must specify either a path or a directory.');
}
if (returnPath.startsWith('/')) {
returnPath = returnPath.substring(1);
}
return returnPath;
}

6
ts/interfaces.ts Normal file
View File

@ -0,0 +1,6 @@
import type { Directory } from "./classes.directory.js";
export interface IPathDecriptor {
path?: string;
directory?: Directory;
}

View File

@ -10,8 +10,9 @@ import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx';
import * as smartstream from '@push.rocks/smartstream';
import * as smartunique from '@push.rocks/smartunique';
export { smartmime, smartpath, smartpromise, smartrx, smartstream };
export { smartmime, smartpath, smartpromise, smartrx, smartstream, smartunique };
// @tsclass
import * as tsclass from '@tsclass/tsclass';