@blamejs/core 0.12.17 → 0.12.18
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 +2 -0
- package/lib/backup/index.js +45 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.18 (2026-05-24) — **`bundleAdapterStorage.listBundles({ withStats })` + `bundleInfo.createdAt` — opt-in mtime + size from `statKey`.** Two additions on `b.backup.bundleAdapterStorage`. `listBundles({ withStats: true })` fans out `statKey` per bundle and populates `createdAt` (ISO string from mtimeMs) + `size` (bytes) on every entry. Without the opt the default fast path stays a single listKeys call — operators rendering a bundle picker UI choose between O(1) listings and O(N) stat-enriched listings explicitly. `bundleInfo(bundleId)` gains the same `createdAt` field for parity. fsAdapter + objectStoreAdapter both expose `statKey`; legacy adapters without the capability leave the fields null. **Added:** *`storage.listBundles({ withStats: true })` — opt-in per-bundle stat fan-out* — When the adapter exposes `statKey`, populates `createdAt` (ISO string from mtimeMs) + `size` (bytes) per entry. Stat fan-out is O(N) round-trips so the opt is OFF by default — operators wanting cheap one-shot listings stay on `listBundles()`. Format precedence (tar.gz > tar > directory) carries through to which payload key gets stat'd. · *`storage.bundleInfo(bundleId)` returns `createdAt`* — The bundle introspection primitive now returns `{ bundleId, format, envelopeKind, sizeBytes, createdAt }`. `createdAt` is the ISO string derived from `statKey.mtimeMs` (when the adapter exposes it) — null otherwise. Matches the listBundles+withStats shape so operators can use bundleInfo for single-bundle drill-downs without re-mapping field names.
|
|
12
|
+
|
|
11
13
|
- v0.12.17 (2026-05-23) — **`bundleAdapterStorage.bundleInfo` + `listBundles.format` — per-bundle introspection for envelope kind + format without restore.** Two introspection additions on `b.backup.bundleAdapterStorage`. `listBundles()` now returns the inferred `format` (`"tar"` / `"tar.gz"` / `"directory"`) per bundle from the storage key suffix — no byte read. `storage.bundleInfo(bundleId)` returns `{ bundleId, format, envelopeKind, sizeBytes }` where `envelopeKind` is the result of a 5-byte magic probe (`"recipient"` / `"passphrase"` / `"none"`). Operators administering a multi-strategy backup repository can now filter bundles by encryption posture or by format without a full restore cycle. **Added:** *`listBundles()` carries inferred format per bundle* — Each entry now includes `format` alongside `bundleId` / `createdAt` / `size`. Inference is from the storage key suffix the writeBundle path produced — `<bid>/bundle.tar` → tar, `<bid>/bundle.tar.gz` → tar.gz, anything else → directory. Cheap: no byte read, no per-key stat call. Operators rendering a bundle picker UI now sort + filter by format from a single list call. · *`storage.bundleInfo(bundleId)` — per-bundle introspection* — Returns `{ bundleId, format, envelopeKind, sizeBytes }`. `format` from the storage layout (no byte read). `envelopeKind` from `b.archive.sniffEnvelope` over the bundle payload — `"recipient"` (BAWRP / v0.12.10 hybrid PQC), `"passphrase"` (BAWPP / v0.12.11 Argon2id), `"none"` (plaintext or directory format). `sizeBytes` is the payload byte count for tar / tar.gz; null for directory format (operator's per-file walk applies if exact size matters). Nonexistent bundles refused with `backup/bundle-not-found`.
|
|
12
14
|
|
|
13
15
|
- v0.12.16 (2026-05-23) — **`b.safeArchive.inspect` auto-unwraps wrap envelopes (parallel to the v0.12.15 extract path).** Mirrors the v0.12.15 auto-unwrap support into `b.safeArchive.inspect`. Operators enumerating entries of a sealed archive get a single inspect() call regardless of envelope shape — pass `opts.recipient` or `opts.passphrase` alongside `source` and the orchestrator unwraps inline before walking the inner format. Missing-key opt surfaces a structured `safe-archive/no-recipient-for-wrap` / `safe-archive/no-passphrase-for-wrap` refusal upfront. Carries the v0.12.15 P1 + P2 fixes (close original source before replacing + forward opts.signal to inner buffer adapter) into the inspect path so the same descriptor-leak + abort-propagation contracts hold. **Added:** *`b.safeArchive.inspect` auto-unwraps `BAWRP` + `BAWPP` envelopes* — The orchestrator's `format: "auto"` sniffer recognises the wrap magics and routes through `b.archive.unwrap` / `b.archive.unwrapWithPassphrase` inline. After unwrap, the inner bytes are wrapped in a buffer adapter + re-sniffed; the resulting summary carries the INNER `format` (`"tar"` / `"zip"` / etc.) — operators querying `summary.format` see the carrier format, not `"wrap-recipient"`. Entry enumeration walks the inner archive after a single key-derivation pass; no temporary file lands on disk.
|
package/lib/backup/index.js
CHANGED
|
@@ -1365,8 +1365,10 @@ function bundleAdapterStorage(opts) {
|
|
|
1365
1365
|
nodeFs.writeFileSync(destPath, bytes, { flag: "wx", mode: 0o600 });
|
|
1366
1366
|
}
|
|
1367
1367
|
},
|
|
1368
|
-
async listBundles() {
|
|
1368
|
+
async listBundles(listOpts) {
|
|
1369
1369
|
// Get every key, partition by bundleId prefix, return sorted.
|
|
1370
|
+
listOpts = listOpts || {};
|
|
1371
|
+
var withStats = listOpts.withStats === true;
|
|
1370
1372
|
// v0.12.17 — each bundle now carries the inferred format
|
|
1371
1373
|
// (tar / tar.gz / directory) so operators picking which
|
|
1372
1374
|
// bundle to restore can filter by format without touching
|
|
@@ -1408,12 +1410,28 @@ function bundleAdapterStorage(opts) {
|
|
|
1408
1410
|
if (statsJ.hasTarGz) fmtJ = "tar.gz"; // matches readBundle precedence
|
|
1409
1411
|
else if (statsJ.hasTar) fmtJ = "tar";
|
|
1410
1412
|
else fmtJ = "directory";
|
|
1411
|
-
|
|
1413
|
+
var entry = {
|
|
1412
1414
|
bundleId: bidJ,
|
|
1413
1415
|
format: fmtJ,
|
|
1414
1416
|
createdAt: null, // adapter may not expose mtime
|
|
1415
1417
|
size: null, // best-effort; operators with stat-fast adapters call bundleInfo
|
|
1416
|
-
}
|
|
1418
|
+
};
|
|
1419
|
+
// v0.12.18 — when opts.withStats is true AND the adapter
|
|
1420
|
+
// exposes statKey, fan-out a stat call per bundle's
|
|
1421
|
+
// payload key. O(N) round-trips so this is opt-in;
|
|
1422
|
+
// listBundles() with no opts stays cheap (single listKeys
|
|
1423
|
+
// call). Operators wanting per-bundle stats but not the
|
|
1424
|
+
// full bundleInfo envelope probe pick this middle ground.
|
|
1425
|
+
if (withStats && typeof adapter.statKey === "function") {
|
|
1426
|
+
var statKey;
|
|
1427
|
+
if (statsJ.hasTarGz) statKey = bidJ + TAR_GZ_KEY_SUFFIX;
|
|
1428
|
+
else if (statsJ.hasTar) statKey = bidJ + TAR_KEY_SUFFIX;
|
|
1429
|
+
else statKey = bidJ + "/manifest.json";
|
|
1430
|
+
var sk = await adapter.statKey(statKey);
|
|
1431
|
+
if (sk && typeof sk.size === "number") entry.size = sk.size;
|
|
1432
|
+
if (sk && typeof sk.mtimeMs === "number") entry.createdAt = new Date(sk.mtimeMs).toISOString();
|
|
1433
|
+
}
|
|
1434
|
+
out.push(entry);
|
|
1417
1435
|
}
|
|
1418
1436
|
out.sort(function (a, b) { return a.bundleId < b.bundleId ? 1 : -1; });
|
|
1419
1437
|
return out;
|
|
@@ -1450,6 +1468,28 @@ function bundleAdapterStorage(opts) {
|
|
|
1450
1468
|
}
|
|
1451
1469
|
var envelopeKind = "none";
|
|
1452
1470
|
var sizeBytes = null;
|
|
1471
|
+
var createdAt = null;
|
|
1472
|
+
// Codex P2 on v0.12.18 PR #169 — directory-format bundles
|
|
1473
|
+
// leave payloadKey null but DO have a manifest.json that
|
|
1474
|
+
// statKey can read. For createdAt parity with
|
|
1475
|
+
// listBundles({ withStats }), stat the manifest in the
|
|
1476
|
+
// directory case so the bundleInfo return shape is
|
|
1477
|
+
// populated identically across formats.
|
|
1478
|
+
if (payloadKey === null && fmt === "directory" &&
|
|
1479
|
+
typeof adapter.statKey === "function") {
|
|
1480
|
+
// Stat the manifest.json so directory-format bundles
|
|
1481
|
+
// populate createdAt + sizeBytes identically to how
|
|
1482
|
+
// listBundles({ withStats }) reports them. NOTE: sizeBytes
|
|
1483
|
+
// is the manifest's size here, not the total file-tree
|
|
1484
|
+
// payload (operators wanting the true total walk
|
|
1485
|
+
// per-file keys themselves) — same convention as
|
|
1486
|
+
// listBundles({ withStats }) for parity.
|
|
1487
|
+
var dirSt = await adapter.statKey(manifestKey);
|
|
1488
|
+
if (dirSt && typeof dirSt.size === "number") sizeBytes = dirSt.size;
|
|
1489
|
+
if (dirSt && typeof dirSt.mtimeMs === "number") {
|
|
1490
|
+
createdAt = new Date(dirSt.mtimeMs).toISOString();
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1453
1493
|
if (payloadKey !== null) {
|
|
1454
1494
|
// Codex P1 on v0.12.17 PR #168 — claim was a 5-byte magic
|
|
1455
1495
|
// probe; the implementation was reading the entire bundle
|
|
@@ -1478,6 +1518,7 @@ function bundleAdapterStorage(opts) {
|
|
|
1478
1518
|
if (typeof adapter.statKey === "function") {
|
|
1479
1519
|
var st = await adapter.statKey(payloadKey);
|
|
1480
1520
|
if (st && typeof st.size === "number") sizeBytes = st.size;
|
|
1521
|
+
if (st && typeof st.mtimeMs === "number") createdAt = new Date(st.mtimeMs).toISOString();
|
|
1481
1522
|
}
|
|
1482
1523
|
}
|
|
1483
1524
|
return {
|
|
@@ -1485,6 +1526,7 @@ function bundleAdapterStorage(opts) {
|
|
|
1485
1526
|
format: fmt,
|
|
1486
1527
|
envelopeKind: envelopeKind,
|
|
1487
1528
|
sizeBytes: sizeBytes,
|
|
1529
|
+
createdAt: createdAt,
|
|
1488
1530
|
};
|
|
1489
1531
|
},
|
|
1490
1532
|
async deleteBundle(bundleId) {
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:355b5273-a761-4332-ae06-53ebc6f35a42",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-24T05:15:21.238Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.12.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.12.18",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.12.
|
|
25
|
+
"version": "0.12.18",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.12.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.12.18",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.12.
|
|
57
|
+
"ref": "@blamejs/core@0.12.18",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|