@bejibun/storage 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ ---
5
+
6
+ ## [v0.1.0](https://github.com/Bejibun-Framework/bejibun-storage/compare/v0.1.0...v0.1.0) - 2026-08-03
7
+
8
+ ### 🩹 Fixes
9
+
10
+ ### 📖 Changes
11
+ Initial release of `@bejibun/storage` -- a filesystem abstraction for the Bejibun Framework, providing a single API over local disk and S3-compatible storage.
12
+
13
+ **Disk drivers:**
14
+ - `local` -- reads/writes files on the local filesystem
15
+ - `s3` -- reads/writes files to an S3-compatible bucket (endpoint, region, bucket, credentials configurable)
16
+ **`Storage` facade / `StorageBuilder`:**
17
+ - `.disk(name)` -- select a configured disk by name
18
+ - `.build(disk)` -- use an ad-hoc disk config at runtime, without touching `config/storage.ts`
19
+ - `.exists(path)` / `.missing(path)` -- check file presence
20
+ - `.metadata(path)` -- get file `Stats` (local) or `S3Stats` (S3)
21
+ - `.size(path)` -- get file size in bytes
22
+ - `.mimeType(path)` -- detect file MIME type
23
+ - `.lastModified(path)` -- get last-modified date
24
+ - `.get(path)` -- retrieve a `Bun.BunFile` or `Bun.S3File`
25
+ - `.put(path, content, options?)` -- write content to a file
26
+ - `.copy(source, destination, options?)` -- copy a file
27
+ - `.move(source, destination, options?)` -- move a file
28
+ - `.delete(path)` -- delete a file
29
+ **Config:**
30
+ - `config/storage.ts` supports a `default` disk plus a `disks` map; ships with `local`, `public`, and `s3` examples
31
+ - Per-operation `StorageOptions` cover S3-specific concerns (`acl`, `storageClass`, `partSize`, `queueSize`, `retry`, `virtualHostedStyle`, etc.) as well as local options (`mode`, `createPath`)
32
+ - `StorageDiskDriverEnum` (`Local` | `S3`) used to identify the driver per disk
33
+ **Error handling:**
34
+ - `StorageException` -- thrown for missing/invalid config, unsupported drivers, and missing required arguments; logs via `@bejibun/logger` before throwing
35
+
36
+ **Dependencies:**
37
+ - `@bejibun/app ^0.1.24`
38
+ - `@bejibun/logger ^0.1.22`
39
+ - `@bejibun/utils ^0.1.28`
40
+
41
+ ### ❤️Contributors
42
+ - Havea Crenata ([@crenata](https://github.com/crenata))
43
+
44
+ **Full Changelog**: https://github.com/Bejibun-Framework/bejibun-storage/blob/master/CHANGELOG.md
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bejibun Framework
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ <div align="center">
2
+
3
+ <img src="https://github.com/Bejibun-Framework/bejibun/blob/master/public/images/bejibun.png?raw=true" width="150" alt="Bejibun" />
4
+
5
+ ![GitHub top language](https://img.shields.io/github/languages/top/Bejibun-Framework/bejibun-storage)
6
+ ![NPM Downloads](https://img.shields.io/npm/d18m/%40bejibun%2Fstorage)
7
+ ![GitHub issues](https://img.shields.io/github/issues/Bejibun-Framework/bejibun-storage)
8
+ ![GitHub](https://img.shields.io/github/license/Bejibun-Framework/bejibun-storage)
9
+ ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Bejibun-Framework/bejibun-storage?display_name=tag&include_prereleases)
10
+
11
+ </div>
12
+
13
+ # Storage for Bejibun
14
+ Storage for Bejibun Framework.
15
+
16
+ ## Usage
17
+
18
+ ### Installation
19
+ Install the package.
20
+
21
+ ```bash
22
+ # Using Bun
23
+ bun add @bejibun/storage
24
+
25
+ # Using Bejibun
26
+ bun ace install @bejibun/storage
27
+ ```
28
+
29
+ ### Configuration
30
+ The configuration file automatically executed if you are using `ace`.
31
+
32
+ Or
33
+
34
+ Add `storage.ts` inside config directory on your project if doesn't exist.
35
+
36
+ ```bash
37
+ config/storage.ts
38
+ ```
39
+
40
+ ```ts
41
+ import App from "@bejibun/app";
42
+ import StorageDiskDriverEnum from "@/enums/StorageDiskDriverEnum";
43
+
44
+ const config: Record<string, any> = {
45
+ default: "local",
46
+
47
+ disks: {
48
+ local: {
49
+ driver: StorageDiskDriverEnum.Local,
50
+ root: App.Path.storagePath("app")
51
+ },
52
+
53
+ public: {
54
+ driver: StorageDiskDriverEnum.Local,
55
+ root: App.Path.storagePath("app/public"),
56
+ url: `${Bun.env.APP_URL}/storage/public`
57
+ },
58
+
59
+ s3: {
60
+ driver: StorageDiskDriverEnum.S3,
61
+ endpoint: Bun.env.S3_ENDPOINT,
62
+ region: Bun.env.S3_REGION,
63
+ bucket: Bun.env.S3_BUCKET,
64
+ access_key_id: Bun.env.S3_ACCESS_KEY_ID,
65
+ secret_access_key: Bun.env.S3_SECRET_ACCESS_KEY,
66
+ url: ""
67
+ }
68
+ }
69
+ };
70
+
71
+ export default config;
72
+ ```
73
+
74
+ You can pass the value with environment variables.
75
+
76
+ ### How to Use
77
+ How to use tha package.
78
+
79
+ #### Standard Use
80
+ ```ts
81
+ import Storage from "@bejibun/core/facades/Storage";
82
+
83
+ await Storage.exists("path/to/your/file.ext"); // Check if the file exists
84
+ await Storage.missing("path/to/your/file.ext"); // Check if the file doesn't exists
85
+ await Storage.get("path/to/your/file.ext"); // Get data content
86
+ await Storage.put("path/to/your/file.ext", "content"); // Store content to file
87
+ await Storage.copy("source/file.ext", "destination/file.ext"); // Copy file
88
+ await Storage.move("source/file.ext", "destination/file.ext"); // Move file
89
+ await Storage.delete("path/to/your/file.ext"); // Delete file
90
+ ```
91
+
92
+ #### With Specified Disk
93
+ ```ts
94
+ import Storage from "@bejibun/core/facades/Storage";
95
+
96
+ await Storage.disk("public").exists("path/to/your/file.ext");
97
+ await Storage.disk("public").missing("path/to/your/file.ext");
98
+ await Storage.disk("public").get("path/to/your/file.ext");
99
+ await Storage.disk("public").put("path/to/your/file.ext", "content");
100
+ await Storage.disk("public").copy("source/file.ext", "destination/file.ext");
101
+ await Storage.disk("public").move("source/file.ext", "destination/file.ext");
102
+ await Storage.disk("public").delete("path/to/your/file.ext");
103
+ ```
104
+
105
+ #### New Disk at Runtime
106
+ ```ts
107
+ import Storage from "@bejibun/core/facades/Storage";
108
+
109
+ await Storage.build({
110
+ driver: "local", // "local" | StorageDiskDriverEnum.Local
111
+ root: App.Path.storagePath("custom")
112
+ }).exists("path/to/your/file.ext");
113
+ await Storage.build({
114
+ driver: "local",
115
+ root: App.Path.storagePath("custom")
116
+ }).missing("path/to/your/file.ext");
117
+ await Storage.build({
118
+ driver: "local",
119
+ root: App.Path.storagePath("custom")
120
+ }).get("path/to/your/file.ext");
121
+ await Storage.build({
122
+ driver: "local",
123
+ root: App.Path.storagePath("custom")
124
+ }).put("path/to/your/file.ext", "content");
125
+ await Storage.build({
126
+ driver: "local",
127
+ root: App.Path.storagePath("custom")
128
+ }).copy("source/file.ext", "destination/file.ext");
129
+ await Storage.build({
130
+ driver: "local",
131
+ root: App.Path.storagePath("custom")
132
+ }).move("source/file.ext", "destination/file.ext");
133
+ await Storage.build({
134
+ driver: "local",
135
+ root: App.Path.storagePath("custom")
136
+ }).delete("path/to/your/file.ext");
137
+ ```
138
+
139
+ ## ☕ Support / Donate
140
+
141
+ If you find this project helpful and want to support it:
142
+
143
+ [![Donate](https://img.shields.io/badge/Donate-Support%20Me-orange?style=for-the-badge)](https://donate.bejibun.com)
144
+
145
+ Or you can buy this `$BJBN (Bejibun)` tokens [here](https://pump.fun/coin/CQhbNnCGKfDaKXt8uE61i5DrBYJV7NPsCDD9vQgypump).
@@ -0,0 +1,24 @@
1
+ import type { Stats } from "fs";
2
+ import type { StorageDisk, StorageOptions } from "../types/storage";
3
+ export default class StorageBuilder {
4
+ protected conf: Record<string, any>;
5
+ protected overrideDisk?: StorageDisk;
6
+ protected drive?: string;
7
+ constructor();
8
+ private get config();
9
+ private get currentDisk();
10
+ private get driver();
11
+ build(overrideDisk: StorageDisk): StorageBuilder;
12
+ disk(drive: string): StorageBuilder;
13
+ exists(filepath: string): Promise<boolean>;
14
+ missing(filepath: string): Promise<boolean>;
15
+ metadata(filepath: string): Promise<Stats | Bun.S3Stats>;
16
+ size(filepath: string): Promise<number>;
17
+ mimeType(filepath: string): Promise<string>;
18
+ lastModified(filepath: string): Promise<Date>;
19
+ get(filepath: string): Promise<Bun.BunFile | Bun.S3File>;
20
+ put(filepath: string, content: any, options?: StorageOptions): Promise<void>;
21
+ copy(source: string, destination: string, options?: StorageOptions): Promise<void>;
22
+ move(source: string, destination: string, options?: StorageOptions): Promise<void>;
23
+ delete(filepath: string): Promise<void>;
24
+ }
@@ -0,0 +1,131 @@
1
+ import App from "@bejibun/app";
2
+ import Logger from "@bejibun/logger";
3
+ import { defineValue, isEmpty } from "@bejibun/utils";
4
+ import Enum from "@bejibun/utils/facades/Enum";
5
+ import fs from "fs";
6
+ import StorageLocalBuilder from "../builders/storage/StorageLocalBuilder";
7
+ import StorageS3Builder from "../builders/storage/StorageS3Builder";
8
+ import StorageConfig from "../config/storage";
9
+ import StorageDiskDriverEnum from "../enums/StorageDiskDriverEnum";
10
+ import StorageException from "../exceptions/StorageException";
11
+ export default class StorageBuilder {
12
+ conf;
13
+ overrideDisk;
14
+ drive;
15
+ constructor() {
16
+ const configPath = App.Path.configPath("storage.ts");
17
+ let config;
18
+ if (fs.existsSync(configPath))
19
+ config = require(configPath).default;
20
+ else
21
+ config = StorageConfig;
22
+ this.conf = config;
23
+ }
24
+ get config() {
25
+ if (isEmpty(this.conf))
26
+ throw new StorageException("There is no config provided.");
27
+ return this.conf;
28
+ }
29
+ get currentDisk() {
30
+ return defineValue(this.overrideDisk, this.config.disks[defineValue(this.drive, this.config.default)]);
31
+ }
32
+ get driver() {
33
+ const driver = defineValue(this.currentDisk?.driver);
34
+ if (isEmpty(driver))
35
+ throw new StorageException(`Missing "driver" on disk config.`);
36
+ if (!Enum.setEnums(StorageDiskDriverEnum).hasValue(driver))
37
+ throw new StorageException(`Not supported "driver" disk.`);
38
+ switch (driver) {
39
+ case StorageDiskDriverEnum.Local:
40
+ return new StorageLocalBuilder(this.currentDisk);
41
+ case StorageDiskDriverEnum.S3:
42
+ return new StorageS3Builder(this.currentDisk);
43
+ default:
44
+ throw new StorageException(`Not supported "driver" disk.`);
45
+ }
46
+ }
47
+ build(overrideDisk) {
48
+ this.overrideDisk = overrideDisk;
49
+ return this;
50
+ }
51
+ disk(drive) {
52
+ this.drive = drive;
53
+ return this;
54
+ }
55
+ async exists(filepath) {
56
+ if (isEmpty(filepath))
57
+ throw new StorageException("The file path is required.");
58
+ return await this.driver.exists(filepath);
59
+ }
60
+ async missing(filepath) {
61
+ if (isEmpty(filepath))
62
+ throw new StorageException("The file path is required.");
63
+ return !await this.driver.missing(filepath);
64
+ }
65
+ async metadata(filepath) {
66
+ if (isEmpty(filepath))
67
+ throw new StorageException("The file path is required.");
68
+ return await this.driver.metadata(filepath);
69
+ }
70
+ async size(filepath) {
71
+ if (isEmpty(filepath))
72
+ throw new StorageException("The file path is required.");
73
+ return await this.driver.size(filepath);
74
+ }
75
+ async mimeType(filepath) {
76
+ if (isEmpty(filepath))
77
+ throw new StorageException("The file path is required.");
78
+ return await this.driver.mimeType(filepath);
79
+ }
80
+ async lastModified(filepath) {
81
+ if (isEmpty(filepath))
82
+ throw new StorageException("The file path is required.");
83
+ return await this.driver.lastModified(filepath);
84
+ }
85
+ async get(filepath) {
86
+ if (isEmpty(filepath))
87
+ throw new StorageException("The file path is required.");
88
+ return await this.driver.get(filepath);
89
+ }
90
+ async put(filepath, content, options) {
91
+ if (isEmpty(filepath))
92
+ throw new StorageException("The file path is required.");
93
+ if (isEmpty(content))
94
+ throw new StorageException("The content is required.");
95
+ try {
96
+ await this.driver.put(filepath, content, options);
97
+ }
98
+ catch (error) {
99
+ Logger.setContext("Storage").error("Something went wrong when saving file.").trace(error);
100
+ }
101
+ }
102
+ async copy(source, destination, options) {
103
+ if (isEmpty(source))
104
+ throw new StorageException("The source file path is required.");
105
+ if (isEmpty(destination))
106
+ throw new StorageException("The destination file path is required.");
107
+ try {
108
+ await this.driver.copy(source, destination, options);
109
+ }
110
+ catch (error) {
111
+ Logger.setContext("Storage").error("Something went wrong when copying file.").trace(error);
112
+ }
113
+ }
114
+ async move(source, destination, options) {
115
+ if (isEmpty(source))
116
+ throw new StorageException("The source file path is required.");
117
+ if (isEmpty(destination))
118
+ throw new StorageException("The destination file path is required.");
119
+ try {
120
+ await this.driver.move(source, destination, options);
121
+ }
122
+ catch (error) {
123
+ Logger.setContext("Storage").error("Something went wrong when moving file.").trace(error);
124
+ }
125
+ }
126
+ async delete(filepath) {
127
+ if (isEmpty(filepath))
128
+ throw new StorageException("The file path is required.");
129
+ await this.driver.delete(filepath);
130
+ }
131
+ }
@@ -0,0 +1,18 @@
1
+ import type { Stats } from "fs";
2
+ import type { StorageDriver, StorageOptions } from "../../types/storage";
3
+ export default class StorageLocalBuilder implements StorageDriver {
4
+ protected _config: Record<string, any>;
5
+ constructor(config: Record<string, any>);
6
+ private get config();
7
+ exists(filepath: string): Promise<boolean>;
8
+ missing(filepath: string): Promise<boolean>;
9
+ metadata(filepath: string): Promise<Stats>;
10
+ size(filepath: string): Promise<number>;
11
+ mimeType(filepath: string): Promise<string>;
12
+ lastModified(filepath: string): Promise<Date>;
13
+ get(filepath: string): Promise<Bun.BunFile>;
14
+ put(filepath: string, content: any, options?: StorageOptions): Promise<void>;
15
+ copy(source: string, destination: string, options?: StorageOptions): Promise<void>;
16
+ move(source: string, destination: string, options?: StorageOptions): Promise<void>;
17
+ delete(filepath: string): Promise<void>;
18
+ }
@@ -0,0 +1,92 @@
1
+ import Logger from "@bejibun/logger";
2
+ import { isEmpty } from "@bejibun/utils";
3
+ import path from "path";
4
+ import StorageException from "../../exceptions/StorageException";
5
+ export default class StorageLocalBuilder {
6
+ _config;
7
+ constructor(config) {
8
+ this._config = config;
9
+ }
10
+ get config() {
11
+ if (isEmpty(this._config.root))
12
+ throw new StorageException(`Missing "root" for "local" disk configuration.`);
13
+ return this._config;
14
+ }
15
+ async exists(filepath) {
16
+ if (isEmpty(filepath))
17
+ throw new StorageException("The file path is required.");
18
+ return await Bun.file(path.resolve(this.config.root, filepath)).exists();
19
+ }
20
+ async missing(filepath) {
21
+ if (isEmpty(filepath))
22
+ throw new StorageException("The file path is required.");
23
+ return !await this.exists(filepath);
24
+ }
25
+ async metadata(filepath) {
26
+ if (isEmpty(filepath))
27
+ throw new StorageException("The file path is required.");
28
+ return await (await this.get(filepath)).stat();
29
+ }
30
+ async size(filepath) {
31
+ if (isEmpty(filepath))
32
+ throw new StorageException("The file path is required.");
33
+ return (await this.metadata(filepath)).size;
34
+ }
35
+ async mimeType(filepath) {
36
+ if (isEmpty(filepath))
37
+ throw new StorageException("The file path is required.");
38
+ return (await this.get(filepath)).type;
39
+ }
40
+ async lastModified(filepath) {
41
+ if (isEmpty(filepath))
42
+ throw new StorageException("The file path is required.");
43
+ return (await this.metadata(filepath)).mtime;
44
+ }
45
+ async get(filepath) {
46
+ if (isEmpty(filepath))
47
+ throw new StorageException("The file path is required.");
48
+ return Bun.file(path.resolve(this.config.root, filepath));
49
+ }
50
+ async put(filepath, content, options) {
51
+ if (isEmpty(filepath))
52
+ throw new StorageException("The file path is required.");
53
+ if (isEmpty(content))
54
+ throw new StorageException("The content is required.");
55
+ try {
56
+ await Bun.write(path.resolve(this.config.root, filepath), content, options);
57
+ }
58
+ catch (error) {
59
+ Logger.setContext("Storage").error("Something went wrong when saving file.").trace(error);
60
+ }
61
+ }
62
+ async copy(source, destination, options) {
63
+ if (isEmpty(source))
64
+ throw new StorageException("The source file path is required.");
65
+ if (isEmpty(destination))
66
+ throw new StorageException("The destination file path is required.");
67
+ try {
68
+ await this.put(destination, await this.get(source), options);
69
+ }
70
+ catch (error) {
71
+ Logger.setContext("Storage").error("Something went wrong when copying file.").trace(error);
72
+ }
73
+ }
74
+ async move(source, destination, options) {
75
+ if (isEmpty(source))
76
+ throw new StorageException("The source file path is required.");
77
+ if (isEmpty(destination))
78
+ throw new StorageException("The destination file path is required.");
79
+ try {
80
+ await this.copy(source, destination, options);
81
+ await this.delete(source);
82
+ }
83
+ catch (error) {
84
+ Logger.setContext("Storage").error("Something went wrong when moving file.").trace(error);
85
+ }
86
+ }
87
+ async delete(filepath) {
88
+ if (isEmpty(filepath))
89
+ throw new StorageException("The file path is required.");
90
+ await Bun.file(path.resolve(this.config.root, filepath)).delete();
91
+ }
92
+ }
@@ -0,0 +1,18 @@
1
+ import type { StorageDriver, StorageOptions } from "../../types/storage";
2
+ export default class StorageS3Builder implements StorageDriver {
3
+ protected _config: Record<string, any>;
4
+ protected client: Bun.S3Client;
5
+ constructor(config: Record<string, any>);
6
+ private get config();
7
+ exists(filepath: string): Promise<boolean>;
8
+ missing(filepath: string): Promise<boolean>;
9
+ metadata(filepath: string): Promise<Bun.S3Stats>;
10
+ size(filepath: string): Promise<number>;
11
+ mimeType(filepath: string): Promise<string>;
12
+ lastModified(filepath: string): Promise<Date>;
13
+ get(filepath: string): Promise<Bun.S3File>;
14
+ put(filepath: string, content: any, options?: StorageOptions): Promise<void>;
15
+ copy(source: string, destination: string, options?: StorageOptions): Promise<void>;
16
+ move(source: string, destination: string, options?: StorageOptions): Promise<void>;
17
+ delete(filepath: string): Promise<void>;
18
+ }
@@ -0,0 +1,103 @@
1
+ import Logger from "@bejibun/logger";
2
+ import { isEmpty } from "@bejibun/utils";
3
+ import StorageException from "../../exceptions/StorageException";
4
+ export default class StorageS3Builder {
5
+ _config;
6
+ client;
7
+ constructor(config) {
8
+ this._config = config;
9
+ this.client = new Bun.S3Client({
10
+ endpoint: this.config.endpoint,
11
+ region: this.config.region,
12
+ bucket: this.config.bucket,
13
+ accessKeyId: this.config.access_key_id,
14
+ secretAccessKey: this.config.secret_access_key
15
+ });
16
+ }
17
+ get config() {
18
+ if (isEmpty(this._config.endpoint))
19
+ throw new StorageException(`Missing "endpoint" for "s3" disk configuration.`);
20
+ if (isEmpty(this._config.access_key_id))
21
+ throw new StorageException(`Missing "access_key_id" for "s3" disk configuration.`);
22
+ if (isEmpty(this._config.secret_access_key))
23
+ throw new StorageException(`Missing "secret_access_key" for "s3" disk configuration.`);
24
+ return this._config;
25
+ }
26
+ async exists(filepath) {
27
+ if (isEmpty(filepath))
28
+ throw new StorageException("The file path is required.");
29
+ return await this.client.file(filepath).exists();
30
+ }
31
+ async missing(filepath) {
32
+ if (isEmpty(filepath))
33
+ throw new StorageException("The file path is required.");
34
+ return !await this.exists(filepath);
35
+ }
36
+ async metadata(filepath) {
37
+ if (isEmpty(filepath))
38
+ throw new StorageException("The file path is required.");
39
+ return await (await this.get(filepath)).stat();
40
+ }
41
+ async size(filepath) {
42
+ if (isEmpty(filepath))
43
+ throw new StorageException("The file path is required.");
44
+ return (await this.metadata(filepath)).size;
45
+ }
46
+ async mimeType(filepath) {
47
+ if (isEmpty(filepath))
48
+ throw new StorageException("The file path is required.");
49
+ return (await this.metadata(filepath)).type;
50
+ }
51
+ async lastModified(filepath) {
52
+ if (isEmpty(filepath))
53
+ throw new StorageException("The file path is required.");
54
+ return (await this.metadata(filepath)).lastModified;
55
+ }
56
+ async get(filepath) {
57
+ if (isEmpty(filepath))
58
+ throw new StorageException("The file path is required.");
59
+ return this.client.file(filepath);
60
+ }
61
+ async put(filepath, content, options) {
62
+ if (isEmpty(filepath))
63
+ throw new StorageException("The file path is required.");
64
+ if (isEmpty(content))
65
+ throw new StorageException("The content is required.");
66
+ try {
67
+ await this.client.write(filepath, content, options);
68
+ }
69
+ catch (error) {
70
+ Logger.setContext("Storage").error("Something went wrong when saving file.").trace(error);
71
+ }
72
+ }
73
+ async copy(source, destination, options) {
74
+ if (isEmpty(source))
75
+ throw new StorageException("The source file path is required.");
76
+ if (isEmpty(destination))
77
+ throw new StorageException("The destination file path is required.");
78
+ try {
79
+ await this.put(destination, await this.get(source), options);
80
+ }
81
+ catch (error) {
82
+ Logger.setContext("Storage").error("Something went wrong when copying file.").trace(error);
83
+ }
84
+ }
85
+ async move(source, destination, options) {
86
+ if (isEmpty(source))
87
+ throw new StorageException("The source file path is required.");
88
+ if (isEmpty(destination))
89
+ throw new StorageException("The destination file path is required.");
90
+ try {
91
+ await this.copy(source, destination, options);
92
+ await this.delete(source);
93
+ }
94
+ catch (error) {
95
+ Logger.setContext("Storage").error("Something went wrong when moving file.").trace(error);
96
+ }
97
+ }
98
+ async delete(filepath) {
99
+ if (isEmpty(filepath))
100
+ throw new StorageException("The file path is required.");
101
+ await this.client.file(filepath).delete();
102
+ }
103
+ }
@@ -0,0 +1,2 @@
1
+ declare const config: Record<string, any>;
2
+ export default config;
@@ -0,0 +1,26 @@
1
+ import App from "@bejibun/app";
2
+ import StorageDiskDriverEnum from "../enums/StorageDiskDriverEnum";
3
+ const config = {
4
+ default: "local",
5
+ disks: {
6
+ local: {
7
+ driver: StorageDiskDriverEnum.Local,
8
+ root: App.Path.storagePath("app")
9
+ },
10
+ public: {
11
+ driver: StorageDiskDriverEnum.Local,
12
+ root: App.Path.storagePath("app/public"),
13
+ url: `${Bun.env.APP_URL}/storage/public`
14
+ },
15
+ s3: {
16
+ driver: StorageDiskDriverEnum.S3,
17
+ endpoint: Bun.env.S3_ENDPOINT,
18
+ region: Bun.env.S3_REGION,
19
+ bucket: Bun.env.S3_BUCKET,
20
+ access_key_id: Bun.env.S3_ACCESS_KEY_ID,
21
+ secret_access_key: Bun.env.S3_SECRET_ACCESS_KEY,
22
+ url: ""
23
+ }
24
+ }
25
+ };
26
+ export default config;
package/configure.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/configure.js ADDED
@@ -0,0 +1,14 @@
1
+ import App from "@bejibun/app";
2
+ import Logger from "@bejibun/logger";
3
+ import path from "path";
4
+ const configPath = path.resolve(__dirname, "config");
5
+ const regex = /\.(m?js|ts)$/;
6
+ const configs = Array.from(new Bun.Glob("**/*").scanSync({
7
+ cwd: configPath
8
+ })).filter(value => (regex.test(value) &&
9
+ !value.endsWith(".d.ts")));
10
+ for (const config of configs) {
11
+ const destination = config.replace(regex, ".ts");
12
+ await Bun.write(App.Path.configPath(destination), await Bun.file(path.resolve(configPath, config)).text());
13
+ Logger.setContext("CONFIGURE").info(`Copying ${config} into config/${destination}`);
14
+ }
@@ -0,0 +1,5 @@
1
+ declare enum StorageDiskDriverEnum {
2
+ Local = "local",
3
+ S3 = "s3"
4
+ }
5
+ export default StorageDiskDriverEnum;
@@ -0,0 +1,6 @@
1
+ var StorageDiskDriverEnum;
2
+ (function (StorageDiskDriverEnum) {
3
+ StorageDiskDriverEnum["Local"] = "local";
4
+ StorageDiskDriverEnum["S3"] = "s3";
5
+ })(StorageDiskDriverEnum || (StorageDiskDriverEnum = {}));
6
+ export default StorageDiskDriverEnum;
@@ -0,0 +1 @@
1
+ export * from "../enums/StorageDiskDriverEnum";
package/enums/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "../enums/StorageDiskDriverEnum";
@@ -0,0 +1,4 @@
1
+ export default class StorageException extends Error {
2
+ code: number;
3
+ constructor(message?: string, code?: number);
4
+ }
@@ -0,0 +1,14 @@
1
+ import Logger from "@bejibun/logger";
2
+ import { defineValue } from "@bejibun/utils";
3
+ export default class StorageException extends Error {
4
+ code;
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.name = "StorageException";
8
+ this.code = defineValue(code, 503);
9
+ Logger.setContext(this.name).error(this.message).trace(this.stack);
10
+ if (Error.captureStackTrace) {
11
+ Error.captureStackTrace(this, StorageException);
12
+ }
13
+ }
14
+ }
@@ -0,0 +1 @@
1
+ export * from "../exceptions/StorageException";
@@ -0,0 +1 @@
1
+ export * from "../exceptions/StorageException";
@@ -0,0 +1,18 @@
1
+ import type { Stats } from "fs";
2
+ import type { StorageDisk, StorageOptions } from "../types/storage";
3
+ import StorageBuilder from "../builders/StorageBuilder";
4
+ export default class Storage {
5
+ static build(disk: StorageDisk): StorageBuilder;
6
+ static disk(disk: string): StorageBuilder;
7
+ static exists(path: string): Promise<boolean>;
8
+ static missing(path: string): Promise<boolean>;
9
+ static metadata(path: string): Promise<Stats | Bun.S3Stats>;
10
+ static size(path: string): Promise<number>;
11
+ static mimeType(path: string): Promise<string>;
12
+ static lastModified(path: string): Promise<Date>;
13
+ static get(path: string): Promise<Bun.BunFile | Bun.S3File>;
14
+ static put(path: string, content: any, options?: StorageOptions): Promise<void>;
15
+ static copy(source: string, destination: string, options?: StorageOptions): Promise<void>;
16
+ static move(source: string, destination: string, options?: StorageOptions): Promise<void>;
17
+ static delete(path: string): Promise<any>;
18
+ }
@@ -0,0 +1,42 @@
1
+ import StorageBuilder from "../builders/StorageBuilder";
2
+ export default class Storage {
3
+ static build(disk) {
4
+ return new StorageBuilder().build(disk);
5
+ }
6
+ static disk(disk) {
7
+ return new StorageBuilder().disk(disk);
8
+ }
9
+ static async exists(path) {
10
+ return await new StorageBuilder().exists(path);
11
+ }
12
+ static async missing(path) {
13
+ return await new StorageBuilder().missing(path);
14
+ }
15
+ static async metadata(path) {
16
+ return await new StorageBuilder().metadata(path);
17
+ }
18
+ static async size(path) {
19
+ return await new StorageBuilder().size(path);
20
+ }
21
+ static async mimeType(path) {
22
+ return await new StorageBuilder().mimeType(path);
23
+ }
24
+ static async lastModified(path) {
25
+ return await new StorageBuilder().lastModified(path);
26
+ }
27
+ static async get(path) {
28
+ return await new StorageBuilder().get(path);
29
+ }
30
+ static async put(path, content, options) {
31
+ return await new StorageBuilder().put(path, content);
32
+ }
33
+ static async copy(source, destination, options) {
34
+ return await new StorageBuilder().copy(source, destination, options);
35
+ }
36
+ static async move(source, destination, options) {
37
+ return await new StorageBuilder().move(source, destination, options);
38
+ }
39
+ static async delete(path) {
40
+ return await new StorageBuilder().delete(path);
41
+ }
42
+ }
@@ -0,0 +1 @@
1
+ export * from "../facades/Storage";
@@ -0,0 +1 @@
1
+ export * from "../facades/Storage";
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { default } from "./facades/Storage";
2
+ export * from "./enums/index";
3
+ export * from "./facades/index";
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default } from "./facades/Storage";
2
+ export * from "./enums/index";
3
+ export * from "./facades/index";
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@bejibun/storage",
3
+ "version": "0.1.0",
4
+ "author": "Havea Crenata <havea.crenata@gmail.com>",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Bejibun-Framework/bejibun-storage.git"
8
+ },
9
+ "main": "index.js",
10
+ "module": "index.js",
11
+ "dependencies": {
12
+ "@bejibun/app": "^0.1.24",
13
+ "@bejibun/logger": "^0.1.22",
14
+ "@bejibun/utils": "^0.1.28"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "latest",
18
+ "tsc-alias": "^1.9.1"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/Bejibun-Framework/bejibun-storage/issues"
22
+ },
23
+ "description": "Storage for Bejibun Framework",
24
+ "homepage": "https://github.com/Bejibun-Framework/bejibun-storage#readme",
25
+ "keywords": [
26
+ "bun",
27
+ "bun framework",
28
+ "storage",
29
+ "bejibun",
30
+ "typescript"
31
+ ],
32
+ "license": "MIT",
33
+ "scripts": {
34
+ "alias": "bunx tsc-alias -p tsconfig.json",
35
+ "types": "cp -rf src/types dist",
36
+ "rsync": "rsync -a dist/ ./",
37
+ "clean": "rm -rf dist",
38
+ "build": "bunx tsc -p tsconfig.json && bun run types && bun run alias && bun run rsync && bun run clean",
39
+ "deploy": "bun run build && bun publish --access public"
40
+ },
41
+ "type": "module",
42
+ "types": "index.d.ts"
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "declaration": true,
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "moduleResolution": "bundler",
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "paths": {
13
+ "@/*": ["./src/*"]
14
+ },
15
+ "ignoreDeprecations": "6.0",
16
+ "types": ["bun-types"]
17
+ }
18
+ }
@@ -0,0 +1 @@
1
+ export * from "../types/storage";
@@ -0,0 +1,138 @@
1
+ import type {Stats} from "fs";
2
+
3
+ export type StorageDisk = {
4
+ driver: string;
5
+ [key: string]: unknown;
6
+ };
7
+
8
+ export type StorageOptions = {
9
+ mode?: number;
10
+ createPath?: boolean;
11
+ acl?:
12
+ | "private"
13
+ | "public-read"
14
+ | "public-read-write"
15
+ | "aws-exec-read"
16
+ | "authenticated-read"
17
+ | "bucket-owner-read"
18
+ | "bucket-owner-full-control"
19
+ | "log-delivery-write";
20
+ bucket?: string;
21
+ region?: string;
22
+ accessKeyId?: string;
23
+ secretAccessKey?: string;
24
+ sessionToken?: string;
25
+ endpoint?: string;
26
+ virtualHostedStyle?: boolean;
27
+ partSize?: number;
28
+ queueSize?: number;
29
+ retry?: number;
30
+ type?: string;
31
+ contentDisposition?: string | undefined;
32
+ storageClass?:
33
+ | "STANDARD"
34
+ | "DEEP_ARCHIVE"
35
+ | "EXPRESS_ONEZONE"
36
+ | "GLACIER"
37
+ | "GLACIER_IR"
38
+ | "INTELLIGENT_TIERING"
39
+ | "ONEZONE_IA"
40
+ | "OUTPOSTS"
41
+ | "REDUCED_REDUNDANCY"
42
+ | "SNOW"
43
+ | "STANDARD_IA";
44
+ requestPayer?: boolean;
45
+ highWaterMark?: number;
46
+ };
47
+
48
+ export interface StorageDriver {
49
+ /**
50
+ * Determine whether a file exists.
51
+ *
52
+ * @param filepath The path to the file.
53
+ * @returns True if the file exists; otherwise false.
54
+ */
55
+ exists(filepath: string): Promise<boolean>;
56
+
57
+ /**
58
+ * Determine whether a file is missing.
59
+ *
60
+ * @param filepath The path to the file.
61
+ * @returns True if the file does not exist; otherwise false.
62
+ */
63
+ missing(filepath: string): Promise<boolean>;
64
+
65
+ /**
66
+ * Retrieve metadata for a file.
67
+ *
68
+ * @param filepath The path to the file.
69
+ * @returns File metadata and statistics.
70
+ */
71
+ metadata(filepath: string): Promise<Stats | Bun.S3Stats>;
72
+
73
+ /**
74
+ * Get the file size in bytes.
75
+ *
76
+ * @param filepath The path to the file.
77
+ * @returns The file size in bytes.
78
+ */
79
+ size(filepath: string): Promise<number>;
80
+
81
+ /**
82
+ * Get the file MIME type.
83
+ *
84
+ * @param filepath The path to the file.
85
+ * @returns The detected MIME type.
86
+ */
87
+ mimeType(filepath: string): Promise<string>;
88
+
89
+ /**
90
+ * Get the file's last modification date.
91
+ *
92
+ * @param filepath The path to the file.
93
+ * @returns The last modified timestamp.
94
+ */
95
+ lastModified(filepath: string): Promise<Date>;
96
+
97
+ /**
98
+ * Retrieve a file from storage.
99
+ *
100
+ * @param filepath The path to the file.
101
+ * @returns The storage file instance.
102
+ */
103
+ get(filepath: string): Promise<Bun.BunFile | Bun.S3File>;
104
+
105
+ /**
106
+ * Store content at the given path.
107
+ *
108
+ * @param filepath The destination file path.
109
+ * @param content The content to store.
110
+ * @param options Additional storage options.
111
+ */
112
+ put(filepath: string, content: any, options?: StorageOptions): Promise<void>;
113
+
114
+ /**
115
+ * Copy a file to a new location.
116
+ *
117
+ * @param source The source file path.
118
+ * @param destination The destination file path.
119
+ * @param options Additional storage options.
120
+ */
121
+ copy(source: string, destination: string, options?: StorageOptions): Promise<void>;
122
+
123
+ /**
124
+ * Move a file to a new location.
125
+ *
126
+ * @param source The source file path.
127
+ * @param destination The destination file path.
128
+ * @param options Additional storage options.
129
+ */
130
+ move(source: string, destination: string, options?: StorageOptions): Promise<void>;
131
+
132
+ /**
133
+ * Delete a file from storage.
134
+ *
135
+ * @param filepath The path to the file.
136
+ */
137
+ delete(filepath: string): Promise<void>;
138
+ }