@camstack/system 1.2.77 → 1.2.78

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 (55) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  17. package/dist/builtins/hub-forwarder/index.js +1 -1
  18. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  19. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  23. package/dist/builtins/local-network/local-network.addon.js +1 -1
  24. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  25. package/dist/builtins/loki-logging/index.js +1 -1
  26. package/dist/builtins/loki-logging/index.mjs +1 -1
  27. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  29. package/dist/builtins/platform-probe/index.js +1 -1
  30. package/dist/builtins/platform-probe/index.mjs +1 -1
  31. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  33. package/dist/builtins/snapshot/index.js +1 -1
  34. package/dist/builtins/snapshot/index.mjs +1 -1
  35. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  39. package/dist/builtins/storage-orchestrator/index.d.ts +2 -0
  40. package/dist/builtins/storage-orchestrator/index.js +1 -0
  41. package/dist/builtins/storage-orchestrator/index.mjs +2 -2
  42. package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +73 -0
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +2 -0
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +468 -8
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +468 -9
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +7 -0
  47. package/dist/builtins/system-config/system-config.addon.js +1 -1
  48. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  49. package/dist/builtins/winston-logging/index.js +1 -1
  50. package/dist/builtins/winston-logging/index.mjs +1 -1
  51. package/dist/{dist-eBPa-ZBZ.mjs → dist-CIIFhXqm.mjs} +367 -46
  52. package/dist/{dist-DWZce90k.js → dist-cINpngi1.js} +378 -45
  53. package/dist/index.js +1 -1
  54. package/dist/index.mjs +2 -2
  55. package/package.json +4 -1
@@ -3,7 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DWZce90k.js");
6
+ const require_dist = require("../../dist-cINpngi1.js");
7
+ let node_crypto = require("node:crypto");
7
8
  let node_fs_promises = require("node:fs/promises");
8
9
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
9
10
  let node_path = require("node:path");
@@ -241,6 +242,52 @@ var StorageOrchestratorService = class {
241
242
  return null;
242
243
  }
243
244
  /**
245
+ * Repoint several logical defaults as one in-memory commit. The migration
246
+ * coordinator persists its `repointing` phase before calling this method, so
247
+ * a process crash during the backing-store writes is recovered by replaying
248
+ * the same idempotent target set before anyone resumes writers.
249
+ */
250
+ async setDefaultLocations(targetByType) {
251
+ if (targetByType.size === 0) return;
252
+ const now = Date.now();
253
+ const nextById = new Map(this.locations);
254
+ const touched = /* @__PURE__ */ new Map();
255
+ for (const [type, targetId] of targetByType) {
256
+ const target = nextById.get(targetId);
257
+ if (!target) throw new Error(`Storage location "${targetId}" not found`);
258
+ if (target.type !== type) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${type}"`);
259
+ for (const [id, location] of nextById) {
260
+ if (location.type !== type) continue;
261
+ const isDefault = id === targetId;
262
+ if (location.isDefault === isDefault) continue;
263
+ const updated = {
264
+ ...location,
265
+ isDefault,
266
+ updatedAt: now
267
+ };
268
+ nextById.set(id, updated);
269
+ touched.set(id, updated);
270
+ }
271
+ }
272
+ if (this.locationStore) {
273
+ const previous = /* @__PURE__ */ new Map();
274
+ for (const id of touched.keys()) {
275
+ const location = this.locations.get(id);
276
+ if (location) previous.set(id, location);
277
+ }
278
+ try {
279
+ for (const location of touched.values()) await this.locationStore.upsert(location);
280
+ } catch (err) {
281
+ for (const location of previous.values()) try {
282
+ await this.locationStore.upsert(location);
283
+ } catch {}
284
+ throw err;
285
+ }
286
+ }
287
+ this.locations.clear();
288
+ for (const [id, location] of nextById) this.locations.set(id, location);
289
+ }
290
+ /**
244
291
  * Insert or update a location. If `isDefault: true`, atomically
245
292
  * demotes any other default for the same type — operators always see
246
293
  * exactly one default per type.
@@ -935,6 +982,355 @@ var StoragePressureManager = class {
935
982
  }
936
983
  };
937
984
  //#endregion
985
+ //#region src/builtins/storage-orchestrator/storage-migration-coordinator.ts
986
+ var STORAGE_CLASSES = [
987
+ "recordings",
988
+ "recordingsLow",
989
+ "eventMedia"
990
+ ];
991
+ var PARTICIPANTS = [
992
+ "pipeline",
993
+ "recorder",
994
+ "analytics"
995
+ ];
996
+ var REVERSE_PARTICIPANTS = [
997
+ "analytics",
998
+ "recorder",
999
+ "pipeline"
1000
+ ];
1001
+ /**
1002
+ * Durable orchestration layer over the independent recorder and event-media
1003
+ * movers. It owns the only public migration state machine; participants expose
1004
+ * intentionally narrow lease/move primitives and never alter camera settings.
1005
+ */
1006
+ var StorageMigrationCoordinator = class {
1007
+ deps;
1008
+ active = null;
1009
+ runPromise = null;
1010
+ startReserved = false;
1011
+ constructor(deps) {
1012
+ this.deps = deps;
1013
+ }
1014
+ async plan(input) {
1015
+ const moves = [];
1016
+ for (const storageClass of STORAGE_CLASSES) {
1017
+ const targetId = input.destinations[storageClass];
1018
+ if (targetId === void 0) continue;
1019
+ const source = this.deps.locations.getDefaultLocation(storageClass);
1020
+ if (!source) throw new Error(`No default storage location for "${storageClass}"`);
1021
+ const target = this.deps.locations.getLocationById(targetId);
1022
+ if (!target) throw new Error(`Storage location "${targetId}" not found`);
1023
+ if (target.type !== storageClass) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${storageClass}"`);
1024
+ if (source.id === target.id) throw new Error(`Storage location "${targetId}" is already the "${storageClass}" default`);
1025
+ moves.push({
1026
+ storageClass,
1027
+ fromLocationId: source.id,
1028
+ toLocationId: target.id,
1029
+ moverJobId: null,
1030
+ state: null,
1031
+ error: null
1032
+ });
1033
+ }
1034
+ if (moves.length === 0) throw new Error("select at least one storage class");
1035
+ return {
1036
+ destinations: input.destinations,
1037
+ moves: moves.map(({ storageClass, fromLocationId, toLocationId }) => ({
1038
+ storageClass,
1039
+ fromLocationId,
1040
+ toLocationId
1041
+ }))
1042
+ };
1043
+ }
1044
+ async start(input) {
1045
+ if (this.startReserved) throw new Error("storage migration is already active");
1046
+ this.startReserved = true;
1047
+ try {
1048
+ const existing = await this.status();
1049
+ if (existing && isTerminal(existing) && existing.pausedParticipants.length > 0) {
1050
+ await this.releaseAfterTerminal(existing);
1051
+ await this.persist(existing);
1052
+ if (existing.pausedParticipants.length > 0) throw new Error(`storage migration ${existing.jobId} still holds maintenance leases`);
1053
+ }
1054
+ if (existing && !isTerminal(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
1055
+ const plan = await this.plan(input);
1056
+ const now = this.deps.now();
1057
+ const job = {
1058
+ jobId: this.deps.newId(),
1059
+ phase: "planning",
1060
+ destinations: input.destinations,
1061
+ throttleMbps: input.throttleMbps ?? 40,
1062
+ moves: plan.moves.map((move) => ({
1063
+ ...move,
1064
+ moverJobId: null,
1065
+ state: null,
1066
+ error: null
1067
+ })),
1068
+ pauseLeaseId: null,
1069
+ pausedParticipants: [],
1070
+ repointed: false,
1071
+ cancelRequested: false,
1072
+ startedAt: now,
1073
+ updatedAt: now,
1074
+ finishedAt: null,
1075
+ error: null
1076
+ };
1077
+ this.active = job;
1078
+ await this.persist(job);
1079
+ this.runPromise = this.run(job);
1080
+ this.runPromise;
1081
+ return job.jobId;
1082
+ } finally {
1083
+ this.startReserved = false;
1084
+ }
1085
+ }
1086
+ async status(jobId) {
1087
+ const job = this.active ?? await this.deps.state.get();
1088
+ if (jobId !== void 0 && job?.jobId !== jobId) return null;
1089
+ return job;
1090
+ }
1091
+ async cancel(jobId) {
1092
+ const job = await this.status(jobId);
1093
+ if (!job || isTerminal(job) || job.repointed) return false;
1094
+ job.cancelRequested = true;
1095
+ await this.persist(job);
1096
+ await Promise.all(job.moves.filter((move) => move.moverJobId !== null).map((move) => this.cancelMove(move)));
1097
+ return true;
1098
+ }
1099
+ /** Boot recovery resumes a durable unfinished state. A missing in-memory
1100
+ * child mover is recreated from the same copy-if-absent input. */
1101
+ async recover() {
1102
+ const job = await this.deps.state.get();
1103
+ if (!job) return;
1104
+ if (job.cancelRequested) {
1105
+ await Promise.all(job.moves.filter((move) => move.moverJobId !== null).map((move) => this.cancelMove(move)));
1106
+ await this.waitForMoves(job);
1107
+ await this.releaseAfterTerminal(job);
1108
+ job.phase = "cancelled";
1109
+ job.finishedAt ??= this.deps.now();
1110
+ await this.persist(job);
1111
+ return;
1112
+ }
1113
+ if (isTerminal(job)) {
1114
+ if (job.pausedParticipants.length > 0) {
1115
+ await this.releaseAfterTerminal(job);
1116
+ await this.persist(job);
1117
+ }
1118
+ return;
1119
+ }
1120
+ this.active = job;
1121
+ job.pauseLeaseId ??= this.deps.newId();
1122
+ if (job.repointed && job.phase === "resuming") job.phase = "refreshing";
1123
+ await this.persist(job);
1124
+ try {
1125
+ await this.pauseParticipants(job, true);
1126
+ } catch (err) {
1127
+ job.error = err instanceof Error ? err.message : String(err);
1128
+ await this.persist(job);
1129
+ return;
1130
+ }
1131
+ if (this.runPromise === null) {
1132
+ this.runPromise = this.run(job);
1133
+ this.runPromise;
1134
+ }
1135
+ }
1136
+ async run(job) {
1137
+ try {
1138
+ if (job.phase === "planning") await this.setPhase(job, "pausing");
1139
+ if (job.phase === "pausing") {
1140
+ job.pauseLeaseId ??= job.jobId;
1141
+ await this.pauseParticipants(job);
1142
+ if (job.cancelRequested) return this.finishCancelled(job);
1143
+ await this.setPhase(job, "moving");
1144
+ }
1145
+ if (job.phase === "moving") {
1146
+ await this.startOrResumeMoves(job);
1147
+ await this.waitForMoves(job);
1148
+ if (job.cancelRequested) return this.finishCancelled(job);
1149
+ await this.setPhase(job, "verifying");
1150
+ }
1151
+ if (job.phase === "verifying") {
1152
+ await this.verifyMoves(job);
1153
+ if (job.cancelRequested) return this.finishCancelled(job);
1154
+ await this.setPhase(job, "repointing");
1155
+ }
1156
+ if (job.phase === "repointing") {
1157
+ const targets = new Map(job.moves.map((move) => [move.storageClass, move.toLocationId]));
1158
+ await this.deps.locations.setDefaultLocations(targets);
1159
+ job.repointed = true;
1160
+ await this.setPhase(job, "refreshing");
1161
+ }
1162
+ if (job.phase === "refreshing") {
1163
+ const leaseId = requireLease(job);
1164
+ if (job.moves.some((move) => move.storageClass !== "eventMedia")) await this.deps.participants.recorder.refresh(leaseId);
1165
+ if (job.moves.some((move) => move.storageClass === "eventMedia")) await this.deps.participants.analytics.refresh(leaseId);
1166
+ await this.setPhase(job, "resuming");
1167
+ }
1168
+ if (job.phase === "resuming") {
1169
+ await this.resumeParticipants(job);
1170
+ await this.setPhase(job, "done");
1171
+ }
1172
+ } catch (err) {
1173
+ job.error = err instanceof Error ? err.message : String(err);
1174
+ if (job.repointed) {
1175
+ job.phase = "refreshing";
1176
+ job.finishedAt = null;
1177
+ await this.persist(job);
1178
+ return;
1179
+ }
1180
+ job.phase = "failed";
1181
+ job.finishedAt = this.deps.now();
1182
+ await this.releaseAfterTerminal(job);
1183
+ await this.persist(job);
1184
+ } finally {
1185
+ if (isTerminal(job)) {
1186
+ await this.releaseAfterTerminal(job);
1187
+ this.active = job;
1188
+ }
1189
+ this.runPromise = null;
1190
+ }
1191
+ }
1192
+ async pauseParticipants(job, reacquire = false) {
1193
+ const leaseId = requireLease(job);
1194
+ for (const name of PARTICIPANTS) {
1195
+ if (job.cancelRequested) return;
1196
+ if (!reacquire && job.pausedParticipants.includes(name)) continue;
1197
+ await this.participant(name).pause(leaseId);
1198
+ if (!job.pausedParticipants.includes(name)) job.pausedParticipants.push(name);
1199
+ await this.persist(job);
1200
+ }
1201
+ }
1202
+ async resumeParticipants(job) {
1203
+ const leaseId = requireLease(job);
1204
+ for (const name of REVERSE_PARTICIPANTS) {
1205
+ if (!job.pausedParticipants.includes(name)) continue;
1206
+ await this.participant(name).resume(leaseId);
1207
+ job.pausedParticipants = job.pausedParticipants.filter((participant) => participant !== name);
1208
+ await this.persist(job);
1209
+ }
1210
+ }
1211
+ async releaseAfterTerminal(job) {
1212
+ if (job.pauseLeaseId === null) return;
1213
+ for (const name of REVERSE_PARTICIPANTS) {
1214
+ if (!job.pausedParticipants.includes(name)) continue;
1215
+ try {
1216
+ await this.participant(name).resume(job.pauseLeaseId);
1217
+ job.pausedParticipants = job.pausedParticipants.filter((participant) => participant !== name);
1218
+ await this.persist(job);
1219
+ } catch {}
1220
+ }
1221
+ }
1222
+ async startOrResumeMoves(job) {
1223
+ if (job.cancelRequested) return;
1224
+ const mediaMove = job.moves.find((move) => move.storageClass === "eventMedia");
1225
+ if (mediaMove?.moverJobId === null) {
1226
+ mediaMove.moverJobId = await this.startMove(job, mediaMove);
1227
+ await this.persist(job);
1228
+ }
1229
+ const footageMoves = job.moves.filter((move) => move.storageClass !== "eventMedia");
1230
+ for (let index = 0; index < footageMoves.length; index++) {
1231
+ const move = footageMoves[index];
1232
+ if (move.moverJobId !== null) continue;
1233
+ if (!footageMoves.slice(0, index).every((predecessor) => predecessor.state === "done")) return;
1234
+ move.moverJobId = await this.startMove(job, move);
1235
+ await this.persist(job);
1236
+ return;
1237
+ }
1238
+ }
1239
+ async waitForMoves(job) {
1240
+ const sleep = this.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1241
+ const pollMs = this.deps.pollMs ?? 250;
1242
+ for (;;) {
1243
+ if (!job.cancelRequested) await this.startOrResumeMoves(job);
1244
+ let complete = true;
1245
+ for (const [index, move] of job.moves.entries()) {
1246
+ if (job.cancelRequested && move.moverJobId === null) continue;
1247
+ if (move.storageClass !== "eventMedia" && job.moves.slice(0, index).filter((predecessor) => predecessor.storageClass !== "eventMedia").some((predecessor) => predecessor.state !== "done")) {
1248
+ complete = false;
1249
+ continue;
1250
+ }
1251
+ if (move.moverJobId === null) {
1252
+ if (!job.cancelRequested) complete = false;
1253
+ continue;
1254
+ }
1255
+ const status = await this.moveStatus(move);
1256
+ if (status === null) {
1257
+ if (job.cancelRequested) {
1258
+ move.state = "cancelled";
1259
+ continue;
1260
+ }
1261
+ move.moverJobId = await this.startMove(job, move);
1262
+ move.state = "running";
1263
+ await this.persist(job);
1264
+ complete = false;
1265
+ continue;
1266
+ }
1267
+ move.state = status.state;
1268
+ move.error = status.error;
1269
+ if (status.state === "failed") throw new Error(move.error ?? `${move.storageClass} move failed`);
1270
+ if (status.state === "cancelled") {
1271
+ job.cancelRequested = true;
1272
+ continue;
1273
+ }
1274
+ if (status.state !== "done") complete = false;
1275
+ }
1276
+ await this.persist(job);
1277
+ if (complete) return;
1278
+ await sleep(pollMs);
1279
+ }
1280
+ }
1281
+ async verifyMoves(job) {
1282
+ for (const move of job.moves) if (move.state !== "done") throw new Error(`${move.storageClass} move did not complete verification`);
1283
+ }
1284
+ async startMove(job, move) {
1285
+ if (move.storageClass === "eventMedia") return (await this.deps.participants.analytics.startMove({
1286
+ toLocationId: move.toLocationId,
1287
+ throttleMbps: job.throttleMbps,
1288
+ leaseId: requireLease(job)
1289
+ })).jobId;
1290
+ return (await this.deps.participants.recorder.startMove({
1291
+ fromLocationId: move.fromLocationId,
1292
+ toLocationId: move.toLocationId,
1293
+ footageClass: move.storageClass,
1294
+ throttleMbps: job.throttleMbps,
1295
+ leaseId: requireLease(job)
1296
+ })).jobId;
1297
+ }
1298
+ async moveStatus(move) {
1299
+ if (move.moverJobId === null) return null;
1300
+ return move.storageClass === "eventMedia" ? this.deps.participants.analytics.getMove(move.moverJobId) : this.deps.participants.recorder.getMove(move.moverJobId);
1301
+ }
1302
+ async cancelMove(move) {
1303
+ if (move.moverJobId === null) return;
1304
+ if (move.storageClass === "eventMedia") await this.deps.participants.analytics.cancelMove(move.moverJobId);
1305
+ else await this.deps.participants.recorder.cancelMove(move.moverJobId);
1306
+ }
1307
+ participant(name) {
1308
+ return this.deps.participants[name];
1309
+ }
1310
+ async setPhase(job, phase) {
1311
+ job.phase = phase;
1312
+ if (isTerminal(job)) job.finishedAt = this.deps.now();
1313
+ await this.persist(job);
1314
+ }
1315
+ async finishCancelled(job) {
1316
+ job.phase = "cancelled";
1317
+ job.finishedAt = this.deps.now();
1318
+ await this.releaseAfterTerminal(job);
1319
+ await this.persist(job);
1320
+ }
1321
+ async persist(job) {
1322
+ job.updatedAt = this.deps.now();
1323
+ await this.deps.state.set(job);
1324
+ }
1325
+ };
1326
+ function requireLease(job) {
1327
+ if (job.pauseLeaseId === null) throw new Error("storage migration has no maintenance lease");
1328
+ return job.pauseLeaseId;
1329
+ }
1330
+ function isTerminal(job) {
1331
+ return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
1332
+ }
1333
+ //#endregion
938
1334
  //#region src/builtins/storage-orchestrator/storage-orchestrator.addon.ts
939
1335
  /**
940
1336
  * `storage-orchestrator` builtin — singleton owner of the consumer-
@@ -980,6 +1376,7 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
980
1376
  pressureManager = null;
981
1377
  pressureTimer = null;
982
1378
  pressureSweepInFlight = false;
1379
+ migration = null;
983
1380
  /**
984
1381
  * Cached `providerId → nodeLocal` snapshot (SP1). Backs the orchestrator's
985
1382
  * synchronous `NodeLocalResolver` — `getProviderInfo()` is async, so we
@@ -1173,6 +1570,57 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
1173
1570
  }
1174
1571
  }
1175
1572
  };
1573
+ const migration = new StorageMigrationCoordinator({
1574
+ locations: service,
1575
+ state: this.state("storage-migration", require_dist.StorageMigrationJobSchema.nullable(), null),
1576
+ participants: {
1577
+ pipeline: {
1578
+ pause: async (leaseId) => {
1579
+ await this.ctx.api.pipelineOrchestrator.pauseForStorageMigration.mutate({ leaseId });
1580
+ },
1581
+ resume: async (leaseId) => {
1582
+ await this.ctx.api.pipelineOrchestrator.resumeForStorageMigration.mutate({ leaseId });
1583
+ }
1584
+ },
1585
+ recorder: {
1586
+ pause: async (leaseId) => {
1587
+ await this.ctx.api.recording.pauseForStorageMigration.mutate({ leaseId });
1588
+ },
1589
+ resume: async (leaseId) => {
1590
+ await this.ctx.api.recording.resumeForStorageMigration.mutate({ leaseId });
1591
+ },
1592
+ startMove: (input) => this.ctx.api.recording.startStorageMigrationMove.mutate(input),
1593
+ getMove: (jobId) => this.ctx.api.recording.getStorageMigrationMoveStatus.query({ jobId }),
1594
+ cancelMove: async (jobId) => (await this.ctx.api.recording.cancelStorageMigrationMove.mutate({ jobId })).cancelled,
1595
+ refresh: async (leaseId) => {
1596
+ await this.ctx.api.recording.refreshStorageLocationsForMigration.mutate({ leaseId });
1597
+ }
1598
+ },
1599
+ analytics: {
1600
+ pause: async (leaseId) => {
1601
+ await this.ctx.api.pipelineAnalytics.pauseForStorageMigration.mutate({ leaseId });
1602
+ },
1603
+ resume: async (leaseId) => {
1604
+ await this.ctx.api.pipelineAnalytics.resumeForStorageMigration.mutate({ leaseId });
1605
+ },
1606
+ startMove: (input) => this.ctx.api.pipelineAnalytics.startStorageMigrationMove.mutate(input),
1607
+ getMove: (jobId) => this.ctx.api.pipelineAnalytics.getStorageMigrationMoveStatus.query({ jobId }),
1608
+ cancelMove: async (jobId) => (await this.ctx.api.pipelineAnalytics.cancelStorageMigrationMove.mutate({ jobId })).cancelled,
1609
+ refresh: async (leaseId) => {
1610
+ await this.ctx.api.pipelineAnalytics.refreshStorageLocationsForMigration.mutate({ leaseId });
1611
+ }
1612
+ }
1613
+ },
1614
+ now: () => Date.now(),
1615
+ newId: () => (0, node_crypto.randomUUID)()
1616
+ });
1617
+ this.migration = migration;
1618
+ const migrationProvider = {
1619
+ plan: (input) => migration.plan(input),
1620
+ start: async (input) => ({ jobId: await migration.start(input) }),
1621
+ status: ({ jobId }) => migration.status(jobId),
1622
+ cancel: async ({ jobId }) => ({ cancelled: await migration.cancel(jobId) })
1623
+ };
1176
1624
  await this.seedFromDeclarations();
1177
1625
  const eventBus = this.ctx.eventBus;
1178
1626
  if (eventBus) {
@@ -1197,13 +1645,20 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
1197
1645
  this.runPressureSweep();
1198
1646
  }, PRESSURE_SWEEP_INTERVAL_MS);
1199
1647
  this.ctx.logger.info("Storage orchestrator initialized");
1200
- return [{
1201
- capability: require_dist.storageCapability,
1202
- provider
1203
- }, {
1204
- capability: require_dist.settingsStoreCapability,
1205
- provider: createDataStoreDispatch(getEngines)
1206
- }];
1648
+ return [
1649
+ {
1650
+ capability: require_dist.storageCapability,
1651
+ provider
1652
+ },
1653
+ {
1654
+ capability: require_dist.storageMigrationCapability,
1655
+ provider: migrationProvider
1656
+ },
1657
+ {
1658
+ capability: require_dist.settingsStoreCapability,
1659
+ provider: createDataStoreDispatch(getEngines)
1660
+ }
1661
+ ];
1207
1662
  }
1208
1663
  async onShutdown() {
1209
1664
  for (const dispose of this.seedSubscriptionDisposers) try {
@@ -1217,6 +1672,10 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
1217
1672
  this.pressureManager = null;
1218
1673
  this.service?.clearSessions();
1219
1674
  this.service = null;
1675
+ this.migration = null;
1676
+ }
1677
+ async onHubReachable() {
1678
+ await this.migration?.recover();
1220
1679
  }
1221
1680
  /**
1222
1681
  * Resolve the live engine from the capability registry and wrap it in a
@@ -1439,6 +1898,7 @@ var StorageOrchestratorAddon = class extends require_dist.BaseAddon {
1439
1898
  };
1440
1899
  //#endregion
1441
1900
  exports.SqliteLocationStore = SqliteLocationStore;
1901
+ exports.StorageMigrationCoordinator = StorageMigrationCoordinator;
1442
1902
  exports.StorageOrchestratorAddon = StorageOrchestratorAddon;
1443
1903
  exports.default = StorageOrchestratorAddon;
1444
1904
  exports.StorageOrchestratorService = StorageOrchestratorService;