@bejibun/storage 0.1.0 → 0.1.11
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/.prettierignore +45 -0
- package/.prettierrc.json +14 -0
- package/CHANGELOG.md +74 -0
- package/README.md +24 -0
- package/benchmarks/README.md +46 -0
- package/benchmarks/package.json +12 -0
- package/benchmarks/scripts/coldstart-baseline.mjs +9 -0
- package/benchmarks/scripts/coldstart-optimized.mjs +9 -0
- package/benchmarks/scripts/coldstart.mjs +93 -0
- package/benchmarks/scripts/readme-writer.mjs +35 -0
- package/benchmarks/scripts/table-format.mjs +123 -0
- package/benchmarks/scripts/throughput-baseline.mjs +68 -0
- package/benchmarks/scripts/throughput-optimized.mjs +68 -0
- package/benchmarks/scripts/throughput.mjs +83 -0
- package/builders/StorageBuilder.d.ts +117 -0
- package/builders/StorageBuilder.js +165 -32
- package/builders/storage/StorageLocalBuilder.d.ts +89 -0
- package/builders/storage/StorageLocalBuilder.js +119 -25
- package/builders/storage/StorageS3Builder.d.ts +90 -0
- package/builders/storage/StorageS3Builder.js +117 -22
- package/config/storage.d.ts +3 -0
- package/config/storage.js +8 -0
- package/configure.js +6 -2
- package/enums/StorageDiskDriverEnum.d.ts +5 -0
- package/enums/StorageDiskDriverEnum.js +5 -0
- package/enums/index.d.ts +4 -1
- package/enums/index.js +4 -1
- package/eslint.config.js +61 -0
- package/exceptions/StorageException.d.ts +10 -0
- package/exceptions/StorageException.js +10 -0
- package/exceptions/index.d.ts +4 -1
- package/exceptions/index.js +4 -1
- package/facades/Storage.d.ts +87 -0
- package/facades/Storage.js +88 -1
- package/facades/index.d.ts +4 -1
- package/facades/index.js +4 -1
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/package.json +23 -12
- package/tests/integration/storage.integration.test.ts +145 -0
- package/tests/unit/storage.test.ts +177 -0
- package/tsconfig.json +2 -2
- package/types/index.d.ts +4 -1
- package/types/storage.d.ts +138 -138
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throughput benchmark.
|
|
3
|
+
*
|
|
4
|
+
* Measures the hot paths touched on every storage operation, method by method. On baseline
|
|
5
|
+
* each `new StorageBuilder()` re-reads the config file from disk (`fs.existsSync` +
|
|
6
|
+
* `require()`) and re-resolves the driver; the optimized build caches the resolved config
|
|
7
|
+
* at module load and uses native nullish checks instead of `defineValue`/`isEmpty`.
|
|
8
|
+
* `exists`/`get`/`delete` exercise the full facade path against a temporary local disk.
|
|
9
|
+
*
|
|
10
|
+
* Run: bun run scripts/throughput.mjs
|
|
11
|
+
*/
|
|
12
|
+
import {spawnSync} from "node:child_process";
|
|
13
|
+
import {fileURLToPath} from "node:url";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import {printTable} from "./table-format.mjs";
|
|
16
|
+
import {updateReadmeSection} from "./readme-writer.mjs";
|
|
17
|
+
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const TRIALS = 9;
|
|
20
|
+
const runtime = process.execPath;
|
|
21
|
+
|
|
22
|
+
function runTrials(scriptPath) {
|
|
23
|
+
const results = [];
|
|
24
|
+
for (let i = 0; i < TRIALS; i++) {
|
|
25
|
+
const res = spawnSync(runtime, [scriptPath], {encoding: "utf8"});
|
|
26
|
+
if (res.status !== 0) {
|
|
27
|
+
console.error("Benchmark failed:", res.stderr);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
results.push(res.stdout.trim().split("|").map(Number));
|
|
31
|
+
}
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function medianRow(trials) {
|
|
36
|
+
const cols = trials[0].length;
|
|
37
|
+
const medians = [];
|
|
38
|
+
for (let c = 0; c < cols; c++) {
|
|
39
|
+
const sorted = trials.map((t) => t[c]).sort((a, b) => a - b);
|
|
40
|
+
medians.push(sorted[Math.floor(sorted.length / 2)]);
|
|
41
|
+
}
|
|
42
|
+
return medians;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ITERATIONS = 20_000;
|
|
46
|
+
const methods = ["construction", "exists", "get", "delete"];
|
|
47
|
+
const bCols = medianRow(runTrials(path.join(__dirname, "throughput-baseline.mjs")));
|
|
48
|
+
const oCols = medianRow(runTrials(path.join(__dirname, "throughput-optimized.mjs")));
|
|
49
|
+
|
|
50
|
+
function fmt(ms) {
|
|
51
|
+
return ms < 1 ? `${(ms * 1000).toFixed(0)}\u00B5s` : `${ms.toFixed(1)}ms`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sp(b, o) {
|
|
55
|
+
const r = b / o;
|
|
56
|
+
return r >= 1.05 ? `${r.toFixed(2)}x` : r <= 0.95 ? `${r.toFixed(2)}x` : "~1.0x";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ops(ms) {
|
|
60
|
+
return Math.round(ITERATIONS / (ms / 1000)).toLocaleString() + "/s";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const rows = methods.map((m, i) => ({
|
|
64
|
+
cells: [m, fmt(bCols[i]), fmt(oCols[i]), sp(bCols[i], oCols[i]), ops(oCols[i])]
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
printTable({
|
|
68
|
+
title: "THROUGHPUT BENCHMARK",
|
|
69
|
+
subtitle: `${ITERATIONS.toLocaleString()} calls each, ${TRIALS} runs (median)`,
|
|
70
|
+
headers: ["Method", "Baseline (0.1.1)", "Optimized", "Speedup", "Optimized ops/s"],
|
|
71
|
+
rows
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const lines = [
|
|
75
|
+
"| Method | baseline (0.1.1) | optimized | speedup | baseline ops/s | optimized ops/s |",
|
|
76
|
+
"|---|---|---|---|---|---|",
|
|
77
|
+
...methods.map(
|
|
78
|
+
(m, i) =>
|
|
79
|
+
`| \`${m}\` | ${bCols[i].toFixed(1)}ms | ${oCols[i].toFixed(1)}ms | **${(bCols[i] / oCols[i]).toFixed(2)}x** | ${ops(bCols[i])} | ${ops(oCols[i])} |`
|
|
80
|
+
)
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
updateReadmeSection("THROUGHPUT", lines.join("\n"));
|
|
@@ -1,24 +1,141 @@
|
|
|
1
1
|
import type { Stats } from "fs";
|
|
2
2
|
import type { StorageDisk, StorageOptions } from "../types/storage";
|
|
3
|
+
/**
|
|
4
|
+
* Builds and dispatches storage operations to the configured disk driver.
|
|
5
|
+
*/
|
|
3
6
|
export default class StorageBuilder {
|
|
7
|
+
/** The loaded storage configuration. */
|
|
4
8
|
protected conf: Record<string, any>;
|
|
9
|
+
/** An optional disk override applied when building a driver. */
|
|
5
10
|
protected overrideDisk?: StorageDisk;
|
|
11
|
+
/** The name of the selected disk. */
|
|
6
12
|
protected drive?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Load the storage configuration from the app config or the built-in default.
|
|
15
|
+
*/
|
|
7
16
|
constructor();
|
|
17
|
+
/**
|
|
18
|
+
* Get the storage configuration, throwing if it is empty.
|
|
19
|
+
*
|
|
20
|
+
* @returns {Record<string, any>} The storage configuration.
|
|
21
|
+
* @throws {StorageException} When no configuration is provided.
|
|
22
|
+
*/
|
|
8
23
|
private get config();
|
|
24
|
+
/**
|
|
25
|
+
* Get the configuration for the current disk.
|
|
26
|
+
*
|
|
27
|
+
* @returns {any} The current disk configuration.
|
|
28
|
+
*/
|
|
9
29
|
private get currentDisk();
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the driver instance for the current disk.
|
|
32
|
+
*
|
|
33
|
+
* @returns {StorageDriver} The resolved storage driver.
|
|
34
|
+
* @throws {StorageException} When the driver is missing or unsupported.
|
|
35
|
+
*/
|
|
10
36
|
private get driver();
|
|
37
|
+
/**
|
|
38
|
+
* Override the disk used for subsequent operations.
|
|
39
|
+
*
|
|
40
|
+
* @param {StorageDisk} overrideDisk - The disk configuration to use.
|
|
41
|
+
* @returns {StorageBuilder} This builder instance.
|
|
42
|
+
*/
|
|
11
43
|
build(overrideDisk: StorageDisk): StorageBuilder;
|
|
44
|
+
/**
|
|
45
|
+
* Select the disk by name for subsequent operations.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} drive - The name of the disk to use.
|
|
48
|
+
* @returns {StorageBuilder} This builder instance.
|
|
49
|
+
*/
|
|
12
50
|
disk(drive: string): StorageBuilder;
|
|
51
|
+
/**
|
|
52
|
+
* Determine whether a file exists.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} filepath - The path to the file.
|
|
55
|
+
* @returns {Promise<boolean>} True if the file exists; otherwise false.
|
|
56
|
+
* @throws {StorageException} When the file path is empty.
|
|
57
|
+
*/
|
|
13
58
|
exists(filepath: string): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Determine whether a file is missing.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} filepath - The path to the file.
|
|
63
|
+
* @returns {Promise<boolean>} True if the file does not exist; otherwise false.
|
|
64
|
+
* @throws {StorageException} When the file path is empty.
|
|
65
|
+
*/
|
|
14
66
|
missing(filepath: string): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Retrieve metadata for a file.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} filepath - The path to the file.
|
|
71
|
+
* @returns {Promise<Stats | Bun.S3Stats>} File metadata and statistics.
|
|
72
|
+
* @throws {StorageException} When the file path is empty.
|
|
73
|
+
*/
|
|
15
74
|
metadata(filepath: string): Promise<Stats | Bun.S3Stats>;
|
|
75
|
+
/**
|
|
76
|
+
* Get the file size in bytes.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} filepath - The path to the file.
|
|
79
|
+
* @returns {Promise<number>} The file size in bytes.
|
|
80
|
+
* @throws {StorageException} When the file path is empty.
|
|
81
|
+
*/
|
|
16
82
|
size(filepath: string): Promise<number>;
|
|
83
|
+
/**
|
|
84
|
+
* Get the file MIME type.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} filepath - The path to the file.
|
|
87
|
+
* @returns {Promise<string>} The detected MIME type.
|
|
88
|
+
* @throws {StorageException} When the file path is empty.
|
|
89
|
+
*/
|
|
17
90
|
mimeType(filepath: string): Promise<string>;
|
|
91
|
+
/**
|
|
92
|
+
* Get the file's last modification date.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} filepath - The path to the file.
|
|
95
|
+
* @returns {Promise<Date>} The last modified timestamp.
|
|
96
|
+
* @throws {StorageException} When the file path is empty.
|
|
97
|
+
*/
|
|
18
98
|
lastModified(filepath: string): Promise<Date>;
|
|
99
|
+
/**
|
|
100
|
+
* Retrieve a file from storage.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} filepath - The path to the file.
|
|
103
|
+
* @returns {Promise<Bun.BunFile | Bun.S3File>} The storage file instance.
|
|
104
|
+
* @throws {StorageException} When the file path is empty.
|
|
105
|
+
*/
|
|
19
106
|
get(filepath: string): Promise<Bun.BunFile | Bun.S3File>;
|
|
107
|
+
/**
|
|
108
|
+
* Store content at the given path.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} filepath - The destination file path.
|
|
111
|
+
* @param {any} content - The content to store.
|
|
112
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
113
|
+
* @throws {StorageException} When the file path or content is empty.
|
|
114
|
+
*/
|
|
20
115
|
put(filepath: string, content: any, options?: StorageOptions): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Copy a file to a new location.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} source - The source file path.
|
|
120
|
+
* @param {string} destination - The destination file path.
|
|
121
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
122
|
+
* @throws {StorageException} When the source or destination path is empty.
|
|
123
|
+
*/
|
|
21
124
|
copy(source: string, destination: string, options?: StorageOptions): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Move a file to a new location.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} source - The source file path.
|
|
129
|
+
* @param {string} destination - The destination file path.
|
|
130
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
131
|
+
* @throws {StorageException} When the source or destination path is empty.
|
|
132
|
+
*/
|
|
22
133
|
move(source: string, destination: string, options?: StorageOptions): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Delete a file from storage.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} filepath - The path to the file.
|
|
138
|
+
* @throws {StorageException} When the file path is empty.
|
|
139
|
+
*/
|
|
23
140
|
delete(filepath: string): Promise<void>;
|
|
24
141
|
}
|
|
@@ -1,37 +1,73 @@
|
|
|
1
1
|
import App from "@bejibun/app";
|
|
2
2
|
import Logger from "@bejibun/logger";
|
|
3
|
-
import { defineValue
|
|
3
|
+
import { defineValue } from "@bejibun/utils";
|
|
4
4
|
import Enum from "@bejibun/utils/facades/Enum";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import StorageS3Builder from "../builders/storage/StorageS3Builder";
|
|
8
|
-
import StorageConfig from "../config/storage";
|
|
5
|
+
import StorageLocalBuilder from "./storage/StorageLocalBuilder";
|
|
6
|
+
import StorageS3Builder from "./storage/StorageS3Builder";
|
|
9
7
|
import StorageDiskDriverEnum from "../enums/StorageDiskDriverEnum";
|
|
10
8
|
import StorageException from "../exceptions/StorageException";
|
|
9
|
+
/** The app storage config, loaded once from disk. */
|
|
10
|
+
let cachedConfig;
|
|
11
|
+
/**
|
|
12
|
+
* Loads the app storage config from disk once, falling back to the built-in default.
|
|
13
|
+
*
|
|
14
|
+
* @returns {any} The loaded storage configuration.
|
|
15
|
+
*/
|
|
16
|
+
const loadConfig = () => {
|
|
17
|
+
if (cachedConfig)
|
|
18
|
+
return cachedConfig;
|
|
19
|
+
try {
|
|
20
|
+
cachedConfig = require(App.Path.configPath("storage.ts")).default;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
cachedConfig = require("../config/storage").default;
|
|
24
|
+
}
|
|
25
|
+
return cachedConfig;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Builds and dispatches storage operations to the configured disk driver.
|
|
29
|
+
*/
|
|
11
30
|
export default class StorageBuilder {
|
|
31
|
+
/** The loaded storage configuration. */
|
|
12
32
|
conf;
|
|
33
|
+
/** An optional disk override applied when building a driver. */
|
|
13
34
|
overrideDisk;
|
|
35
|
+
/** The name of the selected disk. */
|
|
14
36
|
drive;
|
|
37
|
+
/**
|
|
38
|
+
* Load the storage configuration from the app config or the built-in default.
|
|
39
|
+
*/
|
|
15
40
|
constructor() {
|
|
16
|
-
|
|
17
|
-
let config;
|
|
18
|
-
if (fs.existsSync(configPath))
|
|
19
|
-
config = require(configPath).default;
|
|
20
|
-
else
|
|
21
|
-
config = StorageConfig;
|
|
22
|
-
this.conf = config;
|
|
41
|
+
this.conf = loadConfig();
|
|
23
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the storage configuration, throwing if it is empty.
|
|
45
|
+
*
|
|
46
|
+
* @returns {Record<string, any>} The storage configuration.
|
|
47
|
+
* @throws {StorageException} When no configuration is provided.
|
|
48
|
+
*/
|
|
24
49
|
get config() {
|
|
25
|
-
if (
|
|
50
|
+
if (!this.conf)
|
|
26
51
|
throw new StorageException("There is no config provided.");
|
|
27
52
|
return this.conf;
|
|
28
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the configuration for the current disk.
|
|
56
|
+
*
|
|
57
|
+
* @returns {any} The current disk configuration.
|
|
58
|
+
*/
|
|
29
59
|
get currentDisk() {
|
|
30
60
|
return defineValue(this.overrideDisk, this.config.disks[defineValue(this.drive, this.config.default)]);
|
|
31
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the driver instance for the current disk.
|
|
64
|
+
*
|
|
65
|
+
* @returns {StorageDriver} The resolved storage driver.
|
|
66
|
+
* @throws {StorageException} When the driver is missing or unsupported.
|
|
67
|
+
*/
|
|
32
68
|
get driver() {
|
|
33
69
|
const driver = defineValue(this.currentDisk?.driver);
|
|
34
|
-
if (
|
|
70
|
+
if (!driver)
|
|
35
71
|
throw new StorageException(`Missing "driver" on disk config.`);
|
|
36
72
|
if (!Enum.setEnums(StorageDiskDriverEnum).hasValue(driver))
|
|
37
73
|
throw new StorageException(`Not supported "driver" disk.`);
|
|
@@ -44,87 +80,184 @@ export default class StorageBuilder {
|
|
|
44
80
|
throw new StorageException(`Not supported "driver" disk.`);
|
|
45
81
|
}
|
|
46
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Override the disk used for subsequent operations.
|
|
85
|
+
*
|
|
86
|
+
* @param {StorageDisk} overrideDisk - The disk configuration to use.
|
|
87
|
+
* @returns {StorageBuilder} This builder instance.
|
|
88
|
+
*/
|
|
47
89
|
build(overrideDisk) {
|
|
48
90
|
this.overrideDisk = overrideDisk;
|
|
49
91
|
return this;
|
|
50
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Select the disk by name for subsequent operations.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} drive - The name of the disk to use.
|
|
97
|
+
* @returns {StorageBuilder} This builder instance.
|
|
98
|
+
*/
|
|
51
99
|
disk(drive) {
|
|
52
100
|
this.drive = drive;
|
|
53
101
|
return this;
|
|
54
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Determine whether a file exists.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} filepath - The path to the file.
|
|
107
|
+
* @returns {Promise<boolean>} True if the file exists; otherwise false.
|
|
108
|
+
* @throws {StorageException} When the file path is empty.
|
|
109
|
+
*/
|
|
55
110
|
async exists(filepath) {
|
|
56
|
-
if (
|
|
111
|
+
if (!filepath)
|
|
57
112
|
throw new StorageException("The file path is required.");
|
|
58
113
|
return await this.driver.exists(filepath);
|
|
59
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Determine whether a file is missing.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} filepath - The path to the file.
|
|
119
|
+
* @returns {Promise<boolean>} True if the file does not exist; otherwise false.
|
|
120
|
+
* @throws {StorageException} When the file path is empty.
|
|
121
|
+
*/
|
|
60
122
|
async missing(filepath) {
|
|
61
|
-
if (
|
|
123
|
+
if (!filepath)
|
|
62
124
|
throw new StorageException("The file path is required.");
|
|
63
|
-
return
|
|
125
|
+
return await this.driver.missing(filepath);
|
|
64
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Retrieve metadata for a file.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} filepath - The path to the file.
|
|
131
|
+
* @returns {Promise<Stats | Bun.S3Stats>} File metadata and statistics.
|
|
132
|
+
* @throws {StorageException} When the file path is empty.
|
|
133
|
+
*/
|
|
65
134
|
async metadata(filepath) {
|
|
66
|
-
if (
|
|
135
|
+
if (!filepath)
|
|
67
136
|
throw new StorageException("The file path is required.");
|
|
68
137
|
return await this.driver.metadata(filepath);
|
|
69
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Get the file size in bytes.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} filepath - The path to the file.
|
|
143
|
+
* @returns {Promise<number>} The file size in bytes.
|
|
144
|
+
* @throws {StorageException} When the file path is empty.
|
|
145
|
+
*/
|
|
70
146
|
async size(filepath) {
|
|
71
|
-
if (
|
|
147
|
+
if (!filepath)
|
|
72
148
|
throw new StorageException("The file path is required.");
|
|
73
149
|
return await this.driver.size(filepath);
|
|
74
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Get the file MIME type.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} filepath - The path to the file.
|
|
155
|
+
* @returns {Promise<string>} The detected MIME type.
|
|
156
|
+
* @throws {StorageException} When the file path is empty.
|
|
157
|
+
*/
|
|
75
158
|
async mimeType(filepath) {
|
|
76
|
-
if (
|
|
159
|
+
if (!filepath)
|
|
77
160
|
throw new StorageException("The file path is required.");
|
|
78
161
|
return await this.driver.mimeType(filepath);
|
|
79
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the file's last modification date.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} filepath - The path to the file.
|
|
167
|
+
* @returns {Promise<Date>} The last modified timestamp.
|
|
168
|
+
* @throws {StorageException} When the file path is empty.
|
|
169
|
+
*/
|
|
80
170
|
async lastModified(filepath) {
|
|
81
|
-
if (
|
|
171
|
+
if (!filepath)
|
|
82
172
|
throw new StorageException("The file path is required.");
|
|
83
173
|
return await this.driver.lastModified(filepath);
|
|
84
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Retrieve a file from storage.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} filepath - The path to the file.
|
|
179
|
+
* @returns {Promise<Bun.BunFile | Bun.S3File>} The storage file instance.
|
|
180
|
+
* @throws {StorageException} When the file path is empty.
|
|
181
|
+
*/
|
|
85
182
|
async get(filepath) {
|
|
86
|
-
if (
|
|
183
|
+
if (!filepath)
|
|
87
184
|
throw new StorageException("The file path is required.");
|
|
88
185
|
return await this.driver.get(filepath);
|
|
89
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Store content at the given path.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} filepath - The destination file path.
|
|
191
|
+
* @param {any} content - The content to store.
|
|
192
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
193
|
+
* @throws {StorageException} When the file path or content is empty.
|
|
194
|
+
*/
|
|
90
195
|
async put(filepath, content, options) {
|
|
91
|
-
if (
|
|
196
|
+
if (!filepath)
|
|
92
197
|
throw new StorageException("The file path is required.");
|
|
93
|
-
if (
|
|
198
|
+
if (!content)
|
|
94
199
|
throw new StorageException("The content is required.");
|
|
95
200
|
try {
|
|
96
201
|
await this.driver.put(filepath, content, options);
|
|
97
202
|
}
|
|
98
203
|
catch (error) {
|
|
99
|
-
Logger.setContext("Storage")
|
|
204
|
+
Logger.setContext("Storage")
|
|
205
|
+
.error("Something went wrong when saving file.")
|
|
206
|
+
.trace(error);
|
|
100
207
|
}
|
|
101
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Copy a file to a new location.
|
|
211
|
+
*
|
|
212
|
+
* @param {string} source - The source file path.
|
|
213
|
+
* @param {string} destination - The destination file path.
|
|
214
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
215
|
+
* @throws {StorageException} When the source or destination path is empty.
|
|
216
|
+
*/
|
|
102
217
|
async copy(source, destination, options) {
|
|
103
|
-
if (
|
|
218
|
+
if (!source)
|
|
104
219
|
throw new StorageException("The source file path is required.");
|
|
105
|
-
if (
|
|
220
|
+
if (!destination)
|
|
106
221
|
throw new StorageException("The destination file path is required.");
|
|
107
222
|
try {
|
|
108
223
|
await this.driver.copy(source, destination, options);
|
|
109
224
|
}
|
|
110
225
|
catch (error) {
|
|
111
|
-
Logger.setContext("Storage")
|
|
226
|
+
Logger.setContext("Storage")
|
|
227
|
+
.error("Something went wrong when copying file.")
|
|
228
|
+
.trace(error);
|
|
112
229
|
}
|
|
113
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Move a file to a new location.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} source - The source file path.
|
|
235
|
+
* @param {string} destination - The destination file path.
|
|
236
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
237
|
+
* @throws {StorageException} When the source or destination path is empty.
|
|
238
|
+
*/
|
|
114
239
|
async move(source, destination, options) {
|
|
115
|
-
if (
|
|
240
|
+
if (!source)
|
|
116
241
|
throw new StorageException("The source file path is required.");
|
|
117
|
-
if (
|
|
242
|
+
if (!destination)
|
|
118
243
|
throw new StorageException("The destination file path is required.");
|
|
119
244
|
try {
|
|
120
245
|
await this.driver.move(source, destination, options);
|
|
121
246
|
}
|
|
122
247
|
catch (error) {
|
|
123
|
-
Logger.setContext("Storage")
|
|
248
|
+
Logger.setContext("Storage")
|
|
249
|
+
.error("Something went wrong when moving file.")
|
|
250
|
+
.trace(error);
|
|
124
251
|
}
|
|
125
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Delete a file from storage.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} filepath - The path to the file.
|
|
257
|
+
* @throws {StorageException} When the file path is empty.
|
|
258
|
+
*/
|
|
126
259
|
async delete(filepath) {
|
|
127
|
-
if (
|
|
260
|
+
if (!filepath)
|
|
128
261
|
throw new StorageException("The file path is required.");
|
|
129
262
|
await this.driver.delete(filepath);
|
|
130
263
|
}
|
|
@@ -1,18 +1,107 @@
|
|
|
1
1
|
import type { Stats } from "fs";
|
|
2
2
|
import type { StorageDriver, StorageOptions } from "../../types/storage";
|
|
3
|
+
/**
|
|
4
|
+
* Local filesystem storage driver backed by Bun file utilities.
|
|
5
|
+
*/
|
|
3
6
|
export default class StorageLocalBuilder implements StorageDriver {
|
|
7
|
+
/** The Local disk configuration. */
|
|
4
8
|
protected _config: Record<string, any>;
|
|
9
|
+
/**
|
|
10
|
+
* Create a local storage driver from disk configuration.
|
|
11
|
+
*
|
|
12
|
+
* @param {Record<string, any>} config - The local disk configuration.
|
|
13
|
+
* @throws {StorageException} When the root path is missing.
|
|
14
|
+
*/
|
|
5
15
|
constructor(config: Record<string, any>);
|
|
6
16
|
private get config();
|
|
17
|
+
/**
|
|
18
|
+
* Determine whether a file exists.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} filepath - The path to the file.
|
|
21
|
+
* @returns {Promise<boolean>} True if the file exists; otherwise false.
|
|
22
|
+
* @throws {StorageException} When the file path is empty.
|
|
23
|
+
*/
|
|
7
24
|
exists(filepath: string): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Determine whether a file is missing.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} filepath - The path to the file.
|
|
29
|
+
* @returns {Promise<boolean>} True if the file does not exist; otherwise false.
|
|
30
|
+
* @throws {StorageException} When the file path is empty.
|
|
31
|
+
*/
|
|
8
32
|
missing(filepath: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Retrieve metadata for a file.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} filepath - The path to the file.
|
|
37
|
+
* @returns {Promise<Stats>} File metadata and statistics.
|
|
38
|
+
* @throws {StorageException} When the file path is empty.
|
|
39
|
+
*/
|
|
9
40
|
metadata(filepath: string): Promise<Stats>;
|
|
41
|
+
/**
|
|
42
|
+
* Get the file size in bytes.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} filepath - The path to the file.
|
|
45
|
+
* @returns {Promise<number>} The file size in bytes.
|
|
46
|
+
* @throws {StorageException} When the file path is empty.
|
|
47
|
+
*/
|
|
10
48
|
size(filepath: string): Promise<number>;
|
|
49
|
+
/**
|
|
50
|
+
* Get the file MIME type.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} filepath - The path to the file.
|
|
53
|
+
* @returns {Promise<string>} The detected MIME type.
|
|
54
|
+
* @throws {StorageException} When the file path is empty.
|
|
55
|
+
*/
|
|
11
56
|
mimeType(filepath: string): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Get the file's last modification date.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} filepath - The path to the file.
|
|
61
|
+
* @returns {Promise<Date>} The last modified timestamp.
|
|
62
|
+
* @throws {StorageException} When the file path is empty.
|
|
63
|
+
*/
|
|
12
64
|
lastModified(filepath: string): Promise<Date>;
|
|
65
|
+
/**
|
|
66
|
+
* Retrieve a file from storage.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} filepath - The path to the file.
|
|
69
|
+
* @returns {Promise<Bun.BunFile>} The local file instance.
|
|
70
|
+
* @throws {StorageException} When the file path is empty.
|
|
71
|
+
*/
|
|
13
72
|
get(filepath: string): Promise<Bun.BunFile>;
|
|
73
|
+
/**
|
|
74
|
+
* Store content at the given path.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} filepath - The destination file path.
|
|
77
|
+
* @param {any} content - The content to store.
|
|
78
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
79
|
+
* @throws {StorageException} When the file path or content is empty.
|
|
80
|
+
*/
|
|
14
81
|
put(filepath: string, content: any, options?: StorageOptions): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Copy a file to a new location.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} source - The source file path.
|
|
86
|
+
* @param {string} destination - The destination file path.
|
|
87
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
88
|
+
* @throws {StorageException} When the source or destination path is empty.
|
|
89
|
+
*/
|
|
15
90
|
copy(source: string, destination: string, options?: StorageOptions): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Move a file to a new location.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} source - The source file path.
|
|
95
|
+
* @param {string} destination - The destination file path.
|
|
96
|
+
* @param {StorageOptions} options - Additional storage options.
|
|
97
|
+
* @throws {StorageException} When the source or destination path is empty.
|
|
98
|
+
*/
|
|
16
99
|
move(source: string, destination: string, options?: StorageOptions): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Delete a file from storage.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} filepath - The path to the file.
|
|
104
|
+
* @throws {StorageException} When the file path is empty.
|
|
105
|
+
*/
|
|
17
106
|
delete(filepath: string): Promise<void>;
|
|
18
107
|
}
|