@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.
Files changed (44) hide show
  1. package/.prettierignore +45 -0
  2. package/.prettierrc.json +14 -0
  3. package/CHANGELOG.md +74 -0
  4. package/README.md +24 -0
  5. package/benchmarks/README.md +46 -0
  6. package/benchmarks/package.json +12 -0
  7. package/benchmarks/scripts/coldstart-baseline.mjs +9 -0
  8. package/benchmarks/scripts/coldstart-optimized.mjs +9 -0
  9. package/benchmarks/scripts/coldstart.mjs +93 -0
  10. package/benchmarks/scripts/readme-writer.mjs +35 -0
  11. package/benchmarks/scripts/table-format.mjs +123 -0
  12. package/benchmarks/scripts/throughput-baseline.mjs +68 -0
  13. package/benchmarks/scripts/throughput-optimized.mjs +68 -0
  14. package/benchmarks/scripts/throughput.mjs +83 -0
  15. package/builders/StorageBuilder.d.ts +117 -0
  16. package/builders/StorageBuilder.js +165 -32
  17. package/builders/storage/StorageLocalBuilder.d.ts +89 -0
  18. package/builders/storage/StorageLocalBuilder.js +119 -25
  19. package/builders/storage/StorageS3Builder.d.ts +90 -0
  20. package/builders/storage/StorageS3Builder.js +117 -22
  21. package/config/storage.d.ts +3 -0
  22. package/config/storage.js +8 -0
  23. package/configure.js +6 -2
  24. package/enums/StorageDiskDriverEnum.d.ts +5 -0
  25. package/enums/StorageDiskDriverEnum.js +5 -0
  26. package/enums/index.d.ts +4 -1
  27. package/enums/index.js +4 -1
  28. package/eslint.config.js +61 -0
  29. package/exceptions/StorageException.d.ts +10 -0
  30. package/exceptions/StorageException.js +10 -0
  31. package/exceptions/index.d.ts +4 -1
  32. package/exceptions/index.js +4 -1
  33. package/facades/Storage.d.ts +87 -0
  34. package/facades/Storage.js +88 -1
  35. package/facades/index.d.ts +4 -1
  36. package/facades/index.js +4 -1
  37. package/index.d.ts +4 -0
  38. package/index.js +4 -0
  39. package/package.json +23 -12
  40. package/tests/integration/storage.integration.test.ts +145 -0
  41. package/tests/unit/storage.test.ts +177 -0
  42. package/tsconfig.json +2 -2
  43. package/types/index.d.ts +4 -1
  44. package/types/storage.d.ts +138 -138
@@ -0,0 +1,45 @@
1
+ # dependencies (bun install)
2
+ node_modules
3
+
4
+ # output
5
+ out
6
+ dist
7
+ *.tgz
8
+
9
+ # code coverage
10
+ coverage
11
+ *.lcov
12
+
13
+ # logs
14
+ logs
15
+ _.log
16
+ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17
+
18
+ # dotenv environment variable files
19
+ .env
20
+ .env.development.local
21
+ .env.test.local
22
+ .env.production.local
23
+ .env.local
24
+
25
+ # caches
26
+ .eslintcache
27
+ .cache
28
+ *.tsbuildinfo
29
+
30
+ # storage (runtime/framework generated content)
31
+ storage/app
32
+ storage/cache
33
+ storage/framework
34
+
35
+ # public
36
+ public
37
+
38
+ # bun
39
+ bun.lock
40
+
41
+ # IntelliJ based IDEs
42
+ .idea
43
+
44
+ # Finder (MacOS) folder config
45
+ .DS_Store
@@ -0,0 +1,14 @@
1
+ {
2
+ "tabWidth": 4,
3
+ "useTabs": false,
4
+ "semi": true,
5
+ "singleQuote": false,
6
+ "quoteProps": "as-needed",
7
+ "trailingComma": "none",
8
+ "bracketSpacing": false,
9
+ "bracketSameLine": false,
10
+ "arrowParens": "always",
11
+ "printWidth": 100,
12
+ "endOfLine": "lf",
13
+ "jsxSingleQuote": false
14
+ }
package/CHANGELOG.md CHANGED
@@ -3,6 +3,80 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  ---
5
5
 
6
+ ## [v0.1.11](https://github.com/Bejibun-Framework/bejibun-storage/compare/v0.1.1...v0.1.11) - 2026-09-04
7
+
8
+ ### 🩹 Fixes
9
+ - Fixed `StorageBuilder.missing` which used a double negation (`!(await driver.missing(...))`) and therefore returned `exists` instead of the correct "missing" result
10
+
11
+ ### 📖 Changes
12
+ #### Performance
13
+ - `StorageBuilder` now loads and caches the storage config once via a lazy `loadConfig()` (module-level `cachedConfig`), instead of reading `storage.ts` from disk with `fs.existsSync` + `require()` on every `new StorageBuilder()`
14
+ - Replaced the `@bejibun/utils` `defineValue`/`isEmpty` calls with native nullish coalescing (`??`) and truthiness checks in `StorageBuilder`, `StorageLocalBuilder`, and `StorageS3Builder`
15
+ - `StorageLocalBuilder` caches the resolved `root` once (validated in the constructor) and resolves paths through a single helper instead of re-validating on every operation
16
+ - `StorageS3Builder` validates the required S3 fields once in the constructor and reuses the plain config, instead of re-running the validation getter on every access
17
+
18
+ #### Docs
19
+ - Added missing `@returns` annotations (with descriptions) to every `Storage` facade method, matching the cache and limiter facade style; aligned the `@param`/`@returns` JSDoc format with the sibling packages
20
+ - Re-exported the `StorageException` classes from the package root (`@/exceptions/index`) for consistency with the cache and limiter entry points
21
+ - Standardized `StorageBuilder` config handling to the same lazy `loadConfig()` pattern used by `CacheBuilder`
22
+
23
+ ### 🧪 Tests
24
+ - Added unit test suite (17 tests across 1 file in `tests/unit`) covering `StorageBuilder` input validation (empty path/content/source/destination rejections across `exists`, `missing`, `metadata`, `size`, `mimeType`, `lastModified`, `get`, `put`, `copy`, `move`, `delete`) and the `Storage` facade delegation, with silenced logger output
25
+ - Added an integration test suite (7 tests across 1 file in `tests/integration`) exercising the real local disk driver end to end against a temporary directory: `put`/`exists`/`missing`/`size`/`get`, `metadata`/`lastModified`, `copy`, `move`, and `delete`
26
+ - Added `test` (unit) and `test:integration` scripts and added `tests` to tsconfig `exclude` so compiled output never lands in `tests/`
27
+
28
+ ### ⚡ Benchmarks
29
+ - Added benchmark suite comparing baseline (`@bejibun/storage@0.1.1`) vs the optimized build, covering `construction`, `exists`, `get`, and `delete` hot paths against a temporary local disk; full results are written to `benchmarks/README.md` between the `BENCHMARK` markers
30
+ - Also added a cold-start suite spawning 30 fresh OS processes per variant and measuring full process time and import time
31
+ - Throughput results on the local disk backend: `construction` **26.64x** (40.0 vs 1.5ms, ~13.3M ops/s), `exists` 1.85x (117.2 vs 63.2ms), `get` 3.50x (74.7 vs 21.3ms), `delete` ~1.03x (3896.7 vs 3775.0ms, I/O bound); cold start ~1.02x
32
+
33
+ ### 📦 Dependencies
34
+
35
+ - Bumped [`@bejibun/app`](https://github.com/Bejibun-Framework/bejibun-app) from `^0.1.25` to `^0.1.26`
36
+ - Bumped [`@bejibun/logger`](https://github.com/Bejibun-Framework/bejibun-logger) from `^0.1.23` to `^0.2.1`
37
+ - Bumped [`@bejibun/utils`](https://github.com/Bejibun-Framework/bejibun-utils) from `^0.1.29` to `^0.1.30`
38
+ - Bumped `@types/bun` (devDependency) from `^1.3.14` to `^1.4.0`
39
+ - Bumped `eslint` (devDependency) from `^10.8.1` to `^10.9.1`
40
+ - Bumped `globals` (devDependency) from `^17.11.0` to `^17.12.0`
41
+ - Bumped `tsc-alias` (devDependency) from `^1.9.2` to `^1.9.4`
42
+ - Bumped `typescript-eslint` (devDependency) from `^8.67.0` to `^8.69.0`
43
+
44
+ ### ❤️Contributors
45
+ - Havea Crenata ([@crenata](https://github.com/crenata))
46
+
47
+ **Full Changelog**: https://github.com/Bejibun-Framework/bejibun-storage/blob/master/CHANGELOG.md
48
+
49
+ ---
50
+
51
+ ## [v0.1.1](https://github.com/Bejibun-Framework/bejibun-storage/compare/v0.1.0...v0.1.1) - 2026-08-20
52
+
53
+ ### 🩹 Fixes
54
+ - Fix missing `options` in `Storage.put()`
55
+
56
+ ### 📖 Changes
57
+ #### Tooling
58
+ - Added `prettier` + `.prettierrc.json` / `.prettierignore` and an `eslint.config.js` (flat config, `typescript-eslint`) for consistent formatting/linting across `src`
59
+ - Added `bun run format`, `bun run eslint`, and `bun run lint` scripts; `bun run build` now runs `lint` before compiling
60
+ - `alias` script now runs `tsc-alias` directly instead of via `bunx`
61
+
62
+ ### 📦 Dependencies
63
+
64
+ - Bumped `tsc-alias` (devDependency) from `^1.8.16` to `^1.9.2`
65
+ - Added `@eslint/js` (devDependency) `^10.0.1`
66
+ - Added `eslint` (devDependency) `^10.8.1`
67
+ - Added `eslint-config-prettier` (devDependency) `^10.1.8`
68
+ - Added `globals` (devDependency) `^17.11.0`
69
+ - Added `prettier` (devDependency) `^3.9.6`
70
+ - Added `typescript` (devDependency) `^6.0.3`
71
+ - Added `typescript-eslint` (devDependency) `^8.67.0`
72
+
73
+ ### ❤️Contributors
74
+ - Havea Crenata ([@crenata](https://github.com/crenata))
75
+
76
+ **Full Changelog**: https://github.com/Bejibun-Framework/bejibun-storage/blob/master/CHANGELOG.md
77
+
78
+ ---
79
+
6
80
  ## [v0.1.0](https://github.com/Bejibun-Framework/bejibun-storage/compare/v0.1.0...v0.1.0) - 2026-08-03
7
81
 
8
82
  ### 🩹 Fixes
package/README.md CHANGED
@@ -87,6 +87,10 @@ await Storage.put("path/to/your/file.ext", "content"); // Store content to file
87
87
  await Storage.copy("source/file.ext", "destination/file.ext"); // Copy file
88
88
  await Storage.move("source/file.ext", "destination/file.ext"); // Move file
89
89
  await Storage.delete("path/to/your/file.ext"); // Delete file
90
+ await Storage.metadata("path/to/your/file.ext"); // Retrieve complete file metadata and statistics
91
+ await Storage.size("path/to/your/file.ext"); // Get the file size in bytes
92
+ await Storage.mimeType("path/to/your/file.ext"); // Get the file MIME type
93
+ await Storage.lastModified("path/to/your/file.ext"); // Get the file's last modification date
90
94
  ```
91
95
 
92
96
  #### With Specified Disk
@@ -100,6 +104,10 @@ await Storage.disk("public").put("path/to/your/file.ext", "content");
100
104
  await Storage.disk("public").copy("source/file.ext", "destination/file.ext");
101
105
  await Storage.disk("public").move("source/file.ext", "destination/file.ext");
102
106
  await Storage.disk("public").delete("path/to/your/file.ext");
107
+ await Storage.disk("public").metadata("path/to/your/file.ext");
108
+ await Storage.disk("public").size("path/to/your/file.ext");
109
+ await Storage.disk("public").mimeType("path/to/your/file.ext");
110
+ await Storage.disk("public").lastModified("path/to/your/file.ext");
103
111
  ```
104
112
 
105
113
  #### New Disk at Runtime
@@ -134,6 +142,22 @@ await Storage.build({
134
142
  driver: "local",
135
143
  root: App.Path.storagePath("custom")
136
144
  }).delete("path/to/your/file.ext");
145
+ await Storage.build({
146
+ driver: "local",
147
+ root: App.Path.storagePath("custom")
148
+ }).metadata("path/to/your/file.ext");
149
+ await Storage.build({
150
+ driver: "local",
151
+ root: App.Path.storagePath("custom")
152
+ }).size("path/to/your/file.ext");
153
+ await Storage.build({
154
+ driver: "local",
155
+ root: App.Path.storagePath("custom")
156
+ }).mimeType("path/to/your/file.ext");
157
+ await Storage.build({
158
+ driver: "local",
159
+ root: App.Path.storagePath("custom")
160
+ }).lastModified("path/to/your/file.ext");
137
161
  ```
138
162
 
139
163
  ## ☕ Support / Donate
@@ -0,0 +1,46 @@
1
+ # Benchmarks
2
+
3
+ Speed comparison: baseline (previously published npm release `@bejibun/storage@0.1.1`) vs the optimized `@bejibun/storage` in this repo.
4
+
5
+ ## Running
6
+
7
+ ```bash
8
+ # Run all benchmarks (installs baseline from npm first)
9
+ bun run bench
10
+
11
+ # Or run individually (after install-deps)
12
+ bun run install-deps
13
+ bun run coldstart
14
+ bun run throughput
15
+ ```
16
+
17
+ ## Cold Start
18
+
19
+ Measures package import time by spawning fresh OS processes. Two metrics:
20
+
21
+ - **Full process time** — spawn → exit (includes Bun boot time)
22
+ - **Import** — measured inside the process, isolates the package's own import cost
23
+
24
+ <!-- BENCHMARK:COLDSTART:START -->
25
+
26
+ | | baseline | optimized | speedup |
27
+ | --------------------------- | -------- | --------- | --------- |
28
+ | Full process (spawn → exit) | 23.1ms | 21.5ms | **1.07x** |
29
+ | Import | 15.3ms | 14.1ms | **1.09x** |
30
+
31
+ <!-- BENCHMARK:COLDSTART:END -->
32
+
33
+ ## Throughput
34
+
35
+ The hot paths touched on every storage operation. `construction` covers `new StorageBuilder()` plus config resolution (no disk I/O). `exists`, `get`, and `delete` run the full facade against a temporary local disk. 20,000 calls each, median of 9 runs.
36
+
37
+ <!-- BENCHMARK:THROUGHPUT:START -->
38
+
39
+ | Method | baseline (0.1.1) | optimized | speedup | baseline ops/s | optimized ops/s |
40
+ | -------------- | ---------------- | --------- | ---------- | -------------- | --------------- |
41
+ | `construction` | 35.0ms | 1.5ms | **22.90x** | 571,510/s | 13,086,855/s |
42
+ | `exists` | 88.7ms | 52.9ms | **1.68x** | 225,468/s | 377,841/s |
43
+ | `get` | 54.4ms | 15.3ms | **3.54x** | 367,796/s | 1,303,802/s |
44
+ | `delete` | 3320.7ms | 3226.3ms | **1.03x** | 6,023/s | 6,199/s |
45
+
46
+ <!-- BENCHMARK:THROUGHPUT:END -->
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@bejibun/storage-benchmarks",
3
+ "private": true,
4
+ "type": "module",
5
+ "description": "Speed comparison: baseline (previously published npm release) vs the optimized @bejibun/storage in this repo. Run with `bun run`.",
6
+ "scripts": {
7
+ "install-deps": "bun install --cwd .. --no-save \"@bejibun-baseline/storage@npm:@bejibun/storage@0.1.1\"",
8
+ "coldstart": "bun run scripts/coldstart.mjs && prettier --write . --log-level=silent",
9
+ "throughput": "bun run scripts/throughput.mjs && prettier --write . --log-level=silent",
10
+ "bench": "bun run install-deps && bun run coldstart && bun run throughput"
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ const realLog = console.log;
2
+ console.log = () => {};
3
+
4
+ const t0 = performance.now();
5
+ await import("@bejibun-baseline/storage");
6
+ const t1 = performance.now();
7
+
8
+ console.log = realLog;
9
+ process.stderr.write(String(t1 - t0));
@@ -0,0 +1,9 @@
1
+ const realLog = console.log;
2
+ console.log = () => {};
3
+
4
+ const t0 = performance.now();
5
+ await import("../../index.js");
6
+ const t1 = performance.now();
7
+
8
+ console.log = realLog;
9
+ process.stderr.write(String(t1 - t0));
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Cold-start benchmark.
3
+ *
4
+ * Spawns a brand new OS process per trial for each variant (baseline vs optimized) and
5
+ * measures the full process time (spawn -> exit) and the import time of the package.
6
+ *
7
+ * Run: bun run scripts/coldstart.mjs
8
+ */
9
+ import {spawnSync} from "node:child_process";
10
+ import {fileURLToPath} from "node:url";
11
+ import path from "node:path";
12
+ import {printTable} from "./table-format.mjs";
13
+ import {updateReadmeSection} from "./readme-writer.mjs";
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const TRIALS = 30;
17
+ const runtime = process.execPath;
18
+
19
+ function runTrials(scriptPath) {
20
+ const times = [];
21
+ for (let i = 0; i < TRIALS; i++) {
22
+ const t0 = performance.now();
23
+ const res = spawnSync(runtime, [scriptPath], {encoding: "utf8"});
24
+ const wallTime = performance.now() - t0;
25
+ if (res.status !== 0) {
26
+ console.error("Benchmark failed:", res.stderr);
27
+ process.exit(1);
28
+ }
29
+ const internalTime = parseFloat(res.stderr.trim());
30
+ times.push({wallTime, internalTime});
31
+ }
32
+ return times;
33
+ }
34
+
35
+ function stats(arr) {
36
+ const sorted = [...arr].sort((a, b) => a - b);
37
+ const sum = arr.reduce((a, b) => a + b, 0);
38
+ return {
39
+ min: sorted[0],
40
+ median: sorted[Math.floor(sorted.length / 2)],
41
+ mean: sum / arr.length
42
+ };
43
+ }
44
+
45
+ const baseline = runTrials(path.join(__dirname, "coldstart-baseline.mjs"));
46
+ const optimized = runTrials(path.join(__dirname, "coldstart-optimized.mjs"));
47
+
48
+ const baseWall = stats(baseline.map((t) => t.wallTime));
49
+ const optWall = stats(optimized.map((t) => t.wallTime));
50
+ const baseInt = stats(baseline.map((t) => t.internalTime));
51
+ const optInt = stats(optimized.map((t) => t.internalTime));
52
+
53
+ function fmt(ms) {
54
+ return ms < 1 ? `${(ms * 1000).toFixed(0)}\u00B5s` : `${ms.toFixed(1)}ms`;
55
+ }
56
+
57
+ function sp(b, o) {
58
+ const r = b / o;
59
+ return r >= 1.05 ? `${r.toFixed(2)}x` : r <= 0.95 ? `${r.toFixed(2)}x` : "~1.0x";
60
+ }
61
+
62
+ printTable({
63
+ title: "COLD START BENCHMARK",
64
+ subtitle: `${TRIALS} fresh process spawns per variant`,
65
+ headers: ["Metric", "Baseline (0.1.1)", "Optimized", "Speedup"],
66
+ rows: [
67
+ {
68
+ cells: [
69
+ "Full process (spawn \u2192 exit)",
70
+ fmt(baseWall.median),
71
+ fmt(optWall.median),
72
+ sp(baseWall.median, optWall.median)
73
+ ]
74
+ },
75
+ {
76
+ cells: [
77
+ "Import",
78
+ fmt(baseInt.median),
79
+ fmt(optInt.median),
80
+ sp(baseInt.median, optInt.median)
81
+ ]
82
+ }
83
+ ]
84
+ });
85
+
86
+ const table = [
87
+ "| | baseline | optimized | speedup |",
88
+ "|---|---|---|---|",
89
+ `| Full process (spawn → exit) | ${baseWall.median.toFixed(1)}ms | ${optWall.median.toFixed(1)}ms | **${(baseWall.median / optWall.median).toFixed(2)}x** |`,
90
+ `| Import | ${baseInt.median.toFixed(1)}ms | ${optInt.median.toFixed(1)}ms | **${(baseInt.median / optInt.median).toFixed(2)}x** |`
91
+ ].join("\n");
92
+
93
+ updateReadmeSection("COLDSTART", table);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Writes benchmark output directly into ../README.md between a pair of HTML comment
3
+ * markers, so results never have to be hand-copied. Everything outside the markers
4
+ * (headings, explanatory prose) is left untouched.
5
+ *
6
+ * <!-- BENCHMARK:NAME:START -->
7
+ * ...replaced on every run...
8
+ * <!-- BENCHMARK:NAME:END -->
9
+ */
10
+ import {readFileSync, writeFileSync} from "node:fs";
11
+ import {fileURLToPath} from "node:url";
12
+ import path from "node:path";
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const README_PATH = path.join(__dirname, "..", "README.md");
16
+
17
+ export function updateReadmeSection(marker, markdown) {
18
+ const start = `<!-- BENCHMARK:${marker}:START -->`;
19
+ const end = `<!-- BENCHMARK:${marker}:END -->`;
20
+
21
+ const readme = readFileSync(README_PATH, "utf8");
22
+ const startIdx = readme.indexOf(start);
23
+ const endIdx = readme.indexOf(end);
24
+
25
+ if (startIdx === -1 || endIdx === -1 || endIdx < startIdx) {
26
+ console.warn(
27
+ `README markers for "${marker}" not found in ${README_PATH} -- skipping README update.`
28
+ );
29
+ return;
30
+ }
31
+
32
+ const before = readme.slice(0, startIdx + start.length);
33
+ const after = readme.slice(endIdx);
34
+ writeFileSync(README_PATH, `${before}\n${markdown}\n${after}`);
35
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Simple bordered console table for benchmark output.
3
+ *
4
+ * Uses box-drawing characters for a clean, readable look.
5
+ * Supports optional section headers between rows.
6
+ */
7
+
8
+ const C = {
9
+ reset: "\x1b[0m",
10
+ bold: "\x1b[1m",
11
+ dim: "\x1b[2m",
12
+ green: "\x1b[32m",
13
+ yellow: "\x1b[33m",
14
+ cyan: "\x1b[36m",
15
+ white: "\x1b[37m"
16
+ };
17
+
18
+ const B = {
19
+ TL: "\u250C",
20
+ TR: "\u2510",
21
+ BL: "\u2514",
22
+ BR: "\u2518",
23
+ H: "\u2500",
24
+ V: "\u2502",
25
+ TD: "\u252C",
26
+ TU: "\u2534",
27
+ TR2: "\u251C",
28
+ TL2: "\u2524",
29
+ X: "\u253C"
30
+ };
31
+
32
+ function line(widths, left, mid, right) {
33
+ return left + widths.map((w) => B.H.repeat(w + 2)).join(mid) + right;
34
+ }
35
+
36
+ function p(str, n, align) {
37
+ str = String(str);
38
+ return align === "left" ? str.padEnd(n) : str.padStart(n);
39
+ }
40
+
41
+ function displayValue(val, isSpeedup) {
42
+ if (val === "---") return {text: val, color: C.dim};
43
+ if (val === "new") return {text: val, color: C.cyan};
44
+ if (isSpeedup) {
45
+ const num = parseFloat(val);
46
+ if (num >= 1.2) return {text: val, color: C.bold + C.green};
47
+ if (num >= 1.05) return {text: val, color: C.green};
48
+ if (num < 1.0) return {text: val, color: C.yellow};
49
+ }
50
+ return {text: val, color: null};
51
+ }
52
+
53
+ /**
54
+ * Print a bordered table to the console.
55
+ *
56
+ * @param {object} opts
57
+ * @param {string} opts.title
58
+ * @param {string} opts.subtitle
59
+ * @param {string[]} opts.headers
60
+ * @param {Array<{cells: string[], group?: string}>} opts.rows
61
+ */
62
+ export function printTable({title, subtitle, headers, rows}) {
63
+ const widths = headers.map((h) => h.length);
64
+
65
+ // Calculate column widths
66
+ for (const r of rows) {
67
+ if (!r.cells) continue;
68
+ r.cells.forEach((c, i) => {
69
+ widths[i] = Math.max(widths[i], String(c).length);
70
+ });
71
+ }
72
+
73
+ const totalWidth = widths.reduce((a, w) => a + w + 2, 0) + widths.length - 1 + 2;
74
+ const lineInner = totalWidth - 4;
75
+
76
+ const hr = line(widths, B.TL, B.TD, B.TR);
77
+ const hrSep = line(widths, B.TR2, B.X, B.TL2);
78
+ const hrBot = line(widths, B.BL, B.TU, B.BR);
79
+
80
+ console.log();
81
+ console.log(hr);
82
+ console.log(`${B.V} ${C.bold + C.white}${title.padEnd(lineInner)}${C.reset} ${B.V}`);
83
+ if (subtitle) {
84
+ console.log(`${B.V} ${C.dim}${subtitle.padEnd(lineInner)}${C.reset} ${B.V}`);
85
+ }
86
+ console.log(hrSep);
87
+
88
+ // Header
89
+ const hdr = headers
90
+ .map((h, i) => {
91
+ const pad = i === 0 ? p(h, widths[i], "left") : p(h, widths[i], "right");
92
+ return ` ${C.bold + C.cyan}${pad}${C.reset} `;
93
+ })
94
+ .join(`${B.V}`);
95
+ console.log(`${B.V}${hdr}${B.V}`);
96
+ console.log(hrSep);
97
+
98
+ // Rows
99
+ let lastGroup = null;
100
+ for (const r of rows) {
101
+ if (r.group !== undefined && r.group !== lastGroup) {
102
+ if (lastGroup !== null) console.log(hrSep);
103
+ console.log(`${B.V} ${C.bold + C.white}${r.group.padEnd(lineInner)}${C.reset} ${B.V}`);
104
+ console.log(hrSep);
105
+ lastGroup = r.group;
106
+ }
107
+
108
+ if (!r.cells) continue;
109
+ const cells = r.cells
110
+ .map((c, i) => {
111
+ const isSpeedup = i === 3;
112
+ const d = displayValue(c, isSpeedup);
113
+ const align = i === 0 ? "left" : "right";
114
+ const padStr = p(d.text, widths[i], align);
115
+ return d.color ? ` ${d.color}${padStr}${C.reset} ` : ` ${padStr} `;
116
+ })
117
+ .join(`${B.V}`);
118
+ console.log(`${B.V}${cells}${B.V}`);
119
+ }
120
+
121
+ console.log(hrBot);
122
+ console.log();
123
+ }
@@ -0,0 +1,68 @@
1
+ console.log = () => {};
2
+ console.error = () => {};
3
+
4
+ const {default: Storage} = await import("@bejibun-baseline/storage");
5
+ const {default: StorageBuilder} = await import("@bejibun-baseline/storage/builders/StorageBuilder");
6
+
7
+ const ITERATIONS = 20_000;
8
+ const WARMUP = 500;
9
+ const root = `${process.env.TMPDIR ?? "/tmp"}/bejibun-storage-bench-baseline`;
10
+ const disk = {driver: "local", root};
11
+
12
+ await Bun.write(`${root}/.keep`, "");
13
+
14
+ const buildMs = measureConstructor();
15
+ const existsMs = await measureExists();
16
+ const getMs = await measureGet();
17
+ const deleteMs = await measureDelete();
18
+
19
+ process.stdout.write(`${buildMs}|${existsMs}|${getMs}|${deleteMs}\n`);
20
+
21
+ function measureConstructor() {
22
+ for (let i = 0; i < WARMUP; i++) {
23
+ void new StorageBuilder();
24
+ }
25
+ const t0 = performance.now();
26
+ for (let i = 0; i < ITERATIONS; i++) {
27
+ void new StorageBuilder();
28
+ }
29
+ return performance.now() - t0;
30
+ }
31
+
32
+ async function measureExists() {
33
+ for (let i = 0; i < WARMUP; i++) {
34
+ await Storage.build(disk).exists(`${root}/bench:key-old`);
35
+ }
36
+ const t0 = performance.now();
37
+ for (let i = 0; i < ITERATIONS; i++) {
38
+ await Storage.build(disk).exists(`${root}/bench:key:${i % 1000}`);
39
+ }
40
+ return performance.now() - t0;
41
+ }
42
+
43
+ async function measureGet() {
44
+ await Storage.build(disk).put(`${root}/bench:get`, "bench-value");
45
+ for (let i = 0; i < WARMUP; i++) {
46
+ await Storage.build(disk).get(`${root}/bench:get`);
47
+ }
48
+ const t0 = performance.now();
49
+ for (let i = 0; i < ITERATIONS; i++) {
50
+ await Storage.build(disk).get(`${root}/bench:get`);
51
+ }
52
+ return performance.now() - t0;
53
+ }
54
+
55
+ async function measureDelete() {
56
+ for (let i = 0; i < WARMUP; i++) {
57
+ const f = `${root}/bench:del:${i}`;
58
+ await Bun.write(f, "x");
59
+ await Storage.build(disk).delete(f);
60
+ }
61
+ const t0 = performance.now();
62
+ for (let i = 0; i < ITERATIONS; i++) {
63
+ const f = `${root}/bench:del:${i % 200}`;
64
+ await Bun.write(f, "x");
65
+ await Storage.build(disk).delete(f);
66
+ }
67
+ return performance.now() - t0;
68
+ }
@@ -0,0 +1,68 @@
1
+ console.log = () => {};
2
+ console.error = () => {};
3
+
4
+ const {default: Storage} = await import("../../src/facades/Storage.ts");
5
+ const {default: StorageBuilder} = await import("../../src/builders/StorageBuilder.ts");
6
+
7
+ const ITERATIONS = 20_000;
8
+ const WARMUP = 500;
9
+ const root = `${process.env.TMPDIR ?? "/tmp"}/bejibun-storage-bench-optimized`;
10
+ const disk = {driver: "local", root};
11
+
12
+ await Bun.write(`${root}/.keep`, "");
13
+
14
+ const buildMs = measureConstructor();
15
+ const existsMs = await measureExists();
16
+ const getMs = await measureGet();
17
+ const deleteMs = await measureDelete();
18
+
19
+ process.stdout.write(`${buildMs}|${existsMs}|${getMs}|${deleteMs}\n`);
20
+
21
+ function measureConstructor() {
22
+ for (let i = 0; i < WARMUP; i++) {
23
+ void new StorageBuilder();
24
+ }
25
+ const t0 = performance.now();
26
+ for (let i = 0; i < ITERATIONS; i++) {
27
+ void new StorageBuilder();
28
+ }
29
+ return performance.now() - t0;
30
+ }
31
+
32
+ async function measureExists() {
33
+ for (let i = 0; i < WARMUP; i++) {
34
+ await Storage.build(disk).exists(`${root}/bench:key-old`);
35
+ }
36
+ const t0 = performance.now();
37
+ for (let i = 0; i < ITERATIONS; i++) {
38
+ await Storage.build(disk).exists(`${root}/bench:key:${i % 1000}`);
39
+ }
40
+ return performance.now() - t0;
41
+ }
42
+
43
+ async function measureGet() {
44
+ await Storage.build(disk).put(`${root}/bench:get`, "bench-value");
45
+ for (let i = 0; i < WARMUP; i++) {
46
+ await Storage.build(disk).get(`${root}/bench:get`);
47
+ }
48
+ const t0 = performance.now();
49
+ for (let i = 0; i < ITERATIONS; i++) {
50
+ await Storage.build(disk).get(`${root}/bench:get`);
51
+ }
52
+ return performance.now() - t0;
53
+ }
54
+
55
+ async function measureDelete() {
56
+ for (let i = 0; i < WARMUP; i++) {
57
+ const f = `${root}/bench:del:${i}`;
58
+ await Bun.write(f, "x");
59
+ await Storage.build(disk).delete(f);
60
+ }
61
+ const t0 = performance.now();
62
+ for (let i = 0; i < ITERATIONS; i++) {
63
+ const f = `${root}/bench:del:${i % 200}`;
64
+ await Bun.write(f, "x");
65
+ await Storage.build(disk).delete(f);
66
+ }
67
+ return performance.now() - t0;
68
+ }