@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
package/.prettierignore
ADDED
|
@@ -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
|
package/.prettierrc.json
ADDED
|
@@ -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,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
|
+
}
|