@camstack/system 1.2.423 → 1.2.425
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/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-databases.d.ts +51 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +13 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +248 -17
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +248 -17
- package/dist/builtins/backup-orchestrator/manifest-store.d.ts +23 -3
- package/dist/builtins/backup-orchestrator/run-queue.d.ts +3 -0
- package/dist/builtins/backup-orchestrator/schedule-store.d.ts +7 -1
- package/dist/builtins/backup-orchestrator/seed-marker-store.d.ts +34 -0
- 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/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-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/local-auth.addon.js +2 -2
- package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- 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/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/database-snapshot-provider.d.ts +18 -0
- package/dist/builtins/sqlite-storage/file-host-client.d.ts +59 -0
- package/dist/builtins/sqlite-storage/file-host-handlers.d.ts +23 -0
- package/dist/builtins/sqlite-storage/file-host-protocol.d.ts +151 -0
- package/dist/builtins/sqlite-storage/file-host-worker.d.ts +1 -0
- 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 +21 -20
- package/dist/builtins/sqlite-storage/index.mjs +2 -1
- package/dist/builtins/sqlite-storage/owned-sqlite-file.d.ts +136 -0
- package/dist/builtins/sqlite-storage/placement-errors.d.ts +60 -0
- package/dist/builtins/sqlite-storage/placement-router.d.ts +80 -0
- package/dist/builtins/sqlite-storage/retired-collections.d.ts +7 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +68 -8
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +23 -26
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/sqlite-storage/vector-provider.d.ts +24 -0
- package/dist/builtins/sqlite-storage/wal-maintenance.d.ts +17 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +3 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +3 -1
- package/dist/builtins/system-backup/system-backup.service.d.ts +48 -15
- package/dist/builtins/system-config/system-config.addon.js +2 -2
- package/dist/builtins/system-config/system-config.addon.mjs +2 -2
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-CgiAeG-2.js → composition-sources-197keifF.js} +1 -1
- package/dist/{composition-sources-BXDchWFn.mjs → composition-sources-DmzTSy26.mjs} +1 -1
- package/dist/{constants-1_7QtPNo.js → constants-B5Jd8Zch.js} +1 -1
- package/dist/{constants-C2dhkIuF.mjs → constants-CkNLi-XW.mjs} +1 -1
- package/dist/{dist-DimotS8e.mjs → dist-DMWeqeTF.mjs} +863 -750
- package/dist/{dist-VIIxVsBW.js → dist-DnALrQF-.js} +866 -747
- package/dist/file-host-worker.js +144 -0
- package/dist/file-host-worker.mjs +144 -0
- package/dist/index.js +9 -8
- package/dist/index.mjs +6 -5
- package/dist/{jwt-signing-key-C8nMdNYW.mjs → jwt-signing-key-CCHXAtrs.mjs} +1 -1
- package/dist/{jwt-signing-key-C8s9syVg.js → jwt-signing-key-DeqG7LD9.js} +1 -1
- package/dist/{restore-archive-DjppfrIB.js → restore-archive-C4UhQOsg.js} +156 -65
- package/dist/{restore-archive-bOMZwY0D.mjs → restore-archive-GevpOHw8.mjs} +151 -66
- package/dist/{retired-settings-keys-_muTvNyh.js → retired-settings-keys-0FOsnzIE.js} +1 -1
- package/dist/{retired-settings-keys-BYnjMBE-.mjs → retired-settings-keys-CdVFl7m2.mjs} +1 -1
- package/dist/sqlite-snapshot-worker.js +1 -1
- package/dist/sqlite-snapshot-worker.mjs +1 -1
- package/dist/wal-checkpoint-worker.js +1 -1
- package/dist/wal-checkpoint-worker.mjs +1 -1
- package/dist/wal-maintenance-BtfyfuT5.mjs +0 -0
- package/dist/wal-maintenance-DJV1rde2.js +0 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ 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-
|
|
6
|
+
const require_dist = require("../../dist-DnALrQF-.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as addonPagesCapability, cr as errMsg, lr as BaseAddon, ti as EventCategory } from "../../dist-DMWeqeTF.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ 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-
|
|
6
|
+
const require_dist = require("../../dist-DnALrQF-.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cr as errMsg, lr as BaseAddon, q as addonWidgetsCapability, ti as EventCategory } from "../../dist-DMWeqeTF.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DnALrQF-.js");
|
|
7
7
|
//#region src/builtins/alerts/device-label.ts
|
|
8
8
|
/**
|
|
9
9
|
* Operator-facing device label for alert messages.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { J as alertsCapability, Tr as createEvent, cr as errMsg, lr as BaseAddon, n as AlertSchema, ti as EventCategory } from "../../dist-DMWeqeTF.mjs";
|
|
2
2
|
//#region src/builtins/alerts/device-label.ts
|
|
3
3
|
/**
|
|
4
4
|
* Operator-facing device label for alert messages.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DnALrQF-.js");
|
|
7
7
|
//#region src/builtins/autotrack/autotrack-config.ts
|
|
8
8
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
9
9
|
deadZoneFraction: .5,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as FIRST_LEVEL_MACRO_CLASSES, Pr as isEvent, Z as bareAddonId, cr as errMsg, hn as ptzAutotrackCapability, lr as BaseAddon, ti as EventCategory } from "../../dist-DMWeqeTF.mjs";
|
|
2
2
|
//#region src/builtins/autotrack/autotrack-config.ts
|
|
3
3
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
4
4
|
deadZoneFraction: .5,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which database files a backup carries, and when (D852).
|
|
3
|
+
*
|
|
4
|
+
* Settings is the one a decision reads and runs nightly. Events and history are
|
|
5
|
+
* statistical or ledger stores; losing up to a week costs chart gaps, never
|
|
6
|
+
* configuration, so they ride a WEEKLY schedule whose archive is a consistent
|
|
7
|
+
* full set. Pure functions, no I/O: the orchestrator feeds them what it read.
|
|
8
|
+
*/
|
|
9
|
+
import type { DataStoreDatabase } from "@camstack/types";
|
|
10
|
+
import type { BackupSchedule } from "./schedule-store.js";
|
|
11
|
+
import type { SeedMarkers } from "./seed-marker-store.js";
|
|
12
|
+
/** Stable id of the seeded weekly schedule. */
|
|
13
|
+
export declare const WEEKLY_SCHEDULE_ID = "weekly-events-history";
|
|
14
|
+
export declare const WEEKLY_SCHEDULE_LABEL = "Weekly — events & history";
|
|
15
|
+
/** Sundays 04:00 — an hour after the nightly settings run. */
|
|
16
|
+
export declare const WEEKLY_SCHEDULE_CRON = "0 4 * * 0";
|
|
17
|
+
export declare const WEEKLY_SCHEDULE_RETENTION = 4;
|
|
18
|
+
/**
|
|
19
|
+
* Events and history ONLY: a run that does not select settings carries no
|
|
20
|
+
* data-root walk either, so the weekly archive is just the two snapshots.
|
|
21
|
+
*/
|
|
22
|
+
export declare const WEEKLY_SCHEDULE_DATABASES: readonly DataStoreDatabase[];
|
|
23
|
+
/** What a schedule carries: its own list, else the minimum (D382). */
|
|
24
|
+
export declare function databasesOfSchedule(schedule: Pick<BackupSchedule, "databases">): readonly DataStoreDatabase[];
|
|
25
|
+
/**
|
|
26
|
+
* True when a run asks for a database beyond settings and the owner has none
|
|
27
|
+
* of them open: the run would only repeat the nightly archive. A settings-only
|
|
28
|
+
* run is never nothing-to-carry.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hasNothingBeyondSettings(requested: readonly DataStoreDatabase[], owned: readonly DataStoreDatabase[]): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The weekly schedule to seed, or null. Created only when NO schedule carries
|
|
33
|
+
* `events` (a seed never overwrites, D728 — an operator's own weekly schedule,
|
|
34
|
+
* or an edited seed, is left alone), and only when there is a nightly schedule
|
|
35
|
+
* to take the destinations from: a hub that backs up nowhere gets no weekly
|
|
36
|
+
* archive either.
|
|
37
|
+
*/
|
|
38
|
+
export declare function planWeeklySeed(existing: readonly BackupSchedule[]): BackupSchedule | null;
|
|
39
|
+
/** The slice of the schedule store the seed needs. */
|
|
40
|
+
export interface ScheduleSeedStore {
|
|
41
|
+
list(): Promise<readonly BackupSchedule[]>;
|
|
42
|
+
upsert(schedule: BackupSchedule): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Seed the weekly schedule if {@link planWeeklySeed} says so; returns what it
|
|
46
|
+
* wrote. Exactly once ever: the marker is written after the row, and a marker
|
|
47
|
+
* that exists ends the seed for good — an operator who disabled or deleted the
|
|
48
|
+
* weekly is never second-guessed. A weekly row found with no marker (seeded
|
|
49
|
+
* before the marker existed) is marked, not re-created.
|
|
50
|
+
*/
|
|
51
|
+
export declare function seedWeeklySchedule(store: ScheduleSeedStore, markers: SeedMarkers): Promise<BackupSchedule | null>;
|
|
@@ -249,6 +249,19 @@ export declare class BackupOrchestratorAddon extends BaseAddon<Record<string, ne
|
|
|
249
249
|
*/
|
|
250
250
|
private onScheduleTick;
|
|
251
251
|
/**
|
|
252
|
+
* A schedule that asks for events/history while the owner has neither open
|
|
253
|
+
* would only repeat the nightly archive (D852: the weekly schedule has
|
|
254
|
+
* nothing to carry until those files exist). Said on the record, no archive
|
|
255
|
+
* built, no outcome stamped. An owner that cannot answer never skips: the
|
|
256
|
+
* run goes ahead and fails — or succeeds — in the open.
|
|
257
|
+
*/
|
|
258
|
+
private nothingToCarry;
|
|
259
|
+
/**
|
|
260
|
+
* Seed the weekly "events & history" schedule (D852) — once, and never over
|
|
261
|
+
* anything the operator has: see `planWeeklySeed`.
|
|
262
|
+
*/
|
|
263
|
+
private seedWeekly;
|
|
264
|
+
/**
|
|
252
265
|
* Fire one schedule and RECORD what happened to it, naming every
|
|
253
266
|
* destination. Three outcomes, three different persisted states —
|
|
254
267
|
* because "fired" and "backed up" were the same field until D384:
|
|
@@ -3,8 +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-
|
|
7
|
-
const require_restore_archive = require("../../restore-archive-
|
|
6
|
+
const require_dist = require("../../dist-DnALrQF-.js");
|
|
7
|
+
const require_restore_archive = require("../../restore-archive-C4UhQOsg.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
10
10
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -12,6 +12,138 @@ let node_path = require("node:path");
|
|
|
12
12
|
node_path = require_chunk.__toESM(node_path);
|
|
13
13
|
let node_fs = require("node:fs");
|
|
14
14
|
node_fs = require_chunk.__toESM(node_fs);
|
|
15
|
+
//#region src/builtins/backup-orchestrator/backup-databases.ts
|
|
16
|
+
/** Stable id of the seeded weekly schedule. */
|
|
17
|
+
var WEEKLY_SCHEDULE_ID = "weekly-events-history";
|
|
18
|
+
var WEEKLY_SCHEDULE_LABEL = "Weekly — events & history";
|
|
19
|
+
/** Sundays 04:00 — an hour after the nightly settings run. */
|
|
20
|
+
var WEEKLY_SCHEDULE_CRON = "0 4 * * 0";
|
|
21
|
+
/**
|
|
22
|
+
* Events and history ONLY: a run that does not select settings carries no
|
|
23
|
+
* data-root walk either, so the weekly archive is just the two snapshots.
|
|
24
|
+
*/
|
|
25
|
+
var WEEKLY_SCHEDULE_DATABASES = ["events", "history"];
|
|
26
|
+
/** What a schedule carries: its own list, else the minimum (D382). */
|
|
27
|
+
function databasesOfSchedule(schedule) {
|
|
28
|
+
return schedule.databases !== void 0 && schedule.databases.length > 0 ? schedule.databases : require_restore_archive.DEFAULT_BACKUP_DATABASES;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* True when a run asks for a database beyond settings and the owner has none
|
|
32
|
+
* of them open: the run would only repeat the nightly archive. A settings-only
|
|
33
|
+
* run is never nothing-to-carry.
|
|
34
|
+
*/
|
|
35
|
+
function hasNothingBeyondSettings(requested, owned) {
|
|
36
|
+
const extra = requested.filter((id) => id !== "settings");
|
|
37
|
+
if (extra.length === 0) return false;
|
|
38
|
+
return !extra.some((id) => owned.includes(id));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The weekly schedule to seed, or null. Created only when NO schedule carries
|
|
42
|
+
* `events` (a seed never overwrites, D728 — an operator's own weekly schedule,
|
|
43
|
+
* or an edited seed, is left alone), and only when there is a nightly schedule
|
|
44
|
+
* to take the destinations from: a hub that backs up nowhere gets no weekly
|
|
45
|
+
* archive either.
|
|
46
|
+
*/
|
|
47
|
+
function planWeeklySeed(existing) {
|
|
48
|
+
if (existing.some((s) => s.id === "weekly-events-history")) return null;
|
|
49
|
+
if (existing.some((s) => databasesOfSchedule(s).includes("events"))) return null;
|
|
50
|
+
const destinations = /* @__PURE__ */ new Set();
|
|
51
|
+
for (const s of existing) {
|
|
52
|
+
if (!s.enabled) continue;
|
|
53
|
+
for (const id of s.locationIds) destinations.add(id);
|
|
54
|
+
}
|
|
55
|
+
if (destinations.size === 0) return null;
|
|
56
|
+
return {
|
|
57
|
+
id: WEEKLY_SCHEDULE_ID,
|
|
58
|
+
label: WEEKLY_SCHEDULE_LABEL,
|
|
59
|
+
cron: WEEKLY_SCHEDULE_CRON,
|
|
60
|
+
enabled: true,
|
|
61
|
+
locationIds: [...destinations].toSorted(),
|
|
62
|
+
retentionCount: 4,
|
|
63
|
+
databases: WEEKLY_SCHEDULE_DATABASES
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Seed the weekly schedule if {@link planWeeklySeed} says so; returns what it
|
|
68
|
+
* wrote. Exactly once ever: the marker is written after the row, and a marker
|
|
69
|
+
* that exists ends the seed for good — an operator who disabled or deleted the
|
|
70
|
+
* weekly is never second-guessed. A weekly row found with no marker (seeded
|
|
71
|
+
* before the marker existed) is marked, not re-created.
|
|
72
|
+
*/
|
|
73
|
+
async function seedWeeklySchedule(store, markers) {
|
|
74
|
+
if (await markers.has("weekly-events-history")) return null;
|
|
75
|
+
const existing = await store.list();
|
|
76
|
+
if (existing.some((s) => s.id === "weekly-events-history")) {
|
|
77
|
+
await markers.mark(WEEKLY_SCHEDULE_ID);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const seed = planWeeklySeed(existing);
|
|
81
|
+
if (seed === null) return null;
|
|
82
|
+
await store.upsert(seed);
|
|
83
|
+
await markers.mark(WEEKLY_SCHEDULE_ID);
|
|
84
|
+
return seed;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/builtins/backup-orchestrator/seed-marker-store.ts
|
|
88
|
+
/**
|
|
89
|
+
* @durable class=config owner=backup-orchestrator
|
|
90
|
+
* write="the orchestrator inserts one row per seed id the first time that
|
|
91
|
+
* seed is created (or found already created); never updated"
|
|
92
|
+
* retention="none — one tiny row per seed ever made; deleting it lets the
|
|
93
|
+
* seed be re-created once more on the next boot"
|
|
94
|
+
*/
|
|
95
|
+
var BACKUP_SEED_MARKERS_COLLECTION = "backup_seed_markers";
|
|
96
|
+
var COLUMNS$1 = [{
|
|
97
|
+
name: "seed_id",
|
|
98
|
+
type: "TEXT",
|
|
99
|
+
primaryKey: true
|
|
100
|
+
}, {
|
|
101
|
+
name: "seeded_at",
|
|
102
|
+
type: "INTEGER",
|
|
103
|
+
notNull: true
|
|
104
|
+
}];
|
|
105
|
+
var BackupSeedMarkerStore = class {
|
|
106
|
+
store;
|
|
107
|
+
now;
|
|
108
|
+
declared = false;
|
|
109
|
+
constructor(store, now = Date.now) {
|
|
110
|
+
this.store = store;
|
|
111
|
+
this.now = now;
|
|
112
|
+
}
|
|
113
|
+
async ensureDeclared() {
|
|
114
|
+
if (this.declared) return;
|
|
115
|
+
await this.store.declareCollection({
|
|
116
|
+
collection: BACKUP_SEED_MARKERS_COLLECTION,
|
|
117
|
+
columns: COLUMNS$1
|
|
118
|
+
});
|
|
119
|
+
this.declared = true;
|
|
120
|
+
}
|
|
121
|
+
async has(seedId) {
|
|
122
|
+
await this.ensureDeclared();
|
|
123
|
+
return (await this.store.query({
|
|
124
|
+
collection: BACKUP_SEED_MARKERS_COLLECTION,
|
|
125
|
+
filter: {
|
|
126
|
+
where: { seed_id: seedId },
|
|
127
|
+
limit: 1
|
|
128
|
+
}
|
|
129
|
+
})).length > 0;
|
|
130
|
+
}
|
|
131
|
+
async mark(seedId) {
|
|
132
|
+
await this.ensureDeclared();
|
|
133
|
+
if (await this.has(seedId)) return;
|
|
134
|
+
await this.store.insert({
|
|
135
|
+
collection: BACKUP_SEED_MARKERS_COLLECTION,
|
|
136
|
+
record: {
|
|
137
|
+
id: seedId,
|
|
138
|
+
data: {
|
|
139
|
+
seed_id: seedId,
|
|
140
|
+
seeded_at: this.now()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
//#endregion
|
|
15
147
|
//#region ../../node_modules/croner/dist/croner.js
|
|
16
148
|
function T(s) {
|
|
17
149
|
return Date.UTC(s.y, s.m - 1, s.d, s.h, s.i, s.s);
|
|
@@ -1277,7 +1409,8 @@ function normalizeManifest(raw) {
|
|
|
1277
1409
|
sizeBytes: Number(cand["sizeBytes"]),
|
|
1278
1410
|
...typeof cand["label"] === "string" ? { label: cand["label"] } : {},
|
|
1279
1411
|
manifest,
|
|
1280
|
-
...encryptionOf(cand["encryption"])
|
|
1412
|
+
...encryptionOf(cand["encryption"]),
|
|
1413
|
+
...databasesOf(cand["databases"])
|
|
1281
1414
|
};
|
|
1282
1415
|
archives.push(entry);
|
|
1283
1416
|
}
|
|
@@ -1290,6 +1423,15 @@ var EMPTY_MANIFEST = {
|
|
|
1290
1423
|
version: 1,
|
|
1291
1424
|
archives: []
|
|
1292
1425
|
};
|
|
1426
|
+
function databasesOf(raw) {
|
|
1427
|
+
if (!Array.isArray(raw)) return {};
|
|
1428
|
+
const known = [];
|
|
1429
|
+
const unknown = [];
|
|
1430
|
+
for (const item of raw) if (item === "settings" || item === "events" || item === "history") known.push(item);
|
|
1431
|
+
else if (typeof item === "string") unknown.push(item);
|
|
1432
|
+
if (known.length > 0) return { databases: known };
|
|
1433
|
+
return unknown.length > 0 ? { unrecognisedDatabases: unknown } : {};
|
|
1434
|
+
}
|
|
1293
1435
|
function encryptionOf(raw) {
|
|
1294
1436
|
if (typeof raw !== "object" || raw === null) return {};
|
|
1295
1437
|
const scheme = Reflect.get(raw, "scheme");
|
|
@@ -1313,7 +1455,8 @@ function outlineOfManifest(manifest) {
|
|
|
1313
1455
|
locations: [...manifest.locations],
|
|
1314
1456
|
entries: [],
|
|
1315
1457
|
totalBytes: manifest.totalBytes,
|
|
1316
|
-
totalFiles: manifest.totalFiles
|
|
1458
|
+
totalFiles: manifest.totalFiles,
|
|
1459
|
+
...manifest.omittedDatabases !== void 0 ? { omittedDatabases: [...manifest.omittedDatabases] } : {}
|
|
1317
1460
|
};
|
|
1318
1461
|
}
|
|
1319
1462
|
/**
|
|
@@ -1329,9 +1472,15 @@ function buildArchiveEntry(input) {
|
|
|
1329
1472
|
sizeBytes: input.sizeBytes,
|
|
1330
1473
|
...input.label !== void 0 ? { label: input.label } : {},
|
|
1331
1474
|
manifest: input.encryption !== void 0 ? outlineOfManifest(input.manifest) : input.manifest,
|
|
1332
|
-
...input.encryption !== void 0 ? { encryption: input.encryption } : {}
|
|
1475
|
+
...input.encryption !== void 0 ? { encryption: input.encryption } : {},
|
|
1476
|
+
...input.databases !== void 0 ? { databases: [...input.databases] } : {}
|
|
1333
1477
|
};
|
|
1334
1478
|
}
|
|
1479
|
+
/** The key under which archives of one cadence prune each other. */
|
|
1480
|
+
function retentionClassOf(entry) {
|
|
1481
|
+
if (entry.databases === void 0 && entry.unrecognisedDatabases !== void 0) return `?${[...entry.unrecognisedDatabases].toSorted().join("+")}`;
|
|
1482
|
+
return [...entry.databases ?? require_restore_archive.DEFAULT_BACKUP_DATABASES].toSorted().join("+");
|
|
1483
|
+
}
|
|
1335
1484
|
/**
|
|
1336
1485
|
* Pure retention split: given an archive list and the operator's
|
|
1337
1486
|
* `retentionCount`, return `{ keep, drop }` where `keep` is the most-
|
|
@@ -1340,20 +1489,27 @@ function buildArchiveEntry(input) {
|
|
|
1340
1489
|
* `retentionCount <= 0` is treated as "no pruning" — defensive against
|
|
1341
1490
|
* a config value that would otherwise wipe every archive on the next
|
|
1342
1491
|
* write.
|
|
1492
|
+
*
|
|
1493
|
+
* `cadence` (see {@link retentionClassOf}) restricts the count to archives of
|
|
1494
|
+
* the run that was just written: nightly archives (settings) and weekly ones
|
|
1495
|
+
* (settings + events + history) are retained separately.
|
|
1343
1496
|
*/
|
|
1344
|
-
function applyRetention(archives, retentionCount) {
|
|
1497
|
+
function applyRetention(archives, retentionCount, cadence) {
|
|
1345
1498
|
if (retentionCount <= 0) return {
|
|
1346
1499
|
keep: archives,
|
|
1347
1500
|
drop: []
|
|
1348
1501
|
};
|
|
1349
1502
|
const sorted = [...archives].toSorted((a, b) => b.createdAt - a.createdAt);
|
|
1350
|
-
|
|
1503
|
+
const inCadence = cadence === void 0 ? sorted : sorted.filter((a) => retentionClassOf(a) === cadence);
|
|
1504
|
+
if (inCadence.length <= retentionCount) return {
|
|
1351
1505
|
keep: sorted,
|
|
1352
1506
|
drop: []
|
|
1353
1507
|
};
|
|
1508
|
+
const drop = inCadence.slice(retentionCount);
|
|
1509
|
+
const dropped = new Set(drop);
|
|
1354
1510
|
return {
|
|
1355
|
-
keep: sorted.
|
|
1356
|
-
drop
|
|
1511
|
+
keep: sorted.filter((a) => !dropped.has(a)),
|
|
1512
|
+
drop
|
|
1357
1513
|
};
|
|
1358
1514
|
}
|
|
1359
1515
|
/**
|
|
@@ -1637,7 +1793,8 @@ function requestSignature(request) {
|
|
|
1637
1793
|
destinations: [...request.destinations ?? []].toSorted(),
|
|
1638
1794
|
locations: [...request.locations ?? []].toSorted(),
|
|
1639
1795
|
label: request.label ?? null,
|
|
1640
|
-
retentionCount: request.retentionCount ?? null
|
|
1796
|
+
retentionCount: request.retentionCount ?? null,
|
|
1797
|
+
databases: [...request.databases ?? []].toSorted()
|
|
1641
1798
|
});
|
|
1642
1799
|
}
|
|
1643
1800
|
//#endregion
|
|
@@ -1696,6 +1853,10 @@ var BACKUP_SCHEDULES_COLUMNS = [
|
|
|
1696
1853
|
name: "data_sources",
|
|
1697
1854
|
type: "JSON"
|
|
1698
1855
|
},
|
|
1856
|
+
{
|
|
1857
|
+
name: "databases",
|
|
1858
|
+
type: "JSON"
|
|
1859
|
+
},
|
|
1699
1860
|
{
|
|
1700
1861
|
name: "last_run_at",
|
|
1701
1862
|
type: "INTEGER"
|
|
@@ -1785,6 +1946,7 @@ function mapScheduleToRow(s) {
|
|
|
1785
1946
|
location_ids: [...s.locationIds],
|
|
1786
1947
|
retention_count: s.retentionCount,
|
|
1787
1948
|
data_sources: s.dataSources ? [...s.dataSources] : null,
|
|
1949
|
+
databases: s.databases ? [...s.databases] : null,
|
|
1788
1950
|
last_run_at: s.lastAttemptAt ?? null,
|
|
1789
1951
|
last_success_at: s.lastSuccessAt ?? null,
|
|
1790
1952
|
last_error: s.lastError ?? null
|
|
@@ -1801,6 +1963,12 @@ function parseStringArray(raw) {
|
|
|
1801
1963
|
if (!Array.isArray(value)) return [];
|
|
1802
1964
|
return value.filter((v) => typeof v === "string");
|
|
1803
1965
|
}
|
|
1966
|
+
/** Known database ids only: an unknown name in a stored row is dropped, not carried. */
|
|
1967
|
+
function parseDatabaseIds(raw) {
|
|
1968
|
+
const out = [];
|
|
1969
|
+
for (const id of parseStringArray(raw)) if (id === "settings" || id === "events" || id === "history") out.push(id);
|
|
1970
|
+
return out;
|
|
1971
|
+
}
|
|
1804
1972
|
function tryParseJson(raw) {
|
|
1805
1973
|
if (raw.length === 0) return null;
|
|
1806
1974
|
try {
|
|
@@ -1814,6 +1982,8 @@ function mapRowToSchedule(row) {
|
|
|
1814
1982
|
if (!id) return null;
|
|
1815
1983
|
const dataSourcesRaw = row["data_sources"];
|
|
1816
1984
|
const dataSources = dataSourcesRaw === null || dataSourcesRaw === void 0 ? void 0 : parseStringArray(dataSourcesRaw);
|
|
1985
|
+
const databasesRaw = row["databases"];
|
|
1986
|
+
const databases = databasesRaw === null || databasesRaw === void 0 ? void 0 : parseDatabaseIds(databasesRaw);
|
|
1817
1987
|
const lastRunRaw = row["last_run_at"];
|
|
1818
1988
|
const lastAttemptAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
|
|
1819
1989
|
const lastSuccessRaw = row["last_success_at"];
|
|
@@ -1828,6 +1998,7 @@ function mapRowToSchedule(row) {
|
|
|
1828
1998
|
locationIds: parseStringArray(row["location_ids"]),
|
|
1829
1999
|
retentionCount: Number(row["retention_count"] ?? 7),
|
|
1830
2000
|
...dataSources !== void 0 ? { dataSources } : {},
|
|
2001
|
+
...databases !== void 0 && databases.length > 0 ? { databases } : {},
|
|
1831
2002
|
...lastAttemptAt !== void 0 ? { lastAttemptAt } : {},
|
|
1832
2003
|
...lastSuccessAt !== void 0 ? { lastSuccessAt } : {},
|
|
1833
2004
|
...lastError !== void 0 ? { lastError } : {}
|
|
@@ -2080,6 +2251,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2080
2251
|
this.ctx.logger.error("backup-orchestrator: schedule table init failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
2081
2252
|
this.schedules = null;
|
|
2082
2253
|
}
|
|
2254
|
+
await this.seedWeekly();
|
|
2083
2255
|
const settingsStore = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
|
|
2084
2256
|
this.encryptionKeys = settingsStore ? new BackupEncryptionKeyStore(settingsStore) : null;
|
|
2085
2257
|
const provider = {
|
|
@@ -2385,7 +2557,8 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2385
2557
|
...input?.destinations ? { destinations: input.destinations } : {},
|
|
2386
2558
|
...input?.locations ? { locations: input.locations } : {},
|
|
2387
2559
|
...input?.label !== void 0 ? { label: input.label } : {},
|
|
2388
|
-
...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {}
|
|
2560
|
+
...input?.retentionCount !== void 0 ? { retentionCount: input.retentionCount } : {},
|
|
2561
|
+
...input?.databases ? { databases: input.databases } : {}
|
|
2389
2562
|
};
|
|
2390
2563
|
const submitted = this.runQueue.submit(request);
|
|
2391
2564
|
if (submitted.queued) this.ctx.logger.info(submitted.joined ? "backup trigger: identical run already queued — joined it" : "backup trigger: a run is in flight — enqueued behind it", { meta: { runId: submitted.runId } });
|
|
@@ -2459,6 +2632,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2459
2632
|
signal,
|
|
2460
2633
|
onArchiveBytes: (bytes) => progress.setStagedBytes(bytes),
|
|
2461
2634
|
owner: this.databaseOwner(),
|
|
2635
|
+
...input.databases ? { databases: input.databases } : {},
|
|
2462
2636
|
...encryptTo !== null ? { encryptTo: encryptTo.publicKey } : {}
|
|
2463
2637
|
});
|
|
2464
2638
|
} catch (err) {
|
|
@@ -2491,6 +2665,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2491
2665
|
sizeBytes: result.sizeBytes,
|
|
2492
2666
|
archiveManifest: result.manifest,
|
|
2493
2667
|
retentionCount: input.retentionCount ?? policy.retentionCount,
|
|
2668
|
+
databases: input.databases ?? require_restore_archive.DEFAULT_BACKUP_DATABASES,
|
|
2494
2669
|
createdAt,
|
|
2495
2670
|
label: formatArchiveLabel(dest.id, createdAt, input.label),
|
|
2496
2671
|
...result.encryption !== void 0 ? { encryption: result.encryption } : {},
|
|
@@ -2562,9 +2737,9 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2562
2737
|
};
|
|
2563
2738
|
return {
|
|
2564
2739
|
listDatabases: async () => {
|
|
2565
|
-
return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases
|
|
2740
|
+
return (await this.ctx.api.databaseSnapshot.listDatabases.query({}, pin())).databases;
|
|
2566
2741
|
},
|
|
2567
|
-
writeSnapshot: async () => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({}, pin())
|
|
2742
|
+
writeSnapshot: async (databaseId) => this.ctx.api.databaseSnapshot.writeSnapshot.mutate({ databaseId }, pin())
|
|
2568
2743
|
};
|
|
2569
2744
|
}
|
|
2570
2745
|
/**
|
|
@@ -2616,10 +2791,11 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2616
2791
|
sizeBytes: input.sizeBytes,
|
|
2617
2792
|
label: input.label,
|
|
2618
2793
|
manifest: input.archiveManifest,
|
|
2619
|
-
...input.encryption !== void 0 ? { encryption: input.encryption } : {}
|
|
2794
|
+
...input.encryption !== void 0 ? { encryption: input.encryption } : {},
|
|
2795
|
+
databases: input.databases
|
|
2620
2796
|
});
|
|
2621
2797
|
await this.manifestLock.run(input.locationId, async () => {
|
|
2622
|
-
const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount);
|
|
2798
|
+
const { keep, drop } = applyRetention([...(await readLocationManifest(this.storageApi(), input.locationId)).archives, entry], input.retentionCount, retentionClassOf(entry));
|
|
2623
2799
|
for (const stale of drop) try {
|
|
2624
2800
|
await this.ctx.api.storage.delete.mutate({
|
|
2625
2801
|
location: input.locationId,
|
|
@@ -2673,7 +2849,8 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
2673
2849
|
sizeBytes: a.sizeBytes,
|
|
2674
2850
|
...a.label !== void 0 ? { label: a.label } : {},
|
|
2675
2851
|
locations: [...a.manifest.locations],
|
|
2676
|
-
...a.encryption !== void 0 ? { encryption: a.encryption } : {}
|
|
2852
|
+
...a.encryption !== void 0 ? { encryption: a.encryption } : {},
|
|
2853
|
+
...a.databases !== void 0 ? { databases: [...a.databases] } : {}
|
|
2677
2854
|
}));
|
|
2678
2855
|
}
|
|
2679
2856
|
/**
|
|
@@ -3040,6 +3217,55 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
3040
3217
|
}
|
|
3041
3218
|
}
|
|
3042
3219
|
/**
|
|
3220
|
+
* A schedule that asks for events/history while the owner has neither open
|
|
3221
|
+
* would only repeat the nightly archive (D852: the weekly schedule has
|
|
3222
|
+
* nothing to carry until those files exist). Said on the record, no archive
|
|
3223
|
+
* built, no outcome stamped. An owner that cannot answer never skips: the
|
|
3224
|
+
* run goes ahead and fails — or succeeds — in the open.
|
|
3225
|
+
*/
|
|
3226
|
+
async nothingToCarry(schedule) {
|
|
3227
|
+
const requested = databasesOfSchedule(schedule);
|
|
3228
|
+
if (!requested.some((id) => id !== "settings")) return false;
|
|
3229
|
+
let owned;
|
|
3230
|
+
try {
|
|
3231
|
+
owned = (await this.databaseOwner().listDatabases()).map((d) => d.databaseId);
|
|
3232
|
+
} catch (err) {
|
|
3233
|
+
this.ctx.logger.warn("Scheduled backup: the database owner did not answer — running anyway", { meta: {
|
|
3234
|
+
scheduleId: schedule.id,
|
|
3235
|
+
label: schedule.label,
|
|
3236
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3237
|
+
} });
|
|
3238
|
+
return false;
|
|
3239
|
+
}
|
|
3240
|
+
if (!hasNothingBeyondSettings(requested, owned)) return false;
|
|
3241
|
+
this.ctx.logger.info("Scheduled backup skipped — no database it carries exists yet", { meta: {
|
|
3242
|
+
scheduleId: schedule.id,
|
|
3243
|
+
label: schedule.label,
|
|
3244
|
+
requested: [...requested],
|
|
3245
|
+
owned
|
|
3246
|
+
} });
|
|
3247
|
+
return true;
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* Seed the weekly "events & history" schedule (D852) — once, and never over
|
|
3251
|
+
* anything the operator has: see `planWeeklySeed`.
|
|
3252
|
+
*/
|
|
3253
|
+
async seedWeekly() {
|
|
3254
|
+
if (!this.schedules) return;
|
|
3255
|
+
try {
|
|
3256
|
+
const store = this.ctx.kernel.capabilityRegistry?.getSingleton("settings-store");
|
|
3257
|
+
if (!store) return;
|
|
3258
|
+
const seed = await seedWeeklySchedule(this.schedules, new BackupSeedMarkerStore(store));
|
|
3259
|
+
if (seed === null) return;
|
|
3260
|
+
this.ctx.logger.info("backup: seeded the weekly events & history schedule", { meta: {
|
|
3261
|
+
scheduleId: seed.id,
|
|
3262
|
+
destinationIds: [...seed.locationIds]
|
|
3263
|
+
} });
|
|
3264
|
+
} catch (err) {
|
|
3265
|
+
this.ctx.logger.warn("backup: the weekly schedule could not be seeded", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
/**
|
|
3043
3269
|
* Fire one schedule and RECORD what happened to it, naming every
|
|
3044
3270
|
* destination. Three outcomes, three different persisted states —
|
|
3045
3271
|
* because "fired" and "backed up" were the same field until D384:
|
|
@@ -3050,13 +3276,15 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
3050
3276
|
*/
|
|
3051
3277
|
async runScheduledBackup(schedule, firedAt) {
|
|
3052
3278
|
const destinationIds = [...schedule.locationIds];
|
|
3279
|
+
if (await this.nothingToCarry(schedule)) return;
|
|
3053
3280
|
let failure;
|
|
3054
3281
|
try {
|
|
3055
3282
|
const result = await this.triggerBackup({
|
|
3056
3283
|
destinations: destinationIds,
|
|
3057
3284
|
...schedule.dataSources && schedule.dataSources.length > 0 ? { locations: [...schedule.dataSources] } : {},
|
|
3058
3285
|
label: schedule.label,
|
|
3059
|
-
retentionCount: schedule.retentionCount
|
|
3286
|
+
retentionCount: schedule.retentionCount,
|
|
3287
|
+
...schedule.databases ? { databases: [...schedule.databases] } : {}
|
|
3060
3288
|
});
|
|
3061
3289
|
if (result.entries.length === 0 && !result.cancelled) failure = `no destination accepted the archive (${destinationIds.join(", ")})`;
|
|
3062
3290
|
} catch (err) {
|
|
@@ -3105,6 +3333,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
3105
3333
|
locationIds: [...s.locationIds],
|
|
3106
3334
|
retentionCount: s.retentionCount,
|
|
3107
3335
|
...s.dataSources !== void 0 ? { dataSources: [...s.dataSources] } : {},
|
|
3336
|
+
...s.databases !== void 0 ? { databases: [...s.databases] } : {},
|
|
3108
3337
|
...s.lastAttemptAt !== void 0 ? { lastAttemptAt: s.lastAttemptAt } : {},
|
|
3109
3338
|
...s.lastSuccessAt !== void 0 ? { lastSuccessAt: s.lastSuccessAt } : {},
|
|
3110
3339
|
...s.lastError !== void 0 ? { lastError: s.lastError } : {},
|
|
@@ -3126,6 +3355,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
3126
3355
|
locationIds: [...input.locationIds],
|
|
3127
3356
|
retentionCount: input.retentionCount,
|
|
3128
3357
|
...input.dataSources && input.dataSources.length > 0 ? { dataSources: [...input.dataSources] } : {},
|
|
3358
|
+
...input.databases && input.databases.length > 0 ? { databases: [...input.databases] } : {},
|
|
3129
3359
|
...existing?.lastAttemptAt !== void 0 ? { lastAttemptAt: existing.lastAttemptAt } : {},
|
|
3130
3360
|
...existing?.lastSuccessAt !== void 0 ? { lastSuccessAt: existing.lastSuccessAt } : {},
|
|
3131
3361
|
...existing?.lastError !== void 0 ? { lastError: existing.lastError } : {}
|
|
@@ -3144,6 +3374,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
3144
3374
|
locationIds: [...next.locationIds],
|
|
3145
3375
|
retentionCount: next.retentionCount,
|
|
3146
3376
|
...next.dataSources !== void 0 ? { dataSources: [...next.dataSources] } : {},
|
|
3377
|
+
...next.databases !== void 0 ? { databases: [...next.databases] } : {},
|
|
3147
3378
|
...next.lastAttemptAt !== void 0 ? { lastAttemptAt: next.lastAttemptAt } : {},
|
|
3148
3379
|
...next.lastSuccessAt !== void 0 ? { lastSuccessAt: next.lastSuccessAt } : {},
|
|
3149
3380
|
...next.lastError !== void 0 ? { lastError: next.lastError } : {},
|