@camstack/system 1.2.461 → 1.2.463
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/dist/addon-runner.js +21 -276
- package/dist/addon-runner.mjs +21 -276
- package/dist/{agent-https-Iw3RSQup.mjs → agent-https-B9gbXoJI.mjs} +1 -1
- package/dist/{agent-https-Dnr_nIVx.js → agent-https-DhFIbEwN.js} +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +1 -3
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +51 -502
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +51 -502
- package/dist/builtins/backup-orchestrator/destination-policy.d.ts +5 -21
- package/dist/builtins/backup-orchestrator/record-kinds.d.ts +0 -2
- package/dist/builtins/backup-orchestrator/schedule-store.d.ts +5 -13
- package/dist/builtins/backup-orchestrator/seed-marker-store.d.ts +9 -19
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/cluster-agent/record-kinds.d.ts +0 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-history/device-history.addon.js +1 -1
- package/dist/builtins/device-history/device-history.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +3 -3
- package/dist/builtins/keychain/index.mjs +3 -3
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/auth-schema.d.ts +2 -12
- package/dist/builtins/local-auth/local-auth.addon.js +52 -191
- package/dist/builtins/local-auth/local-auth.addon.mjs +52 -191
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +13 -4
- package/dist/builtins/local-auth/record-kinds.d.ts +1 -1
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/record-entries/index.js +1 -1
- package/dist/builtins/record-entries/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/file-host-protocol.d.ts +0 -9
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/owned-sqlite-file.d.ts +0 -8
- package/dist/builtins/sqlite-storage/records/records-sql.d.ts +0 -2
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +0 -28
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +1 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +6 -170
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +6 -170
- package/dist/builtins/sqlite-storage/storage-admin-provider.d.ts +3 -17
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +3 -3
- package/dist/builtins/system-config/system-config.addon.mjs +3 -3
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-DNRZWR1j.mjs → composition-sources-CGtooHSL.mjs} +1 -1
- package/dist/{composition-sources-b0e_2I-D.js → composition-sources-oXvmbMHX.js} +1 -1
- package/dist/{constants-6OeJiZgi.js → constants-5mrGZxwS.js} +1 -1
- package/dist/{constants-BnkOdj9Q.mjs → constants-CdutnpL9.mjs} +1 -1
- package/dist/{device-ca-CPTDs--d.js → device-ca-B3xUzps7.js} +1 -1
- package/dist/{device-ca-Bnkw8GyU.mjs → device-ca-DZHKlZlD.mjs} +1 -1
- package/dist/{dist-Wy8cTxg6.mjs → dist-C2OBCa-A.mjs} +40 -80
- package/dist/{dist-Bn-RNanh.js → dist-D9aw_d0G.js} +39 -91
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-CJu0nhla.mjs → hub-mdns-ciao-Bm09NrMS.mjs} +2 -2
- package/dist/{hub-mdns-ciao-CTBTlia4.js → hub-mdns-ciao-CFlhVKK6.js} +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +88 -240
- package/dist/index.mjs +89 -236
- package/dist/kernel/index.d.ts +0 -1
- package/dist/kernel/moleculer/runner-addon-settings.d.ts +1 -6
- package/dist/kernel/moleculer/runner-device-settings.d.ts +2 -2
- package/dist/{restore-archive-DM1OBYq4.js → restore-archive-6zkRREHO.js} +1 -1
- package/dist/{restore-archive-CM23Pq9B.mjs → restore-archive-CSOR_tyN.mjs} +1 -1
- package/dist/{retired-settings-keys-D_VwZiDt.js → retired-settings-keys-CGUqX-Oz.js} +0 -6
- package/dist/{retired-settings-keys-Bhwe4ymb.mjs → retired-settings-keys-C_Kjp-Bj.mjs} +1 -1
- package/dist/{wal-maintenance-Bj_MmDkr.mjs → wal-maintenance-Dg6IzSgz.mjs} +0 -0
- package/dist/{wal-maintenance-CaJjuLHq.js → wal-maintenance-jXcOxm2T.js} +0 -0
- package/package.json +1 -1
- package/dist/builtins/sqlite-storage/retired-collections.d.ts +0 -146
- package/dist/kernel/legacy-blob-guard.d.ts +0 -63
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ca as EventCategory, Er as storageOccupancyCapability, Tt as backupCapability, Vn as mayWriteToLocation, ei as BaseAddon, ia as nodePin, va as toNodeId } from "../../dist-C2OBCa-A.mjs";
|
|
2
2
|
import { t as require_read_all_rows } from "../../read-all-rows-TaPqdWNF.mjs";
|
|
3
|
-
import { _ as backupRecordStoresOf, a as BackupSnapshotRefusedError, d as ENCRYPTED_BACKUP_SUFFIX, f as generateBackupKeyPair, g as backupOrchestratorRecordKinds, i as BackupCancelledError, l as BackupKeyFormatError, m as parseBackupSecretKey, n as precheckRestoreKey, o as DEFAULT_BACKUP_DATABASES, p as parseBackupPublicKey, r as prepareRestoreArchive, s as SystemBackupService } from "../../restore-archive-
|
|
3
|
+
import { _ as backupRecordStoresOf, a as BackupSnapshotRefusedError, d as ENCRYPTED_BACKUP_SUFFIX, f as generateBackupKeyPair, g as backupOrchestratorRecordKinds, i as BackupCancelledError, l as BackupKeyFormatError, m as parseBackupSecretKey, n as precheckRestoreKey, o as DEFAULT_BACKUP_DATABASES, p as parseBackupPublicKey, r as prepareRestoreArchive, s as SystemBackupService } from "../../restore-archive-CSOR_tyN.mjs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import * as fsp from "node:fs/promises";
|
|
6
6
|
import * as path$1 from "node:path";
|
|
@@ -79,75 +79,26 @@ async function seedWeeklySchedule(store, markers) {
|
|
|
79
79
|
//#endregion
|
|
80
80
|
//#region src/builtins/backup-orchestrator/seed-marker-store.ts
|
|
81
81
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* seed is created (or found already created); never updated"
|
|
85
|
-
* retention="none — one tiny row per seed ever made; deleting it lets the
|
|
86
|
-
* seed be re-created once more on the next boot"
|
|
82
|
+
* One `seed-markers` row per seed id (D902: no `backup_seed_markers` table).
|
|
83
|
+
* Written once, never updated; deleting one lets that seed run once more.
|
|
87
84
|
*/
|
|
88
|
-
var BACKUP_SEED_MARKERS_COLLECTION = "backup_seed_markers";
|
|
89
|
-
var COLUMNS$1 = [{
|
|
90
|
-
name: "seed_id",
|
|
91
|
-
type: "TEXT",
|
|
92
|
-
primaryKey: true
|
|
93
|
-
}, {
|
|
94
|
-
name: "seeded_at",
|
|
95
|
-
type: "INTEGER",
|
|
96
|
-
notNull: true
|
|
97
|
-
}];
|
|
98
85
|
var BackupSeedMarkerStore = class {
|
|
99
|
-
store;
|
|
100
|
-
now;
|
|
101
|
-
declared = false;
|
|
102
86
|
markersKind;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.now = now;
|
|
87
|
+
now;
|
|
88
|
+
constructor(markersKind, now = Date.now) {
|
|
106
89
|
this.markersKind = markersKind;
|
|
107
|
-
|
|
108
|
-
async ensureDeclared() {
|
|
109
|
-
if (this.markersKind !== null) return;
|
|
110
|
-
if (this.declared) return;
|
|
111
|
-
await this.store.declareCollection({
|
|
112
|
-
collection: BACKUP_SEED_MARKERS_COLLECTION,
|
|
113
|
-
columns: COLUMNS$1
|
|
114
|
-
});
|
|
115
|
-
this.declared = true;
|
|
90
|
+
this.now = now;
|
|
116
91
|
}
|
|
117
92
|
async has(seedId) {
|
|
118
|
-
await this.
|
|
119
|
-
const kind = this.markersKind;
|
|
120
|
-
if (kind !== null) return await kind.get(seedId) !== null;
|
|
121
|
-
return (await this.store.query({
|
|
122
|
-
collection: BACKUP_SEED_MARKERS_COLLECTION,
|
|
123
|
-
filter: {
|
|
124
|
-
where: { seed_id: seedId },
|
|
125
|
-
limit: 1
|
|
126
|
-
}
|
|
127
|
-
})).length > 0;
|
|
93
|
+
return await this.markersKind.get(seedId) !== null;
|
|
128
94
|
}
|
|
129
95
|
async mark(seedId) {
|
|
130
|
-
await this.ensureDeclared();
|
|
131
96
|
if (await this.has(seedId)) return;
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
id: seedId,
|
|
138
|
-
data
|
|
139
|
-
});
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
await this.store.insert({
|
|
143
|
-
collection: BACKUP_SEED_MARKERS_COLLECTION,
|
|
144
|
-
record: {
|
|
145
|
-
id: seedId,
|
|
146
|
-
data: {
|
|
147
|
-
seed_id: seedId,
|
|
148
|
-
seeded_at: this.now()
|
|
149
|
-
}
|
|
150
|
-
}
|
|
97
|
+
const body = { seededAt: this.now() };
|
|
98
|
+
const data = backupOrchestratorRecordKinds["seed-markers"].schema.parse(body);
|
|
99
|
+
await this.markersKind.put({
|
|
100
|
+
id: seedId,
|
|
101
|
+
data
|
|
151
102
|
});
|
|
152
103
|
}
|
|
153
104
|
};
|
|
@@ -947,165 +898,35 @@ function previewNextRuns(cron, count, after) {
|
|
|
947
898
|
//#endregion
|
|
948
899
|
//#region src/builtins/backup-orchestrator/destination-policy.ts
|
|
949
900
|
var import_read_all_rows = require_read_all_rows();
|
|
950
|
-
/**
|
|
951
|
-
* @durable class=config owner=backup-orchestrator
|
|
952
|
-
* write="`ensureDefault` auto-creates one row the first time a `backups` storage
|
|
953
|
-
* location is listed (enabled, retentionCount 7, no cadence of its own), an operator
|
|
954
|
-
* edit rewrites it (`upsert` = delete + insert, carrying `last_run_at` forward so a
|
|
955
|
-
* label change does not reset the baseline), and `recordAttempt` stamps
|
|
956
|
-
* `last_run_at` + `last_error` on every attempted write to the destination"
|
|
957
|
-
* retention="none — `delete(locationId)` exists but NOTHING calls it, so a policy row
|
|
958
|
-
* outlives the `StorageLocation` it names and the table only ever grows, by one row
|
|
959
|
-
* per backups location ever seen. Losing a row is recoverable: the next
|
|
960
|
-
* `listDestinations` pass re-creates it from the defaults, which silently discards
|
|
961
|
-
* the operator's own retention count. The legacy `cron` column is written NULL and
|
|
962
|
-
* never read — it scheduled nothing after 2026-07-28 and reading it made a
|
|
963
|
-
* destination advertise a nightly run nobody performed (D384)."
|
|
964
|
-
*/
|
|
965
|
-
var BACKUP_DESTINATION_POLICIES = "backup_destination_policies";
|
|
966
|
-
var BACKUP_DESTINATION_POLICIES_COLUMNS = [
|
|
967
|
-
{
|
|
968
|
-
name: "location_id",
|
|
969
|
-
type: "TEXT",
|
|
970
|
-
primaryKey: true
|
|
971
|
-
},
|
|
972
|
-
{
|
|
973
|
-
name: "enabled",
|
|
974
|
-
type: "INTEGER",
|
|
975
|
-
notNull: true,
|
|
976
|
-
defaultValue: 1
|
|
977
|
-
},
|
|
978
|
-
{
|
|
979
|
-
name: "retention_count",
|
|
980
|
-
type: "INTEGER",
|
|
981
|
-
notNull: true,
|
|
982
|
-
defaultValue: 7
|
|
983
|
-
},
|
|
984
|
-
{
|
|
985
|
-
name: "label",
|
|
986
|
-
type: "TEXT"
|
|
987
|
-
},
|
|
988
|
-
{
|
|
989
|
-
name: "cron",
|
|
990
|
-
type: "TEXT"
|
|
991
|
-
},
|
|
992
|
-
{
|
|
993
|
-
name: "last_run_at",
|
|
994
|
-
type: "INTEGER"
|
|
995
|
-
},
|
|
996
|
-
{
|
|
997
|
-
name: "last_error",
|
|
998
|
-
type: "TEXT"
|
|
999
|
-
}
|
|
1000
|
-
];
|
|
1001
901
|
var BackupDestinationPolicyService = class {
|
|
1002
|
-
store;
|
|
1003
|
-
declared = false;
|
|
1004
902
|
policiesKind;
|
|
1005
|
-
constructor(
|
|
1006
|
-
this.store = store;
|
|
903
|
+
constructor(policiesKind) {
|
|
1007
904
|
this.policiesKind = policiesKind;
|
|
1008
905
|
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Lazy `declareCollection` — called from every read/write path.
|
|
1011
|
-
* `declareCollection` is idempotent, so repeated calls collapse to a no-op
|
|
1012
|
-
* after the first. This keeps the constructor synchronous and avoids a
|
|
1013
|
-
* separate `initialize()` step that callers could forget.
|
|
1014
|
-
*/
|
|
1015
|
-
async initialize() {
|
|
1016
|
-
await this.ensureDeclared();
|
|
1017
|
-
}
|
|
1018
|
-
async ensureDeclared() {
|
|
1019
|
-
if (this.policiesKind !== null) return;
|
|
1020
|
-
if (this.declared) return;
|
|
1021
|
-
await this.store.declareCollection({
|
|
1022
|
-
collection: BACKUP_DESTINATION_POLICIES,
|
|
1023
|
-
columns: BACKUP_DESTINATION_POLICIES_COLUMNS
|
|
1024
|
-
});
|
|
1025
|
-
this.declared = true;
|
|
1026
|
-
}
|
|
1027
906
|
async list() {
|
|
1028
|
-
await this.
|
|
1029
|
-
|
|
1030
|
-
if (kind !== null) {
|
|
1031
|
-
const rows = await (0, import_read_all_rows.readAllRows)(kind);
|
|
1032
|
-
rows.sort((a, b) => a.id.localeCompare(b.id));
|
|
1033
|
-
const out = [];
|
|
1034
|
-
for (const row of rows) {
|
|
1035
|
-
const mapped = bodyToPolicy(row.id, row.data);
|
|
1036
|
-
if (mapped) out.push(mapped);
|
|
1037
|
-
}
|
|
1038
|
-
return out;
|
|
1039
|
-
}
|
|
1040
|
-
const rows = await this.store.query({
|
|
1041
|
-
collection: BACKUP_DESTINATION_POLICIES,
|
|
1042
|
-
filter: { orderBy: {
|
|
1043
|
-
field: "location_id",
|
|
1044
|
-
direction: "asc"
|
|
1045
|
-
} }
|
|
1046
|
-
});
|
|
907
|
+
const rows = await (0, import_read_all_rows.readAllRows)(this.policiesKind);
|
|
908
|
+
rows.sort((a, b) => a.id.localeCompare(b.id));
|
|
1047
909
|
const out = [];
|
|
1048
910
|
for (const row of rows) {
|
|
1049
|
-
const mapped =
|
|
911
|
+
const mapped = bodyToPolicy(row.id, row.data);
|
|
1050
912
|
if (mapped) out.push(mapped);
|
|
1051
913
|
}
|
|
1052
914
|
return out;
|
|
1053
915
|
}
|
|
1054
916
|
async get(locationId) {
|
|
1055
|
-
await this.
|
|
1056
|
-
|
|
1057
|
-
if (kind !== null) {
|
|
1058
|
-
const row = await kind.get(locationId);
|
|
1059
|
-
return row === null ? null : bodyToPolicy(locationId, row.data);
|
|
1060
|
-
}
|
|
1061
|
-
const rows = await this.store.query({
|
|
1062
|
-
collection: BACKUP_DESTINATION_POLICIES,
|
|
1063
|
-
filter: {
|
|
1064
|
-
where: { location_id: locationId },
|
|
1065
|
-
limit: 1
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
|
-
return rows[0] ? mapRowToPolicy(rows[0].data) : null;
|
|
917
|
+
const row = await this.policiesKind.get(locationId);
|
|
918
|
+
return row === null ? null : bodyToPolicy(locationId, row.data);
|
|
1069
919
|
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Replace-or-insert. Implemented as delete + insert, mirroring the
|
|
1072
|
-
* `SqliteLocationStore` contract.
|
|
1073
|
-
*/
|
|
920
|
+
/** Replace-or-insert: one full `put` of the row. */
|
|
1074
921
|
async upsert(p) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
const data = backupOrchestratorRecordKinds["destination-policies"].schema.parse(body);
|
|
1080
|
-
await kind.put({
|
|
1081
|
-
id: p.locationId,
|
|
1082
|
-
data
|
|
1083
|
-
});
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
await this.store.deleteWhere({
|
|
1087
|
-
collection: BACKUP_DESTINATION_POLICIES,
|
|
1088
|
-
filter: { where: { location_id: p.locationId } }
|
|
1089
|
-
});
|
|
1090
|
-
await this.store.insert({
|
|
1091
|
-
collection: BACKUP_DESTINATION_POLICIES,
|
|
1092
|
-
record: {
|
|
1093
|
-
id: p.locationId,
|
|
1094
|
-
data: mapPolicyToRow(p)
|
|
1095
|
-
}
|
|
922
|
+
const data = backupOrchestratorRecordKinds["destination-policies"].schema.parse(policyToBody(p));
|
|
923
|
+
await this.policiesKind.put({
|
|
924
|
+
id: p.locationId,
|
|
925
|
+
data
|
|
1096
926
|
});
|
|
1097
927
|
}
|
|
1098
928
|
async delete(locationId) {
|
|
1099
|
-
await this.
|
|
1100
|
-
const kind = this.policiesKind;
|
|
1101
|
-
if (kind !== null) {
|
|
1102
|
-
await kind.delete(locationId);
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
await this.store.deleteWhere({
|
|
1106
|
-
collection: BACKUP_DESTINATION_POLICIES,
|
|
1107
|
-
filter: { where: { location_id: locationId } }
|
|
1108
|
-
});
|
|
929
|
+
await this.policiesKind.delete(locationId);
|
|
1109
930
|
}
|
|
1110
931
|
/**
|
|
1111
932
|
* Auto-create a default policy for a `backups` location seen for the
|
|
@@ -1173,41 +994,6 @@ function bodyToPolicy(locationId, body) {
|
|
|
1173
994
|
...body.lastError !== void 0 ? { lastError: body.lastError } : {}
|
|
1174
995
|
};
|
|
1175
996
|
}
|
|
1176
|
-
function mapPolicyToRow(p) {
|
|
1177
|
-
return {
|
|
1178
|
-
location_id: p.locationId,
|
|
1179
|
-
enabled: p.enabled ? 1 : 0,
|
|
1180
|
-
retention_count: p.retentionCount,
|
|
1181
|
-
label: p.label ?? null,
|
|
1182
|
-
cron: null,
|
|
1183
|
-
last_run_at: p.lastAttemptAt ?? null,
|
|
1184
|
-
last_error: p.lastError ?? null
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
/**
|
|
1188
|
-
* Decode a row pulled from the structured table. Defensive against
|
|
1189
|
-
* schema drift / partial migrations: returns `null` on missing
|
|
1190
|
-
* `location_id` so corrupt rows skip the result list rather than
|
|
1191
|
-
* throwing the whole orchestrator down at boot.
|
|
1192
|
-
*/
|
|
1193
|
-
function mapRowToPolicy(row) {
|
|
1194
|
-
const locationId = String(row["location_id"] ?? "");
|
|
1195
|
-
if (!locationId) return null;
|
|
1196
|
-
const labelRaw = row["label"];
|
|
1197
|
-
const label = typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : void 0;
|
|
1198
|
-
const lastRunRaw = row["last_run_at"];
|
|
1199
|
-
const lastAttemptAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
|
|
1200
|
-
const lastErrorRaw = row["last_error"];
|
|
1201
|
-
const lastError = typeof lastErrorRaw === "string" && lastErrorRaw.length > 0 ? lastErrorRaw : void 0;
|
|
1202
|
-
return {
|
|
1203
|
-
locationId,
|
|
1204
|
-
enabled: row["enabled"] === 1 || row["enabled"] === true,
|
|
1205
|
-
retentionCount: Number(row["retention_count"] ?? 0),
|
|
1206
|
-
...label !== void 0 ? { label } : {},
|
|
1207
|
-
...lastAttemptAt !== void 0 ? { lastAttemptAt } : {},
|
|
1208
|
-
...lastError !== void 0 ? { lastError } : {}
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
997
|
//#endregion
|
|
1212
998
|
//#region src/builtins/backup-orchestrator/download-helpers.ts
|
|
1213
999
|
/**
|
|
@@ -1900,176 +1686,35 @@ function requestSignature(request) {
|
|
|
1900
1686
|
}
|
|
1901
1687
|
//#endregion
|
|
1902
1688
|
//#region src/builtins/backup-orchestrator/schedule-store.ts
|
|
1903
|
-
/**
|
|
1904
|
-
* @durable class=config owner=backup-orchestrator
|
|
1905
|
-
* write="an operator creating or editing a schedule (`upsert`, implemented as
|
|
1906
|
-
* delete + insert: cron, enabled, the fan-out `location_ids`, `retention_count`,
|
|
1907
|
-
* optional `data_sources`), plus a `last_run_at` stamp the orchestrator writes on
|
|
1908
|
-
* every due tick BEFORE the archive runs, so an upload outliving the tick cannot
|
|
1909
|
-
* fire the same schedule twice, and a `last_success_at` / `last_error` pair it
|
|
1910
|
-
* writes AFTER the run resolves"
|
|
1911
|
-
* retention="only the operator's `delete(id)` — no sweep, no expiry, and `retention_count`
|
|
1912
|
-
* bounds the ARCHIVES at the destination, never these rows. Losing the table stops
|
|
1913
|
-
* every scheduled backup silently: no cron remains to fire, and the fan-out sets and
|
|
1914
|
-
* retention counts are gone with it."
|
|
1915
|
-
*/
|
|
1916
|
-
var BACKUP_SCHEDULES = "backup_schedules";
|
|
1917
|
-
var BACKUP_SCHEDULES_COLUMNS = [
|
|
1918
|
-
{
|
|
1919
|
-
name: "id",
|
|
1920
|
-
type: "TEXT",
|
|
1921
|
-
primaryKey: true
|
|
1922
|
-
},
|
|
1923
|
-
{
|
|
1924
|
-
name: "label",
|
|
1925
|
-
type: "TEXT",
|
|
1926
|
-
notNull: true,
|
|
1927
|
-
defaultValue: ""
|
|
1928
|
-
},
|
|
1929
|
-
{
|
|
1930
|
-
name: "cron",
|
|
1931
|
-
type: "TEXT",
|
|
1932
|
-
notNull: true,
|
|
1933
|
-
defaultValue: ""
|
|
1934
|
-
},
|
|
1935
|
-
{
|
|
1936
|
-
name: "enabled",
|
|
1937
|
-
type: "INTEGER",
|
|
1938
|
-
notNull: true,
|
|
1939
|
-
defaultValue: 1
|
|
1940
|
-
},
|
|
1941
|
-
{
|
|
1942
|
-
name: "location_ids",
|
|
1943
|
-
type: "JSON",
|
|
1944
|
-
notNull: true,
|
|
1945
|
-
defaultValue: "[]"
|
|
1946
|
-
},
|
|
1947
|
-
{
|
|
1948
|
-
name: "retention_count",
|
|
1949
|
-
type: "INTEGER",
|
|
1950
|
-
notNull: true,
|
|
1951
|
-
defaultValue: 7
|
|
1952
|
-
},
|
|
1953
|
-
{
|
|
1954
|
-
name: "data_sources",
|
|
1955
|
-
type: "JSON"
|
|
1956
|
-
},
|
|
1957
|
-
{
|
|
1958
|
-
name: "databases",
|
|
1959
|
-
type: "JSON"
|
|
1960
|
-
},
|
|
1961
|
-
{
|
|
1962
|
-
name: "last_run_at",
|
|
1963
|
-
type: "INTEGER"
|
|
1964
|
-
},
|
|
1965
|
-
{
|
|
1966
|
-
name: "last_success_at",
|
|
1967
|
-
type: "INTEGER"
|
|
1968
|
-
},
|
|
1969
|
-
{
|
|
1970
|
-
name: "last_error",
|
|
1971
|
-
type: "TEXT"
|
|
1972
|
-
}
|
|
1973
|
-
];
|
|
1974
1689
|
var BackupScheduleService = class {
|
|
1975
|
-
store;
|
|
1976
|
-
declared = false;
|
|
1977
1690
|
schedulesKind;
|
|
1978
|
-
constructor(
|
|
1979
|
-
this.store = store;
|
|
1691
|
+
constructor(schedulesKind) {
|
|
1980
1692
|
this.schedulesKind = schedulesKind;
|
|
1981
1693
|
}
|
|
1982
|
-
async initialize() {
|
|
1983
|
-
await this.ensureDeclared();
|
|
1984
|
-
}
|
|
1985
|
-
async ensureDeclared() {
|
|
1986
|
-
if (this.schedulesKind !== null) return;
|
|
1987
|
-
if (this.declared) return;
|
|
1988
|
-
await this.store.declareCollection({
|
|
1989
|
-
collection: BACKUP_SCHEDULES,
|
|
1990
|
-
columns: BACKUP_SCHEDULES_COLUMNS
|
|
1991
|
-
});
|
|
1992
|
-
this.declared = true;
|
|
1993
|
-
}
|
|
1994
1694
|
async list() {
|
|
1995
|
-
await this.
|
|
1996
|
-
|
|
1997
|
-
if (kind !== null) {
|
|
1998
|
-
const rows = await (0, import_read_all_rows.readAllRows)(kind);
|
|
1999
|
-
rows.sort((a, b) => a.id.localeCompare(b.id));
|
|
2000
|
-
const out = [];
|
|
2001
|
-
for (const row of rows) {
|
|
2002
|
-
const mapped = bodyToSchedule(row.id, row.data);
|
|
2003
|
-
if (mapped) out.push(mapped);
|
|
2004
|
-
}
|
|
2005
|
-
return out;
|
|
2006
|
-
}
|
|
2007
|
-
const rows = await this.store.query({
|
|
2008
|
-
collection: BACKUP_SCHEDULES,
|
|
2009
|
-
filter: { orderBy: {
|
|
2010
|
-
field: "id",
|
|
2011
|
-
direction: "asc"
|
|
2012
|
-
} }
|
|
2013
|
-
});
|
|
1695
|
+
const rows = await (0, import_read_all_rows.readAllRows)(this.schedulesKind);
|
|
1696
|
+
rows.sort((a, b) => a.id.localeCompare(b.id));
|
|
2014
1697
|
const out = [];
|
|
2015
1698
|
for (const row of rows) {
|
|
2016
|
-
const mapped =
|
|
1699
|
+
const mapped = bodyToSchedule(row.id, row.data);
|
|
2017
1700
|
if (mapped) out.push(mapped);
|
|
2018
1701
|
}
|
|
2019
1702
|
return out;
|
|
2020
1703
|
}
|
|
2021
1704
|
async get(id) {
|
|
2022
|
-
await this.
|
|
2023
|
-
|
|
2024
|
-
if (kind !== null) {
|
|
2025
|
-
const row = await kind.get(id);
|
|
2026
|
-
return row === null ? null : bodyToSchedule(id, row.data);
|
|
2027
|
-
}
|
|
2028
|
-
const rows = await this.store.query({
|
|
2029
|
-
collection: BACKUP_SCHEDULES,
|
|
2030
|
-
filter: {
|
|
2031
|
-
where: { id },
|
|
2032
|
-
limit: 1
|
|
2033
|
-
}
|
|
2034
|
-
});
|
|
2035
|
-
return rows[0] ? mapRowToSchedule(rows[0].data) : null;
|
|
1705
|
+
const row = await this.schedulesKind.get(id);
|
|
1706
|
+
return row === null ? null : bodyToSchedule(id, row.data);
|
|
2036
1707
|
}
|
|
2037
|
-
/** Replace-or-insert
|
|
1708
|
+
/** Replace-or-insert: one full `put` of the row. */
|
|
2038
1709
|
async upsert(s) {
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
const data = backupOrchestratorRecordKinds.schedules.schema.parse(body);
|
|
2044
|
-
await kind.put({
|
|
2045
|
-
id: s.id,
|
|
2046
|
-
data
|
|
2047
|
-
});
|
|
2048
|
-
return;
|
|
2049
|
-
}
|
|
2050
|
-
await this.store.deleteWhere({
|
|
2051
|
-
collection: BACKUP_SCHEDULES,
|
|
2052
|
-
filter: { where: { id: s.id } }
|
|
2053
|
-
});
|
|
2054
|
-
await this.store.insert({
|
|
2055
|
-
collection: BACKUP_SCHEDULES,
|
|
2056
|
-
record: {
|
|
2057
|
-
id: s.id,
|
|
2058
|
-
data: mapScheduleToRow(s)
|
|
2059
|
-
}
|
|
1710
|
+
const data = backupOrchestratorRecordKinds.schedules.schema.parse(scheduleToBody(s));
|
|
1711
|
+
await this.schedulesKind.put({
|
|
1712
|
+
id: s.id,
|
|
1713
|
+
data
|
|
2060
1714
|
});
|
|
2061
1715
|
}
|
|
2062
1716
|
async delete(id) {
|
|
2063
|
-
await this.
|
|
2064
|
-
const kind = this.schedulesKind;
|
|
2065
|
-
if (kind !== null) {
|
|
2066
|
-
await kind.delete(id);
|
|
2067
|
-
return;
|
|
2068
|
-
}
|
|
2069
|
-
await this.store.deleteWhere({
|
|
2070
|
-
collection: BACKUP_SCHEDULES,
|
|
2071
|
-
filter: { where: { id } }
|
|
2072
|
-
});
|
|
1717
|
+
await this.schedulesKind.delete(id);
|
|
2073
1718
|
}
|
|
2074
1719
|
};
|
|
2075
1720
|
function scheduleToBody(s) {
|
|
@@ -2102,73 +1747,6 @@ function bodyToSchedule(id, body) {
|
|
|
2102
1747
|
...body.lastError !== void 0 ? { lastError: body.lastError } : {}
|
|
2103
1748
|
};
|
|
2104
1749
|
}
|
|
2105
|
-
function mapScheduleToRow(s) {
|
|
2106
|
-
return {
|
|
2107
|
-
id: s.id,
|
|
2108
|
-
label: s.label,
|
|
2109
|
-
cron: s.cron,
|
|
2110
|
-
enabled: s.enabled ? 1 : 0,
|
|
2111
|
-
location_ids: [...s.locationIds],
|
|
2112
|
-
retention_count: s.retentionCount,
|
|
2113
|
-
data_sources: s.dataSources ? [...s.dataSources] : null,
|
|
2114
|
-
databases: s.databases ? [...s.databases] : null,
|
|
2115
|
-
last_run_at: s.lastAttemptAt ?? null,
|
|
2116
|
-
last_success_at: s.lastSuccessAt ?? null,
|
|
2117
|
-
last_error: s.lastError ?? null
|
|
2118
|
-
};
|
|
2119
|
-
}
|
|
2120
|
-
/**
|
|
2121
|
-
* `location_ids` / `data_sources` are `JSON` columns, so the surface hands
|
|
2122
|
-
* back real arrays. A STRING still arrives for rows an older build wrote and
|
|
2123
|
-
* nothing has rewritten since — a schedule whose fan-out set silently read as
|
|
2124
|
-
* empty would back up to nowhere, so both shapes are accepted.
|
|
2125
|
-
*/
|
|
2126
|
-
function parseStringArray(raw) {
|
|
2127
|
-
const value = typeof raw === "string" ? tryParseJson(raw) : raw;
|
|
2128
|
-
if (!Array.isArray(value)) return [];
|
|
2129
|
-
return value.filter((v) => typeof v === "string");
|
|
2130
|
-
}
|
|
2131
|
-
/** Known database ids only: an unknown name in a stored row is dropped, not carried. */
|
|
2132
|
-
function parseDatabaseIds(raw) {
|
|
2133
|
-
const out = [];
|
|
2134
|
-
for (const id of parseStringArray(raw)) if (id === "settings" || id === "events" || id === "history") out.push(id);
|
|
2135
|
-
return out;
|
|
2136
|
-
}
|
|
2137
|
-
function tryParseJson(raw) {
|
|
2138
|
-
if (raw.length === 0) return null;
|
|
2139
|
-
try {
|
|
2140
|
-
return JSON.parse(raw);
|
|
2141
|
-
} catch {
|
|
2142
|
-
return null;
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
function mapRowToSchedule(row) {
|
|
2146
|
-
const id = String(row["id"] ?? "");
|
|
2147
|
-
if (!id) return null;
|
|
2148
|
-
const dataSourcesRaw = row["data_sources"];
|
|
2149
|
-
const dataSources = dataSourcesRaw === null || dataSourcesRaw === void 0 ? void 0 : parseStringArray(dataSourcesRaw);
|
|
2150
|
-
const databasesRaw = row["databases"];
|
|
2151
|
-
const databases = databasesRaw === null || databasesRaw === void 0 ? void 0 : parseDatabaseIds(databasesRaw);
|
|
2152
|
-
const lastRunRaw = row["last_run_at"];
|
|
2153
|
-
const lastAttemptAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
|
|
2154
|
-
const lastSuccessRaw = row["last_success_at"];
|
|
2155
|
-
const lastSuccessAt = typeof lastSuccessRaw === "number" && lastSuccessRaw > 0 ? lastSuccessRaw : void 0;
|
|
2156
|
-
const lastErrorRaw = row["last_error"];
|
|
2157
|
-
const lastError = typeof lastErrorRaw === "string" && lastErrorRaw.length > 0 ? lastErrorRaw : void 0;
|
|
2158
|
-
return {
|
|
2159
|
-
id,
|
|
2160
|
-
label: typeof row["label"] === "string" ? String(row["label"]) : "",
|
|
2161
|
-
cron: typeof row["cron"] === "string" ? String(row["cron"]) : "",
|
|
2162
|
-
enabled: row["enabled"] === 1 || row["enabled"] === true,
|
|
2163
|
-
locationIds: parseStringArray(row["location_ids"]),
|
|
2164
|
-
retentionCount: Number(row["retention_count"] ?? 7),
|
|
2165
|
-
...dataSources !== void 0 ? { dataSources } : {},
|
|
2166
|
-
...databases !== void 0 && databases.length > 0 ? { databases } : {},
|
|
2167
|
-
...lastAttemptAt !== void 0 ? { lastAttemptAt } : {},
|
|
2168
|
-
...lastSuccessAt !== void 0 ? { lastSuccessAt } : {},
|
|
2169
|
-
...lastError !== void 0 ? { lastError } : {}
|
|
2170
|
-
};
|
|
2171
|
-
}
|
|
2172
1750
|
//#endregion
|
|
2173
1751
|
//#region src/builtins/backup-orchestrator/staging-paths.ts
|
|
2174
1752
|
/** Basename of the staging dir under the node data root. */
|
|
@@ -2300,8 +1878,8 @@ function selectBackupTargets(input) {
|
|
|
2300
1878
|
* Responsibilities:
|
|
2301
1879
|
* - Schedule (cron-like timer at top of hour, daily/weekly cadence)
|
|
2302
1880
|
* - Per-destination policy (enable/disable, retention count, label)
|
|
2303
|
-
* persisted in `
|
|
2304
|
-
* the
|
|
1881
|
+
* persisted in the `destination-policies` records kind (this builtin
|
|
1882
|
+
* owns the kind).
|
|
2305
1883
|
* - Archive build (one tar.gz with embedded manifest, fanned out to
|
|
2306
1884
|
* every selected `backups`-typed `StorageLocation` via the
|
|
2307
1885
|
* consumer-facing `storage` cap's chunked upload protocol).
|
|
@@ -2312,7 +1890,7 @@ function selectBackupTargets(input) {
|
|
|
2312
1890
|
* Storage routing: destinations are
|
|
2313
1891
|
* `api.storage.listLocations({ type: 'backups' })` — every operator-
|
|
2314
1892
|
* visible `backups` location IS a destination. The orchestrator joins
|
|
2315
|
-
* that list with the `
|
|
1893
|
+
* that list with the `destination-policies` records to surface
|
|
2316
1894
|
* `enabled` / `retentionCount` / `label`. The actual archive bytes
|
|
2317
1895
|
* travel through `api.storage.beginUpload` / `writeChunk` /
|
|
2318
1896
|
* `finalizeUpload`, with the staging archive built into
|
|
@@ -2408,19 +1986,11 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
2408
1986
|
this.systemBackup = new SystemBackupService(this.dataDir, this.ctx.logger);
|
|
2409
1987
|
this.runQueue = new BackupRunQueue((request, signal, progress) => this.runBackupBuild(request, signal, progress));
|
|
2410
1988
|
await this.cleanStagingArea();
|
|
2411
|
-
|
|
2412
|
-
if (this.policies
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
this.
|
|
2416
|
-
this.policies = null;
|
|
2417
|
-
}
|
|
2418
|
-
this.schedules = this.resolveScheduleService();
|
|
2419
|
-
if (this.schedules) try {
|
|
2420
|
-
await this.schedules.initialize();
|
|
2421
|
-
} catch (err) {
|
|
2422
|
-
this.ctx.logger.error("backup-orchestrator: schedule table init failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
2423
|
-
this.schedules = null;
|
|
1989
|
+
const backupRecords = this.backupRecords;
|
|
1990
|
+
if (backupRecords === null) this.ctx.logger.warn("backup-orchestrator: no ctx.records — schedules and destination policies will not persist");
|
|
1991
|
+
else {
|
|
1992
|
+
this.policies = new BackupDestinationPolicyService(backupRecords.destinationPolicies);
|
|
1993
|
+
this.schedules = new BackupScheduleService(backupRecords.schedules);
|
|
2424
1994
|
}
|
|
2425
1995
|
await this.seedWeekly();
|
|
2426
1996
|
const settingsStore = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
|
|
@@ -2534,7 +2104,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
2534
2104
|
* lazily seeding a default for any `locationId` we haven't seen
|
|
2535
2105
|
* before. Idempotent — `ensureDefault` no-ops on already-persisted
|
|
2536
2106
|
* rows. Falls back to the in-memory default policy when the policy
|
|
2537
|
-
* service isn't wired (no
|
|
2107
|
+
* service isn't wired (no `ctx.records`).
|
|
2538
2108
|
*/
|
|
2539
2109
|
async loadPoliciesIndexed(locationIds) {
|
|
2540
2110
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3040,7 +2610,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
3040
2610
|
*/
|
|
3041
2611
|
async upsertDestinationPolicy(input) {
|
|
3042
2612
|
if (!this.policies) {
|
|
3043
|
-
this.ctx.logger.warn("backup upsertDestinationPolicy: no
|
|
2613
|
+
this.ctx.logger.warn("backup upsertDestinationPolicy: no ctx.records — change will not persist", { meta: { locationId: input.locationId } });
|
|
3044
2614
|
return;
|
|
3045
2615
|
}
|
|
3046
2616
|
const existing = await this.policies.get(input.locationId).catch(() => null);
|
|
@@ -3423,11 +2993,10 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
3423
2993
|
* anything the operator has: see `planWeeklySeed`.
|
|
3424
2994
|
*/
|
|
3425
2995
|
async seedWeekly() {
|
|
3426
|
-
|
|
2996
|
+
const markers = this.backupRecords?.seedMarkers;
|
|
2997
|
+
if (!this.schedules || markers === void 0) return;
|
|
3427
2998
|
try {
|
|
3428
|
-
const
|
|
3429
|
-
if (!store) return;
|
|
3430
|
-
const seed = await seedWeeklySchedule(this.schedules, new BackupSeedMarkerStore(store, Date.now, this.backupRecords?.seedMarkers ?? null));
|
|
2999
|
+
const seed = await seedWeeklySchedule(this.schedules, new BackupSeedMarkerStore(markers));
|
|
3431
3000
|
if (seed === null) return;
|
|
3432
3001
|
this.ctx.logger.info("backup: seeded the weekly events & history schedule", { meta: {
|
|
3433
3002
|
scheduleId: seed.id,
|
|
@@ -3533,7 +3102,7 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
3533
3102
|
});
|
|
3534
3103
|
}
|
|
3535
3104
|
async upsertSchedule(input) {
|
|
3536
|
-
if (!this.schedules) throw new Error("backup upsertSchedule: no
|
|
3105
|
+
if (!this.schedules) throw new Error("backup upsertSchedule: no ctx.records — schedule cannot be persisted");
|
|
3537
3106
|
const cron = input.cron.trim();
|
|
3538
3107
|
if (cron.length > 0 && !isValidCron(cron)) throw new Error(`Invalid cron expression: "${cron}"`);
|
|
3539
3108
|
const id = input.id && input.id.length > 0 ? input.id : randomUUID();
|
|
@@ -3576,26 +3145,6 @@ var BackupOrchestratorAddon = class extends BaseAddon {
|
|
|
3576
3145
|
if (!this.schedules) return;
|
|
3577
3146
|
await this.schedules.delete(input.id);
|
|
3578
3147
|
}
|
|
3579
|
-
resolveScheduleService() {
|
|
3580
|
-
const reg = this.ctx.kernel.capabilityRegistry;
|
|
3581
|
-
if (!reg) return null;
|
|
3582
|
-
const store = reg.getSingleton("settings-store");
|
|
3583
|
-
if (!store) {
|
|
3584
|
-
this.ctx.logger.warn("backup-orchestrator: no settings-store provider — backup schedules will not persist");
|
|
3585
|
-
return null;
|
|
3586
|
-
}
|
|
3587
|
-
return new BackupScheduleService(store, this.backupRecords?.schedules ?? null);
|
|
3588
|
-
}
|
|
3589
|
-
resolvePolicyService() {
|
|
3590
|
-
const reg = this.ctx.kernel.capabilityRegistry;
|
|
3591
|
-
if (!reg) return null;
|
|
3592
|
-
const store = reg.getSingleton("settings-store");
|
|
3593
|
-
if (!store) {
|
|
3594
|
-
this.ctx.logger.warn("backup-orchestrator: no settings-store provider — destination policies will not persist");
|
|
3595
|
-
return null;
|
|
3596
|
-
}
|
|
3597
|
-
return new BackupDestinationPolicyService(store, this.backupRecords?.destinationPolicies ?? null);
|
|
3598
|
-
}
|
|
3599
3148
|
/**
|
|
3600
3149
|
* Narrow `ctx.api.storage` to the subset the manifest helpers need.
|
|
3601
3150
|
* Lets the helpers be unit-tested with a Map-backed fake without
|