@camstack/system 1.2.21 → 1.2.23
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 +1 -1
- package/dist/addon-runner.mjs +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/backup-orchestrator/backup-orchestrator.addon.d.ts +17 -13
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1319 -1097
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1319 -1097
- package/dist/builtins/backup-orchestrator/schedule-store.d.ts +33 -0
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.d.ts +16 -0
- package/dist/builtins/local-network/local-network.addon.js +30 -2
- package/dist/builtins/local-network/local-network.addon.mjs +30 -2
- package/dist/builtins/loki-logging/index.js +34 -12
- package/dist/builtins/loki-logging/index.mjs +34 -12
- package/dist/builtins/loki-logging/loki-logging.addon.d.ts +19 -0
- 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/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +7 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +112 -6
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +112 -6
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +17 -1
- package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +5 -0
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-DmQ6thmF.mjs → dist-CIek6F9X.mjs} +536 -76
- package/dist/{dist-OQ1yU92D.js → dist-CS0pdC8l.js} +536 -76
- package/dist/index.js +58 -8
- package/dist/index.mjs +58 -8
- package/dist/kernel/capability-registry.d.ts +9 -0
- package/dist/kernel/transport/local-child-registry.d.ts +18 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +22 -0
- package/dist/logging/log-manager.d.ts +32 -1
- package/dist/logging/partitioned-log-buffer.d.ts +15 -3
- package/dist/{manifest-python-deps-B30qVAuW.mjs → manifest-python-deps-57PA4rHm.mjs} +15 -6
- package/dist/{manifest-python-deps-CnRW2Utv.js → manifest-python-deps-CqDPWqEE.js} +14 -5
- package/package.json +3 -2
|
@@ -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-CS0pdC8l.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_fs$1 = require_chunk.__toESM(node_fs, 1);
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -3410,1166 +3410,1302 @@ function entryMatchesLocation(entryPath, prefix) {
|
|
|
3410
3410
|
return entryPath === prefix || entryPath.startsWith(`${prefix}/`);
|
|
3411
3411
|
}
|
|
3412
3412
|
//#endregion
|
|
3413
|
-
//#region
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
name: "label",
|
|
3435
|
-
type: "TEXT"
|
|
3436
|
-
},
|
|
3437
|
-
{
|
|
3438
|
-
name: "cron",
|
|
3439
|
-
type: "TEXT"
|
|
3440
|
-
},
|
|
3441
|
-
{
|
|
3442
|
-
name: "last_run_at",
|
|
3443
|
-
type: "INTEGER"
|
|
3444
|
-
}
|
|
3445
|
-
] };
|
|
3446
|
-
var BackupDestinationPolicyService = class {
|
|
3447
|
-
backend;
|
|
3448
|
-
tableEnsured = false;
|
|
3449
|
-
constructor(backend) {
|
|
3450
|
-
this.backend = backend;
|
|
3451
|
-
}
|
|
3452
|
-
/**
|
|
3453
|
-
* Lazy `ensureTable` — called from every read/write path. The
|
|
3454
|
-
* settings backend's own `ensureTable` is idempotent (additive
|
|
3455
|
-
* migration), so repeated calls collapse to a no-op after the first.
|
|
3456
|
-
* This keeps the constructor synchronous and avoids a separate
|
|
3457
|
-
* `initialize()` step that callers could forget.
|
|
3458
|
-
*/
|
|
3459
|
-
async initialize() {
|
|
3460
|
-
await this.ensureTable();
|
|
3461
|
-
}
|
|
3462
|
-
async ensureTable() {
|
|
3463
|
-
if (this.tableEnsured) return;
|
|
3464
|
-
if (!this.backend.ensureTable) throw new Error("BackupDestinationPolicyService: settings backend does not implement ensureTable — expected the SQLite settings backend, got a backend without structured-table support");
|
|
3465
|
-
await this.backend.ensureTable(TABLE_NAME, BACKUP_DESTINATION_POLICIES_SCHEMA);
|
|
3466
|
-
this.tableEnsured = true;
|
|
3467
|
-
}
|
|
3468
|
-
async list() {
|
|
3469
|
-
await this.ensureTable();
|
|
3470
|
-
if (!this.backend.tableQuery) return [];
|
|
3471
|
-
const rows = await this.backend.tableQuery(TABLE_NAME, { orderBy: {
|
|
3472
|
-
field: "location_id",
|
|
3473
|
-
direction: "asc"
|
|
3474
|
-
} });
|
|
3475
|
-
const out = [];
|
|
3476
|
-
for (const row of rows) {
|
|
3477
|
-
const mapped = mapRowToPolicy(row);
|
|
3478
|
-
if (mapped) out.push(mapped);
|
|
3479
|
-
}
|
|
3480
|
-
return out;
|
|
3481
|
-
}
|
|
3482
|
-
async get(locationId) {
|
|
3483
|
-
await this.ensureTable();
|
|
3484
|
-
if (!this.backend.tableGet) return null;
|
|
3485
|
-
const row = await this.backend.tableGet(TABLE_NAME, { location_id: locationId });
|
|
3486
|
-
return row ? mapRowToPolicy(row) : null;
|
|
3487
|
-
}
|
|
3488
|
-
/**
|
|
3489
|
-
* Replace-or-insert. Implemented as `delete` + `insert` (mirrors the
|
|
3490
|
-
* `SqliteLocationStore` contract) so backends that don't surface
|
|
3491
|
-
* `tableUpdate` still work.
|
|
3492
|
-
*/
|
|
3493
|
-
async upsert(p) {
|
|
3494
|
-
await this.ensureTable();
|
|
3495
|
-
if (!this.backend.tableDelete || !this.backend.tableInsert) throw new Error("BackupDestinationPolicyService: backend missing tableDelete/tableInsert");
|
|
3496
|
-
await this.backend.tableDelete(TABLE_NAME, { location_id: p.locationId });
|
|
3497
|
-
await this.backend.tableInsert(TABLE_NAME, mapPolicyToRow(p));
|
|
3498
|
-
}
|
|
3499
|
-
async delete(locationId) {
|
|
3500
|
-
await this.ensureTable();
|
|
3501
|
-
if (!this.backend.tableDelete) throw new Error("BackupDestinationPolicyService: backend missing tableDelete");
|
|
3502
|
-
await this.backend.tableDelete(TABLE_NAME, { location_id: locationId });
|
|
3413
|
+
//#region ../../node_modules/croner/dist/croner.js
|
|
3414
|
+
function T(s) {
|
|
3415
|
+
return Date.UTC(s.y, s.m - 1, s.d, s.h, s.i, s.s);
|
|
3416
|
+
}
|
|
3417
|
+
function D(s, e) {
|
|
3418
|
+
return s.y === e.y && s.m === e.m && s.d === e.d && s.h === e.h && s.i === e.i && s.s === e.s;
|
|
3419
|
+
}
|
|
3420
|
+
function A(s, e) {
|
|
3421
|
+
let t = new Date(Date.parse(s));
|
|
3422
|
+
if (isNaN(t)) throw new Error("Invalid ISO8601 passed to timezone parser.");
|
|
3423
|
+
let r = s.substring(9);
|
|
3424
|
+
return r.includes("Z") || r.includes("+") || r.includes("-") ? b(t.getUTCFullYear(), t.getUTCMonth() + 1, t.getUTCDate(), t.getUTCHours(), t.getUTCMinutes(), t.getUTCSeconds(), "Etc/UTC") : b(t.getFullYear(), t.getMonth() + 1, t.getDate(), t.getHours(), t.getMinutes(), t.getSeconds(), e);
|
|
3425
|
+
}
|
|
3426
|
+
function v(s, e, t) {
|
|
3427
|
+
return k(A(s, e), t);
|
|
3428
|
+
}
|
|
3429
|
+
function k(s, e) {
|
|
3430
|
+
let t = new Date(T(s)), r = g(t, s.tz), a = T(s) - T(r), o = new Date(t.getTime() + a), h = g(o, s.tz);
|
|
3431
|
+
if (D(h, s)) {
|
|
3432
|
+
let u = /* @__PURE__ */ new Date(o.getTime() - 36e5);
|
|
3433
|
+
return D(g(u, s.tz), s) ? u : o;
|
|
3503
3434
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
};
|
|
3525
|
-
await this.upsert(fresh);
|
|
3526
|
-
return fresh;
|
|
3435
|
+
let l = new Date(o.getTime() + T(s) - T(h));
|
|
3436
|
+
if (D(g(l, s.tz), s)) return l;
|
|
3437
|
+
if (e) throw new Error("Invalid date passed to fromTZ()");
|
|
3438
|
+
return o.getTime() > l.getTime() ? o : l;
|
|
3439
|
+
}
|
|
3440
|
+
function g(s, e) {
|
|
3441
|
+
let t, r;
|
|
3442
|
+
try {
|
|
3443
|
+
t = new Intl.DateTimeFormat("en-US", {
|
|
3444
|
+
timeZone: e,
|
|
3445
|
+
year: "numeric",
|
|
3446
|
+
month: "numeric",
|
|
3447
|
+
day: "numeric",
|
|
3448
|
+
hour: "numeric",
|
|
3449
|
+
minute: "numeric",
|
|
3450
|
+
second: "numeric",
|
|
3451
|
+
hour12: !1
|
|
3452
|
+
}), r = t.formatToParts(s);
|
|
3453
|
+
} catch (i) {
|
|
3454
|
+
let a = i instanceof Error ? i.message : String(i);
|
|
3455
|
+
throw new RangeError(`toTZ: Invalid timezone '${e}' or date. Please provide a valid IANA timezone (e.g., 'America/New_York', 'Europe/Stockholm'). Original error: ${a}`);
|
|
3527
3456
|
}
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3457
|
+
let n = {
|
|
3458
|
+
year: 0,
|
|
3459
|
+
month: 0,
|
|
3460
|
+
day: 0,
|
|
3461
|
+
hour: 0,
|
|
3462
|
+
minute: 0,
|
|
3463
|
+
second: 0
|
|
3464
|
+
};
|
|
3465
|
+
for (let i of r) (i.type === "year" || i.type === "month" || i.type === "day" || i.type === "hour" || i.type === "minute" || i.type === "second") && (n[i.type] = parseInt(i.value, 10));
|
|
3466
|
+
if (isNaN(n.year) || isNaN(n.month) || isNaN(n.day) || isNaN(n.hour) || isNaN(n.minute) || isNaN(n.second)) throw new Error(`toTZ: Failed to parse all date components from timezone '${e}'. This may indicate an invalid date or timezone configuration. Parsed components: ${JSON.stringify(n)}`);
|
|
3467
|
+
return n.hour === 24 && (n.hour = 0), {
|
|
3468
|
+
y: n.year,
|
|
3469
|
+
m: n.month,
|
|
3470
|
+
d: n.day,
|
|
3471
|
+
h: n.hour,
|
|
3472
|
+
i: n.minute,
|
|
3473
|
+
s: n.second,
|
|
3474
|
+
tz: e
|
|
3537
3475
|
};
|
|
3538
3476
|
}
|
|
3539
|
-
|
|
3540
|
-
* Decode a row pulled from the structured table. Defensive against
|
|
3541
|
-
* schema drift / partial migrations: returns `null` on missing
|
|
3542
|
-
* `location_id` so corrupt rows skip the result list rather than
|
|
3543
|
-
* throwing the whole orchestrator down at boot.
|
|
3544
|
-
*/
|
|
3545
|
-
function mapRowToPolicy(row) {
|
|
3546
|
-
const locationId = String(row["location_id"] ?? "");
|
|
3547
|
-
if (!locationId) return null;
|
|
3548
|
-
const labelRaw = row["label"];
|
|
3549
|
-
const label = typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : void 0;
|
|
3550
|
-
const cronRaw = row["cron"];
|
|
3551
|
-
const cron = typeof cronRaw === "string" && cronRaw.length > 0 ? cronRaw : void 0;
|
|
3552
|
-
const lastRunRaw = row["last_run_at"];
|
|
3553
|
-
const lastRunAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
|
|
3477
|
+
function b(s, e, t, r, n, i, a) {
|
|
3554
3478
|
return {
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3479
|
+
y: s,
|
|
3480
|
+
m: e,
|
|
3481
|
+
d: t,
|
|
3482
|
+
h: r,
|
|
3483
|
+
i: n,
|
|
3484
|
+
s: i,
|
|
3485
|
+
tz: a
|
|
3561
3486
|
};
|
|
3562
3487
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3488
|
+
var O = [
|
|
3489
|
+
1,
|
|
3490
|
+
2,
|
|
3491
|
+
4,
|
|
3492
|
+
8,
|
|
3493
|
+
16
|
|
3494
|
+
], C = class {
|
|
3495
|
+
pattern;
|
|
3496
|
+
timezone;
|
|
3497
|
+
mode;
|
|
3498
|
+
alternativeWeekdays;
|
|
3499
|
+
sloppyRanges;
|
|
3500
|
+
second;
|
|
3501
|
+
minute;
|
|
3502
|
+
hour;
|
|
3503
|
+
day;
|
|
3504
|
+
month;
|
|
3505
|
+
dayOfWeek;
|
|
3506
|
+
year;
|
|
3507
|
+
lastDayOfMonth;
|
|
3508
|
+
lastWeekday;
|
|
3509
|
+
nearestWeekdays;
|
|
3510
|
+
starDOM;
|
|
3511
|
+
starDOW;
|
|
3512
|
+
starYear;
|
|
3513
|
+
useAndLogic;
|
|
3514
|
+
constructor(e, t, r) {
|
|
3515
|
+
this.pattern = e, this.timezone = t, this.mode = r?.mode ?? "auto", this.alternativeWeekdays = r?.alternativeWeekdays ?? !1, this.sloppyRanges = r?.sloppyRanges ?? !1, this.second = Array(60).fill(0), this.minute = Array(60).fill(0), this.hour = Array(24).fill(0), this.day = Array(31).fill(0), this.month = Array(12).fill(0), this.dayOfWeek = Array(7).fill(0), this.year = Array(1e4).fill(0), this.lastDayOfMonth = !1, this.lastWeekday = !1, this.nearestWeekdays = Array(31).fill(0), this.starDOM = !1, this.starDOW = !1, this.starYear = !1, this.useAndLogic = !1, this.parse();
|
|
3586
3516
|
}
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3517
|
+
parse() {
|
|
3518
|
+
if (!(typeof this.pattern == "string" || this.pattern instanceof String)) throw new TypeError("CronPattern: Pattern has to be of type string.");
|
|
3519
|
+
this.pattern.indexOf("@") >= 0 && (this.pattern = this.handleNicknames(this.pattern).trim());
|
|
3520
|
+
let e = this.pattern.match(/\S+/g) || [""], t = e.length;
|
|
3521
|
+
if (e.length < 5 || e.length > 7) throw new TypeError("CronPattern: invalid configuration format ('" + this.pattern + "'), exactly five, six, or seven space separated parts are required.");
|
|
3522
|
+
if (this.mode !== "auto") {
|
|
3523
|
+
let n;
|
|
3524
|
+
switch (this.mode) {
|
|
3525
|
+
case "5-part":
|
|
3526
|
+
n = 5;
|
|
3527
|
+
break;
|
|
3528
|
+
case "6-part":
|
|
3529
|
+
n = 6;
|
|
3530
|
+
break;
|
|
3531
|
+
case "7-part":
|
|
3532
|
+
n = 7;
|
|
3533
|
+
break;
|
|
3534
|
+
case "5-or-6-parts":
|
|
3535
|
+
n = [5, 6];
|
|
3536
|
+
break;
|
|
3537
|
+
case "6-or-7-parts":
|
|
3538
|
+
n = [6, 7];
|
|
3539
|
+
break;
|
|
3540
|
+
default: n = 0;
|
|
3541
|
+
}
|
|
3542
|
+
if (!(Array.isArray(n) ? n.includes(t) : t === n)) {
|
|
3543
|
+
let a = Array.isArray(n) ? n.join(" or ") : n.toString();
|
|
3544
|
+
throw new TypeError(`CronPattern: mode '${this.mode}' requires exactly ${a} parts, but pattern '${this.pattern}' has ${t} parts.`);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
if (e.length === 5 && e.unshift("0"), e.length === 6 && e.push("*"), e[3].toUpperCase() === "LW" ? (this.lastWeekday = !0, e[3] = "") : e[3].toUpperCase().indexOf("L") >= 0 && (e[3] = e[3].replace(/L/gi, ""), this.lastDayOfMonth = !0), e[3] == "*" && (this.starDOM = !0), e[6] == "*" && (this.starYear = !0), e[4].length >= 3 && (e[4] = this.replaceAlphaMonths(e[4])), e[5].length >= 3 && (e[5] = this.alternativeWeekdays ? this.replaceAlphaDaysQuartz(e[5]) : this.replaceAlphaDays(e[5])), e[5].startsWith("+") && (this.useAndLogic = !0, e[5] = e[5].substring(1), e[5] === "")) throw new TypeError("CronPattern: Day-of-week field cannot be empty after '+' modifier.");
|
|
3548
|
+
switch (e[5] == "*" && (this.starDOW = !0), this.pattern.indexOf("?") >= 0 && (e[0] = e[0].replace(/\?/g, "*"), e[1] = e[1].replace(/\?/g, "*"), e[2] = e[2].replace(/\?/g, "*"), e[3] = e[3].replace(/\?/g, "*"), e[4] = e[4].replace(/\?/g, "*"), e[5] = e[5].replace(/\?/g, "*"), e[6] && (e[6] = e[6].replace(/\?/g, "*"))), this.mode) {
|
|
3549
|
+
case "5-part":
|
|
3550
|
+
e[0] = "0", e[6] = "*";
|
|
3551
|
+
break;
|
|
3552
|
+
case "6-part":
|
|
3553
|
+
e[6] = "*";
|
|
3554
|
+
break;
|
|
3555
|
+
case "5-or-6-parts":
|
|
3556
|
+
e[6] = "*";
|
|
3557
|
+
break;
|
|
3558
|
+
case "6-or-7-parts": break;
|
|
3559
|
+
case "7-part":
|
|
3560
|
+
case "auto": break;
|
|
3561
|
+
}
|
|
3562
|
+
this.throwAtIllegalCharacters(e), this.partToArray("second", e[0], 0, 1), this.partToArray("minute", e[1], 0, 1), this.partToArray("hour", e[2], 0, 1), this.partToArray("day", e[3], -1, 1), this.partToArray("month", e[4], -1, 1);
|
|
3563
|
+
let r = this.alternativeWeekdays ? -1 : 0;
|
|
3564
|
+
this.partToArray("dayOfWeek", e[5], r, 63), this.partToArray("year", e[6], 0, 1), !this.alternativeWeekdays && this.dayOfWeek[7] && (this.dayOfWeek[0] = this.dayOfWeek[7]);
|
|
3610
3565
|
}
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
if (typeof raw !== "object" || raw == null) return EMPTY_MANIFEST;
|
|
3619
|
-
const obj = raw;
|
|
3620
|
-
const archives = [];
|
|
3621
|
-
if (Array.isArray(obj.archives)) for (const a of obj.archives) {
|
|
3622
|
-
if (typeof a !== "object" || a == null) continue;
|
|
3623
|
-
const cand = a;
|
|
3624
|
-
if (typeof cand["id"] !== "string" || typeof cand["filename"] !== "string") continue;
|
|
3625
|
-
if (typeof cand["createdAt"] !== "number" || typeof cand["sizeBytes"] !== "number") continue;
|
|
3626
|
-
const manifest = cand["manifest"];
|
|
3627
|
-
if (typeof manifest !== "object" || manifest == null) continue;
|
|
3628
|
-
const entry = {
|
|
3629
|
-
id: String(cand["id"]),
|
|
3630
|
-
filename: String(cand["filename"]),
|
|
3631
|
-
createdAt: Number(cand["createdAt"]),
|
|
3632
|
-
sizeBytes: Number(cand["sizeBytes"]),
|
|
3633
|
-
...typeof cand["label"] === "string" ? { label: cand["label"] } : {},
|
|
3634
|
-
manifest
|
|
3635
|
-
};
|
|
3636
|
-
archives.push(entry);
|
|
3566
|
+
partToArray(e, t, r, n) {
|
|
3567
|
+
let i = this[e], a = e === "day" && this.lastDayOfMonth, o = e === "day" && this.lastWeekday;
|
|
3568
|
+
if (t === "" && !a && !o) throw new TypeError("CronPattern: configuration entry " + e + " (" + t + ") is empty, check for trailing spaces.");
|
|
3569
|
+
if (t === "*") return i.fill(n);
|
|
3570
|
+
let h = t.split(",");
|
|
3571
|
+
if (h.length > 1) for (let l = 0; l < h.length; l++) this.partToArray(e, h[l], r, n);
|
|
3572
|
+
else t.indexOf("-") !== -1 && t.indexOf("/") !== -1 ? this.handleRangeWithStepping(t, e, r, n) : t.indexOf("-") !== -1 ? this.handleRange(t, e, r, n) : t.indexOf("/") !== -1 ? this.handleStepping(t, e, r, n) : t !== "" && this.handleNumber(t, e, r, n);
|
|
3637
3573
|
}
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
filename: input.filename,
|
|
3656
|
-
createdAt: input.createdAt,
|
|
3657
|
-
sizeBytes: input.sizeBytes,
|
|
3658
|
-
...input.label !== void 0 ? { label: input.label } : {},
|
|
3659
|
-
manifest: input.manifest
|
|
3660
|
-
};
|
|
3661
|
-
}
|
|
3662
|
-
/**
|
|
3663
|
-
* Pure retention split: given an archive list and the operator's
|
|
3664
|
-
* `retentionCount`, return `{ keep, drop }` where `keep` is the most-
|
|
3665
|
-
* recent N archives (newest first) and `drop` is the older overflow.
|
|
3666
|
-
*
|
|
3667
|
-
* `retentionCount <= 0` is treated as "no pruning" — defensive against
|
|
3668
|
-
* a config value that would otherwise wipe every archive on the next
|
|
3669
|
-
* write.
|
|
3670
|
-
*/
|
|
3671
|
-
function applyRetention(archives, retentionCount) {
|
|
3672
|
-
if (retentionCount <= 0) return {
|
|
3673
|
-
keep: archives,
|
|
3674
|
-
drop: []
|
|
3675
|
-
};
|
|
3676
|
-
const sorted = [...archives].toSorted((a, b) => b.createdAt - a.createdAt);
|
|
3677
|
-
if (sorted.length <= retentionCount) return {
|
|
3678
|
-
keep: sorted,
|
|
3679
|
-
drop: []
|
|
3680
|
-
};
|
|
3681
|
-
return {
|
|
3682
|
-
keep: sorted.slice(0, retentionCount),
|
|
3683
|
-
drop: sorted.slice(retentionCount)
|
|
3684
|
-
};
|
|
3685
|
-
}
|
|
3686
|
-
/**
|
|
3687
|
-
* Per-location async mutex. The orchestrator owns one instance and
|
|
3688
|
-
* funnels every read-modify-write of `manifests.json` through it so
|
|
3689
|
-
* parallel destination uploads (within the same location) commit in
|
|
3690
|
-
* arrival order. Across locations the operations run in parallel —
|
|
3691
|
-
* each location keys its own promise chain.
|
|
3692
|
-
*/
|
|
3693
|
-
var LocationManifestLock = class {
|
|
3694
|
-
chains = /* @__PURE__ */ new Map();
|
|
3695
|
-
async run(locationId, fn) {
|
|
3696
|
-
const previous = this.chains.get(locationId) ?? Promise.resolve();
|
|
3697
|
-
let resolveNext;
|
|
3698
|
-
const next = new Promise((res) => {
|
|
3699
|
-
resolveNext = res;
|
|
3700
|
-
});
|
|
3701
|
-
this.chains.set(locationId, previous.then(() => next));
|
|
3702
|
-
try {
|
|
3703
|
-
await previous;
|
|
3704
|
-
return await fn();
|
|
3705
|
-
} finally {
|
|
3706
|
-
resolveNext();
|
|
3707
|
-
const tail = this.chains.get(locationId);
|
|
3708
|
-
if (tail) Promise.resolve(tail).then(() => {
|
|
3709
|
-
if (this.chains.get(locationId) === tail) this.chains.delete(locationId);
|
|
3710
|
-
}).catch(() => {});
|
|
3574
|
+
throwAtIllegalCharacters(e) {
|
|
3575
|
+
for (let t = 0; t < e.length; t++) if ((t === 3 ? /[^/*0-9,\-WwLl]+/ : t === 5 ? /[^/*0-9,\-#Ll]+/ : /[^/*0-9,\-]+/).test(e[t])) throw new TypeError("CronPattern: configuration entry " + t + " (" + e[t] + ") contains illegal characters.");
|
|
3576
|
+
}
|
|
3577
|
+
handleNumber(e, t, r, n) {
|
|
3578
|
+
let i = this.extractNth(e, t), a = e.toUpperCase().includes("W");
|
|
3579
|
+
if (t !== "day" && a) throw new TypeError("CronPattern: Nearest weekday modifier (W) only allowed in day-of-month.");
|
|
3580
|
+
a && (t = "nearestWeekdays");
|
|
3581
|
+
let o = parseInt(i[0], 10) + r;
|
|
3582
|
+
if (isNaN(o)) throw new TypeError("CronPattern: " + t + " is not a number: '" + e + "'");
|
|
3583
|
+
this.setPart(t, o, i[1] || n);
|
|
3584
|
+
}
|
|
3585
|
+
setPart(e, t, r) {
|
|
3586
|
+
if (!Object.prototype.hasOwnProperty.call(this, e)) throw new TypeError("CronPattern: Invalid part specified: " + e);
|
|
3587
|
+
if (e === "dayOfWeek") {
|
|
3588
|
+
if (t === 7 && (t = 0), t < 0 || t > 6) throw new RangeError("CronPattern: Invalid value for dayOfWeek: " + t);
|
|
3589
|
+
this.setNthWeekdayOfMonth(t, r);
|
|
3590
|
+
return;
|
|
3711
3591
|
}
|
|
3592
|
+
if (e === "second" || e === "minute") {
|
|
3593
|
+
if (t < 0 || t >= 60) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3594
|
+
} else if (e === "hour") {
|
|
3595
|
+
if (t < 0 || t >= 24) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3596
|
+
} else if (e === "day" || e === "nearestWeekdays") {
|
|
3597
|
+
if (t < 0 || t >= 31) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3598
|
+
} else if (e === "month") {
|
|
3599
|
+
if (t < 0 || t >= 12) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3600
|
+
} else if (e === "year" && (t < 1 || t >= 1e4)) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t + " (supported range: 1-9999)");
|
|
3601
|
+
this[e][t] = r;
|
|
3712
3602
|
}
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
* local filesystem path via the consumer-facing `storage` cap. Pulled
|
|
3722
|
-
* out of `backup-orchestrator.addon.ts` so it can be unit-tested with
|
|
3723
|
-
* an in-memory fake `AddonApi['storage']` slice — the addon is hard
|
|
3724
|
-
* to bootstrap in vitest because of the BaseAddon ceremony.
|
|
3725
|
-
*
|
|
3726
|
-
* Cleanup contract:
|
|
3727
|
-
* - Successful run leaves the cache file in place + calls
|
|
3728
|
-
* `endDownload`.
|
|
3729
|
-
* - Mid-stream failure destroys the writeStream, removes the
|
|
3730
|
-
* partial cache file, and best-effort calls `endDownload` so the
|
|
3731
|
-
* storage provider's session bookkeeping doesn't leak.
|
|
3732
|
-
*/
|
|
3733
|
-
async function downloadArchiveToCache(input) {
|
|
3734
|
-
const { downloadId, sizeBytes } = await input.api.beginDownload.mutate({
|
|
3735
|
-
location: input.locationId,
|
|
3736
|
-
relativePath: input.filename
|
|
3737
|
-
});
|
|
3738
|
-
const writeStream = node_fs.createWriteStream(input.cachePath);
|
|
3739
|
-
let offset = 0;
|
|
3740
|
-
try {
|
|
3741
|
-
while (offset < sizeBytes) {
|
|
3742
|
-
const remaining = sizeBytes - offset;
|
|
3743
|
-
const length = Math.min(input.chunkBytes, remaining);
|
|
3744
|
-
const chunk = await input.api.readChunk.query({
|
|
3745
|
-
downloadId,
|
|
3746
|
-
offset,
|
|
3747
|
-
length
|
|
3748
|
-
});
|
|
3749
|
-
if (chunk.byteLength === 0) throw new Error(`backup restore: empty chunk at offset ${offset}/${sizeBytes} from "${input.locationId}/${input.filename}"`);
|
|
3750
|
-
writeStream.write(chunk);
|
|
3751
|
-
offset += chunk.byteLength;
|
|
3603
|
+
validateNotNaN(e, t) {
|
|
3604
|
+
if (isNaN(e)) throw new TypeError(t);
|
|
3605
|
+
}
|
|
3606
|
+
validateRange(e, t, r, n, i) {
|
|
3607
|
+
if (e > t) throw new TypeError("CronPattern: From value is larger than to value: '" + i + "'");
|
|
3608
|
+
if (r !== void 0) {
|
|
3609
|
+
if (r === 0) throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");
|
|
3610
|
+
if (r > this[n].length) throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part (" + this[n].length + ")");
|
|
3752
3611
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3612
|
+
}
|
|
3613
|
+
handleRangeWithStepping(e, t, r, n) {
|
|
3614
|
+
if (e.toUpperCase().includes("W")) throw new TypeError("CronPattern: Syntax error, W is not allowed in ranges with stepping.");
|
|
3615
|
+
let i = this.extractNth(e, t), a = i[0].match(/^(\d+)-(\d+)\/(\d+)$/);
|
|
3616
|
+
if (a === null) throw new TypeError("CronPattern: Syntax error, illegal range with stepping: '" + e + "'");
|
|
3617
|
+
let [, o, h, l] = a, y = parseInt(o, 10) + r, u = parseInt(h, 10) + r, d = parseInt(l, 10);
|
|
3618
|
+
this.validateNotNaN(y, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(u, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateNotNaN(d, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(y, u, d, t, e);
|
|
3619
|
+
for (let c = y; c <= u; c += d) this.setPart(t, c, i[1] || n);
|
|
3620
|
+
}
|
|
3621
|
+
extractNth(e, t) {
|
|
3622
|
+
let r = e, n;
|
|
3623
|
+
if (r.includes("#")) {
|
|
3624
|
+
if (t !== "dayOfWeek") throw new Error("CronPattern: nth (#) only allowed in day-of-week field");
|
|
3625
|
+
n = r.split("#")[1], r = r.split("#")[0];
|
|
3626
|
+
} else if (r.toUpperCase().endsWith("L")) {
|
|
3627
|
+
if (t !== "dayOfWeek") throw new Error("CronPattern: L modifier only allowed in day-of-week field (use L alone for day-of-month)");
|
|
3628
|
+
n = "L", r = r.slice(0, -1);
|
|
3766
3629
|
}
|
|
3767
|
-
|
|
3768
|
-
await new Promise((resolve) => {
|
|
3769
|
-
writeStream.once("close", () => resolve());
|
|
3770
|
-
writeStream.destroy();
|
|
3771
|
-
});
|
|
3772
|
-
try {
|
|
3773
|
-
await node_fs_promises.rm(input.cachePath, { force: true });
|
|
3774
|
-
} catch {}
|
|
3775
|
-
try {
|
|
3776
|
-
await input.api.endDownload.mutate({ downloadId });
|
|
3777
|
-
} catch {}
|
|
3778
|
-
throw err;
|
|
3630
|
+
return [r, n];
|
|
3779
3631
|
}
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
return s.y === e.y && s.m === e.m && s.d === e.d && s.h === e.h && s.i === e.i && s.s === e.s;
|
|
3788
|
-
}
|
|
3789
|
-
function A(s, e) {
|
|
3790
|
-
let t = new Date(Date.parse(s));
|
|
3791
|
-
if (isNaN(t)) throw new Error("Invalid ISO8601 passed to timezone parser.");
|
|
3792
|
-
let r = s.substring(9);
|
|
3793
|
-
return r.includes("Z") || r.includes("+") || r.includes("-") ? b(t.getUTCFullYear(), t.getUTCMonth() + 1, t.getUTCDate(), t.getUTCHours(), t.getUTCMinutes(), t.getUTCSeconds(), "Etc/UTC") : b(t.getFullYear(), t.getMonth() + 1, t.getDate(), t.getHours(), t.getMinutes(), t.getSeconds(), e);
|
|
3794
|
-
}
|
|
3795
|
-
function v(s, e, t) {
|
|
3796
|
-
return k(A(s, e), t);
|
|
3797
|
-
}
|
|
3798
|
-
function k(s, e) {
|
|
3799
|
-
let t = new Date(T(s)), r = g(t, s.tz), a = T(s) - T(r), o = new Date(t.getTime() + a), h = g(o, s.tz);
|
|
3800
|
-
if (D(h, s)) {
|
|
3801
|
-
let u = /* @__PURE__ */ new Date(o.getTime() - 36e5);
|
|
3802
|
-
return D(g(u, s.tz), s) ? u : o;
|
|
3632
|
+
handleRange(e, t, r, n) {
|
|
3633
|
+
if (e.toUpperCase().includes("W")) throw new TypeError("CronPattern: Syntax error, W is not allowed in a range.");
|
|
3634
|
+
let i = this.extractNth(e, t), a = i[0].split("-");
|
|
3635
|
+
if (a.length !== 2) throw new TypeError("CronPattern: Syntax error, illegal range: '" + e + "'");
|
|
3636
|
+
let o = parseInt(a[0], 10) + r, h = parseInt(a[1], 10) + r;
|
|
3637
|
+
this.validateNotNaN(o, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(h, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateRange(o, h, void 0, t, e);
|
|
3638
|
+
for (let l = o; l <= h; l++) this.setPart(t, l, i[1] || n);
|
|
3803
3639
|
}
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
minute: "numeric",
|
|
3819
|
-
second: "numeric",
|
|
3820
|
-
hour12: !1
|
|
3821
|
-
}), r = t.formatToParts(s);
|
|
3822
|
-
} catch (i) {
|
|
3823
|
-
let a = i instanceof Error ? i.message : String(i);
|
|
3824
|
-
throw new RangeError(`toTZ: Invalid timezone '${e}' or date. Please provide a valid IANA timezone (e.g., 'America/New_York', 'Europe/Stockholm'). Original error: ${a}`);
|
|
3640
|
+
handleStepping(e, t, r, n) {
|
|
3641
|
+
if (e.toUpperCase().includes("W")) throw new TypeError("CronPattern: Syntax error, W is not allowed in parts with stepping.");
|
|
3642
|
+
let i = this.extractNth(e, t), a = i[0].split("/");
|
|
3643
|
+
if (a.length !== 2) throw new TypeError("CronPattern: Syntax error, illegal stepping: '" + e + "'");
|
|
3644
|
+
if (this.sloppyRanges) a[0] === "" && (a[0] = "*");
|
|
3645
|
+
else {
|
|
3646
|
+
if (a[0] === "") throw new TypeError("CronPattern: Syntax error, stepping with missing prefix ('" + e + "') is not allowed. Use wildcard (*/step) or range (min-max/step) instead.");
|
|
3647
|
+
if (a[0] !== "*") throw new TypeError("CronPattern: Syntax error, stepping with numeric prefix ('" + e + "') is not allowed. Use wildcard (*/step) or range (min-max/step) instead.");
|
|
3648
|
+
}
|
|
3649
|
+
let o = 0;
|
|
3650
|
+
a[0] !== "*" && (o = parseInt(a[0], 10) + r);
|
|
3651
|
+
let h = parseInt(a[1], 10);
|
|
3652
|
+
this.validateNotNaN(h, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(0, this[t].length - 1, h, t, e);
|
|
3653
|
+
for (let l = o; l < this[t].length; l += h) this.setPart(t, l, i[1] || n);
|
|
3825
3654
|
}
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
}
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3655
|
+
replaceAlphaDays(e) {
|
|
3656
|
+
return e.replace(/-sun/gi, "-7").replace(/sun/gi, "0").replace(/mon/gi, "1").replace(/tue/gi, "2").replace(/wed/gi, "3").replace(/thu/gi, "4").replace(/fri/gi, "5").replace(/sat/gi, "6");
|
|
3657
|
+
}
|
|
3658
|
+
replaceAlphaDaysQuartz(e) {
|
|
3659
|
+
return e.replace(/sun/gi, "1").replace(/mon/gi, "2").replace(/tue/gi, "3").replace(/wed/gi, "4").replace(/thu/gi, "5").replace(/fri/gi, "6").replace(/sat/gi, "7");
|
|
3660
|
+
}
|
|
3661
|
+
replaceAlphaMonths(e) {
|
|
3662
|
+
return e.replace(/jan/gi, "1").replace(/feb/gi, "2").replace(/mar/gi, "3").replace(/apr/gi, "4").replace(/may/gi, "5").replace(/jun/gi, "6").replace(/jul/gi, "7").replace(/aug/gi, "8").replace(/sep/gi, "9").replace(/oct/gi, "10").replace(/nov/gi, "11").replace(/dec/gi, "12");
|
|
3663
|
+
}
|
|
3664
|
+
handleNicknames(e) {
|
|
3665
|
+
let t = e.trim().toLowerCase();
|
|
3666
|
+
if (t === "@yearly" || t === "@annually") return "0 0 1 1 *";
|
|
3667
|
+
if (t === "@monthly") return "0 0 1 * *";
|
|
3668
|
+
if (t === "@weekly") return "0 0 * * 0";
|
|
3669
|
+
if (t === "@daily" || t === "@midnight") return "0 0 * * *";
|
|
3670
|
+
if (t === "@hourly") return "0 * * * *";
|
|
3671
|
+
if (t === "@reboot") throw new TypeError("CronPattern: @reboot is not supported in this environment. This is an event-based trigger that requires system startup detection.");
|
|
3672
|
+
return e;
|
|
3673
|
+
}
|
|
3674
|
+
setNthWeekdayOfMonth(e, t) {
|
|
3675
|
+
if (typeof t != "number" && t.toUpperCase() === "L") this.dayOfWeek[e] = this.dayOfWeek[e] | 32;
|
|
3676
|
+
else if (t === 63) this.dayOfWeek[e] = 63;
|
|
3677
|
+
else if (t < 6 && t > 0) this.dayOfWeek[e] = this.dayOfWeek[e] | O[t - 1];
|
|
3678
|
+
else throw new TypeError(`CronPattern: nth weekday out of range, should be 1-5 or L. Value: ${t}, Type: ${typeof t}`);
|
|
3679
|
+
}
|
|
3680
|
+
};
|
|
3681
|
+
var P = [
|
|
3682
|
+
31,
|
|
3683
|
+
28,
|
|
3684
|
+
31,
|
|
3685
|
+
30,
|
|
3686
|
+
31,
|
|
3687
|
+
30,
|
|
3688
|
+
31,
|
|
3689
|
+
31,
|
|
3690
|
+
30,
|
|
3691
|
+
31,
|
|
3692
|
+
30,
|
|
3693
|
+
31
|
|
3694
|
+
], f = [
|
|
3695
|
+
[
|
|
3696
|
+
"month",
|
|
3697
|
+
"year",
|
|
3698
|
+
0
|
|
3699
|
+
],
|
|
3700
|
+
[
|
|
3701
|
+
"day",
|
|
3702
|
+
"month",
|
|
3703
|
+
-1
|
|
3704
|
+
],
|
|
3705
|
+
[
|
|
3706
|
+
"hour",
|
|
3707
|
+
"day",
|
|
3708
|
+
0
|
|
3709
|
+
],
|
|
3710
|
+
[
|
|
3711
|
+
"minute",
|
|
3712
|
+
"hour",
|
|
3713
|
+
0
|
|
3714
|
+
],
|
|
3715
|
+
[
|
|
3716
|
+
"second",
|
|
3717
|
+
"minute",
|
|
3718
|
+
0
|
|
3719
|
+
]
|
|
3720
|
+
], m = class s {
|
|
3721
|
+
tz;
|
|
3722
|
+
ms;
|
|
3869
3723
|
second;
|
|
3870
3724
|
minute;
|
|
3871
3725
|
hour;
|
|
3872
3726
|
day;
|
|
3873
3727
|
month;
|
|
3874
|
-
dayOfWeek;
|
|
3875
3728
|
year;
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
constructor(e, t, r) {
|
|
3884
|
-
this.pattern = e, this.timezone = t, this.mode = r?.mode ?? "auto", this.alternativeWeekdays = r?.alternativeWeekdays ?? !1, this.sloppyRanges = r?.sloppyRanges ?? !1, this.second = Array(60).fill(0), this.minute = Array(60).fill(0), this.hour = Array(24).fill(0), this.day = Array(31).fill(0), this.month = Array(12).fill(0), this.dayOfWeek = Array(7).fill(0), this.year = Array(1e4).fill(0), this.lastDayOfMonth = !1, this.lastWeekday = !1, this.nearestWeekdays = Array(31).fill(0), this.starDOM = !1, this.starDOW = !1, this.starYear = !1, this.useAndLogic = !1, this.parse();
|
|
3729
|
+
constructor(e, t) {
|
|
3730
|
+
if (this.tz = t, e && e instanceof Date) if (!isNaN(e)) this.fromDate(e);
|
|
3731
|
+
else throw new TypeError("CronDate: Invalid date passed to CronDate constructor");
|
|
3732
|
+
else if (e == null) this.fromDate(/* @__PURE__ */ new Date());
|
|
3733
|
+
else if (e && typeof e == "string") this.fromString(e);
|
|
3734
|
+
else if (e instanceof s) this.fromCronDate(e);
|
|
3735
|
+
else throw new TypeError("CronDate: Invalid type (" + typeof e + ") passed to CronDate constructor");
|
|
3885
3736
|
}
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3737
|
+
getLastDayOfMonth(e, t) {
|
|
3738
|
+
return t !== 1 ? P[t] : new Date(Date.UTC(e, t + 1, 0)).getUTCDate();
|
|
3739
|
+
}
|
|
3740
|
+
getLastWeekday(e, t) {
|
|
3741
|
+
let r = this.getLastDayOfMonth(e, t), i = new Date(Date.UTC(e, t, r)).getUTCDay();
|
|
3742
|
+
return i === 0 ? r - 2 : i === 6 ? r - 1 : r;
|
|
3743
|
+
}
|
|
3744
|
+
getNearestWeekday(e, t, r) {
|
|
3745
|
+
let n = this.getLastDayOfMonth(e, t);
|
|
3746
|
+
if (r > n) return -1;
|
|
3747
|
+
let a = new Date(Date.UTC(e, t, r)).getUTCDay();
|
|
3748
|
+
return a === 0 ? r === n ? r - 2 : r + 1 : a === 6 ? r === 1 ? r + 2 : r - 1 : r;
|
|
3749
|
+
}
|
|
3750
|
+
isNthWeekdayOfMonth(e, t, r, n) {
|
|
3751
|
+
let a = new Date(Date.UTC(e, t, r)).getUTCDay(), o = 0;
|
|
3752
|
+
for (let h = 1; h <= r; h++) new Date(Date.UTC(e, t, h)).getUTCDay() === a && o++;
|
|
3753
|
+
if (n & 63 && O[o - 1] & n) return !0;
|
|
3754
|
+
if (n & 32) {
|
|
3755
|
+
let h = this.getLastDayOfMonth(e, t);
|
|
3756
|
+
for (let l = r + 1; l <= h; l++) if (new Date(Date.UTC(e, t, l)).getUTCDay() === a) return !1;
|
|
3757
|
+
return !0;
|
|
3758
|
+
}
|
|
3759
|
+
return !1;
|
|
3760
|
+
}
|
|
3761
|
+
fromDate(e) {
|
|
3762
|
+
if (this.tz !== void 0) if (typeof this.tz == "number") this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes() + this.tz, this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), this.apply();
|
|
3763
|
+
else try {
|
|
3764
|
+
let t = g(e, this.tz);
|
|
3765
|
+
this.ms = e.getMilliseconds(), this.second = t.s, this.minute = t.i, this.hour = t.h, this.day = t.d, this.month = t.m - 1, this.year = t.y;
|
|
3766
|
+
} catch (t) {
|
|
3767
|
+
let r = t instanceof Error ? t.message : String(t);
|
|
3768
|
+
throw new TypeError(`CronDate: Failed to convert date to timezone '${this.tz}'. This may happen with invalid timezone names or dates. Original error: ${r}`);
|
|
3769
|
+
}
|
|
3770
|
+
else this.ms = e.getMilliseconds(), this.second = e.getSeconds(), this.minute = e.getMinutes(), this.hour = e.getHours(), this.day = e.getDate(), this.month = e.getMonth(), this.year = e.getFullYear();
|
|
3771
|
+
}
|
|
3772
|
+
fromCronDate(e) {
|
|
3773
|
+
this.tz = e.tz, this.year = e.year, this.month = e.month, this.day = e.day, this.hour = e.hour, this.minute = e.minute, this.second = e.second, this.ms = e.ms;
|
|
3774
|
+
}
|
|
3775
|
+
apply() {
|
|
3776
|
+
if (this.month > 11 || this.month < 0 || this.day > P[this.month] || this.day < 1 || this.hour > 59 || this.minute > 59 || this.second > 59 || this.hour < 0 || this.minute < 0 || this.second < 0) {
|
|
3777
|
+
let e = new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms));
|
|
3778
|
+
return this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes(), this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), !0;
|
|
3779
|
+
} else return !1;
|
|
3780
|
+
}
|
|
3781
|
+
fromString(e) {
|
|
3782
|
+
if (typeof this.tz == "number") {
|
|
3783
|
+
let t = v(e);
|
|
3784
|
+
this.ms = t.getUTCMilliseconds(), this.second = t.getUTCSeconds(), this.minute = t.getUTCMinutes(), this.hour = t.getUTCHours(), this.day = t.getUTCDate(), this.month = t.getUTCMonth(), this.year = t.getUTCFullYear(), this.apply();
|
|
3785
|
+
} else return this.fromDate(v(e, this.tz));
|
|
3786
|
+
}
|
|
3787
|
+
findNext(e, t, r, n) {
|
|
3788
|
+
return this._findMatch(e, t, r, n, 1);
|
|
3789
|
+
}
|
|
3790
|
+
_findMatch(e, t, r, n, i) {
|
|
3791
|
+
let a = this[t], o;
|
|
3792
|
+
r.lastDayOfMonth && (o = this.getLastDayOfMonth(this.year, this.month));
|
|
3793
|
+
let h = !r.starDOW && t == "day" ? new Date(Date.UTC(this.year, this.month, 1, 0, 0, 0, 0)).getUTCDay() : void 0, l = this[t] + n, y = i === 1 ? (u) => u < r[t].length : (u) => u >= 0;
|
|
3794
|
+
for (let u = l; y(u); u += i) {
|
|
3795
|
+
let d = r[t][u];
|
|
3796
|
+
if (t === "day" && !d) {
|
|
3797
|
+
for (let c = 0; c < r.nearestWeekdays.length; c++) if (r.nearestWeekdays[c]) {
|
|
3798
|
+
let M = this.getNearestWeekday(this.year, this.month, c - n);
|
|
3799
|
+
if (M === -1) continue;
|
|
3800
|
+
if (M === u - n) {
|
|
3801
|
+
d = 1;
|
|
3802
|
+
break;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3910
3805
|
}
|
|
3911
|
-
if (
|
|
3912
|
-
let
|
|
3913
|
-
|
|
3806
|
+
if (t === "day" && r.lastWeekday) {
|
|
3807
|
+
let c = this.getLastWeekday(this.year, this.month);
|
|
3808
|
+
u - n === c && (d = 1);
|
|
3914
3809
|
}
|
|
3810
|
+
if (t === "day" && r.lastDayOfMonth && u - n == o && (d = 1), t === "day" && !r.starDOW) {
|
|
3811
|
+
let c = r.dayOfWeek[(h + (u - n - 1)) % 7];
|
|
3812
|
+
if (c && c & 63) c = this.isNthWeekdayOfMonth(this.year, this.month, u - n, c) ? 1 : 0;
|
|
3813
|
+
else if (c) throw new Error(`CronDate: Invalid value for dayOfWeek encountered. ${c}`);
|
|
3814
|
+
r.useAndLogic ? d = d && c : !e.domAndDow && !r.starDOM ? d = d || c : d = d && c;
|
|
3815
|
+
}
|
|
3816
|
+
if (d) return this[t] = u - n, a !== this[t] ? 2 : 1;
|
|
3915
3817
|
}
|
|
3916
|
-
|
|
3917
|
-
switch (e[5] == "*" && (this.starDOW = !0), this.pattern.indexOf("?") >= 0 && (e[0] = e[0].replace(/\?/g, "*"), e[1] = e[1].replace(/\?/g, "*"), e[2] = e[2].replace(/\?/g, "*"), e[3] = e[3].replace(/\?/g, "*"), e[4] = e[4].replace(/\?/g, "*"), e[5] = e[5].replace(/\?/g, "*"), e[6] && (e[6] = e[6].replace(/\?/g, "*"))), this.mode) {
|
|
3918
|
-
case "5-part":
|
|
3919
|
-
e[0] = "0", e[6] = "*";
|
|
3920
|
-
break;
|
|
3921
|
-
case "6-part":
|
|
3922
|
-
e[6] = "*";
|
|
3923
|
-
break;
|
|
3924
|
-
case "5-or-6-parts":
|
|
3925
|
-
e[6] = "*";
|
|
3926
|
-
break;
|
|
3927
|
-
case "6-or-7-parts": break;
|
|
3928
|
-
case "7-part":
|
|
3929
|
-
case "auto": break;
|
|
3930
|
-
}
|
|
3931
|
-
this.throwAtIllegalCharacters(e), this.partToArray("second", e[0], 0, 1), this.partToArray("minute", e[1], 0, 1), this.partToArray("hour", e[2], 0, 1), this.partToArray("day", e[3], -1, 1), this.partToArray("month", e[4], -1, 1);
|
|
3932
|
-
let r = this.alternativeWeekdays ? -1 : 0;
|
|
3933
|
-
this.partToArray("dayOfWeek", e[5], r, 63), this.partToArray("year", e[6], 0, 1), !this.alternativeWeekdays && this.dayOfWeek[7] && (this.dayOfWeek[0] = this.dayOfWeek[7]);
|
|
3818
|
+
return 3;
|
|
3934
3819
|
}
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3820
|
+
recurse(e, t, r) {
|
|
3821
|
+
if (r === 0 && !e.starYear) {
|
|
3822
|
+
if (this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0) {
|
|
3823
|
+
let i = -1;
|
|
3824
|
+
for (let a = this.year + 1; a < e.year.length && a < 1e4; a++) if (e.year[a] === 1) {
|
|
3825
|
+
i = a;
|
|
3826
|
+
break;
|
|
3827
|
+
}
|
|
3828
|
+
if (i === -1) return null;
|
|
3829
|
+
this.year = i, this.month = 0, this.day = 1, this.hour = 0, this.minute = 0, this.second = 0, this.ms = 0;
|
|
3830
|
+
}
|
|
3831
|
+
if (this.year >= 1e4) return null;
|
|
3832
|
+
}
|
|
3833
|
+
let n = this.findNext(t, f[r][0], e, f[r][2]);
|
|
3834
|
+
if (n > 1) {
|
|
3835
|
+
let i = r + 1;
|
|
3836
|
+
for (; i < f.length;) this[f[i][0]] = -f[i][2], i++;
|
|
3837
|
+
if (n === 3) {
|
|
3838
|
+
if (this[f[r][1]]++, this[f[r][0]] = -f[r][2], this.apply(), r === 0 && !e.starYear) {
|
|
3839
|
+
for (; this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0 && this.year < 1e4;) this.year++;
|
|
3840
|
+
if (this.year >= 1e4 || this.year >= e.year.length) return null;
|
|
3841
|
+
}
|
|
3842
|
+
return this.recurse(e, t, 0);
|
|
3843
|
+
} else if (this.apply()) return this.recurse(e, t, r - 1);
|
|
3844
|
+
}
|
|
3845
|
+
return r += 1, r >= f.length ? this : (e.starYear ? this.year >= 3e3 : this.year >= 1e4) ? null : this.recurse(e, t, r);
|
|
3942
3846
|
}
|
|
3943
|
-
|
|
3944
|
-
|
|
3847
|
+
increment(e, t, r) {
|
|
3848
|
+
return this.second += t.interval !== void 0 && t.interval > 1 && r ? t.interval : 1, this.ms = 0, this.apply(), this.recurse(e, t, 0);
|
|
3945
3849
|
}
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
if (t !== "day" && a) throw new TypeError("CronPattern: Nearest weekday modifier (W) only allowed in day-of-month.");
|
|
3949
|
-
a && (t = "nearestWeekdays");
|
|
3950
|
-
let o = parseInt(i[0], 10) + r;
|
|
3951
|
-
if (isNaN(o)) throw new TypeError("CronPattern: " + t + " is not a number: '" + e + "'");
|
|
3952
|
-
this.setPart(t, o, i[1] || n);
|
|
3850
|
+
decrement(e, t) {
|
|
3851
|
+
return this.second -= t.interval !== void 0 && t.interval > 1 ? t.interval : 1, this.ms = 0, this.apply(), this.recurseBackward(e, t, 0, 0);
|
|
3953
3852
|
}
|
|
3954
|
-
|
|
3955
|
-
if (
|
|
3956
|
-
if (
|
|
3957
|
-
if (
|
|
3958
|
-
|
|
3959
|
-
|
|
3853
|
+
recurseBackward(e, t, r, n = 0) {
|
|
3854
|
+
if (n > 1e4) return null;
|
|
3855
|
+
if (r === 0 && !e.starYear) {
|
|
3856
|
+
if (this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0) {
|
|
3857
|
+
let a = -1;
|
|
3858
|
+
for (let o = this.year - 1; o >= 0; o--) if (e.year[o] === 1) {
|
|
3859
|
+
a = o;
|
|
3860
|
+
break;
|
|
3861
|
+
}
|
|
3862
|
+
if (a === -1) return null;
|
|
3863
|
+
this.year = a, this.month = 11, this.day = 31, this.hour = 23, this.minute = 59, this.second = 59, this.ms = 0;
|
|
3864
|
+
}
|
|
3865
|
+
if (this.year < 0) return null;
|
|
3866
|
+
}
|
|
3867
|
+
let i = this.findPrevious(t, f[r][0], e, f[r][2]);
|
|
3868
|
+
if (i > 1) {
|
|
3869
|
+
let a = r + 1;
|
|
3870
|
+
for (; a < f.length;) {
|
|
3871
|
+
let o = f[a][0], h = f[a][2], l = this.getMaxPatternValue(o, e, h);
|
|
3872
|
+
this[o] = l, a++;
|
|
3873
|
+
}
|
|
3874
|
+
if (i === 3) {
|
|
3875
|
+
if (this[f[r][1]]--, r === 0) {
|
|
3876
|
+
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
3877
|
+
this.day > y && (this.day = y);
|
|
3878
|
+
}
|
|
3879
|
+
if (r === 1) if (this.day <= 0) this.day = 1;
|
|
3880
|
+
else {
|
|
3881
|
+
let y = this.year, u = this.month;
|
|
3882
|
+
for (; u < 0;) u += 12, y--;
|
|
3883
|
+
for (; u > 11;) u -= 12, y++;
|
|
3884
|
+
let d = u !== 1 ? P[u] : new Date(Date.UTC(y, u + 1, 0)).getUTCDate();
|
|
3885
|
+
this.day > d && (this.day = d);
|
|
3886
|
+
}
|
|
3887
|
+
this.apply();
|
|
3888
|
+
let o = f[r][0], h = f[r][2], l = this.getMaxPatternValue(o, e, h);
|
|
3889
|
+
if (o === "day") {
|
|
3890
|
+
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
3891
|
+
this[o] = Math.min(l, y);
|
|
3892
|
+
} else this[o] = l;
|
|
3893
|
+
if (this.apply(), r === 0) {
|
|
3894
|
+
let y = f[1][2], u = this.getMaxPatternValue("day", e, y), d = this.getLastDayOfMonth(this.year, this.month), c = Math.min(u, d);
|
|
3895
|
+
c !== this.day && (this.day = c, this.hour = this.getMaxPatternValue("hour", e, f[2][2]), this.minute = this.getMaxPatternValue("minute", e, f[3][2]), this.second = this.getMaxPatternValue("second", e, f[4][2]));
|
|
3896
|
+
}
|
|
3897
|
+
if (r === 0 && !e.starYear) {
|
|
3898
|
+
for (; this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0;) this.year--;
|
|
3899
|
+
if (this.year < 0) return null;
|
|
3900
|
+
}
|
|
3901
|
+
return this.recurseBackward(e, t, 0, n + 1);
|
|
3902
|
+
} else if (this.apply()) return this.recurseBackward(e, t, r - 1, n + 1);
|
|
3960
3903
|
}
|
|
3961
|
-
|
|
3962
|
-
if (t < 0 || t >= 60) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3963
|
-
} else if (e === "hour") {
|
|
3964
|
-
if (t < 0 || t >= 24) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3965
|
-
} else if (e === "day" || e === "nearestWeekdays") {
|
|
3966
|
-
if (t < 0 || t >= 31) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3967
|
-
} else if (e === "month") {
|
|
3968
|
-
if (t < 0 || t >= 12) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
3969
|
-
} else if (e === "year" && (t < 1 || t >= 1e4)) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t + " (supported range: 1-9999)");
|
|
3970
|
-
this[e][t] = r;
|
|
3971
|
-
}
|
|
3972
|
-
validateNotNaN(e, t) {
|
|
3973
|
-
if (isNaN(e)) throw new TypeError(t);
|
|
3904
|
+
return r += 1, r >= f.length ? this : this.year < 0 ? null : this.recurseBackward(e, t, r, n + 1);
|
|
3974
3905
|
}
|
|
3975
|
-
|
|
3976
|
-
if (e
|
|
3977
|
-
if (
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
}
|
|
3906
|
+
getMaxPatternValue(e, t, r) {
|
|
3907
|
+
if (e === "day" && t.lastDayOfMonth) return this.getLastDayOfMonth(this.year, this.month);
|
|
3908
|
+
if (e === "day" && !t.starDOW) return this.getLastDayOfMonth(this.year, this.month);
|
|
3909
|
+
for (let n = t[e].length - 1; n >= 0; n--) if (t[e][n]) return n - r;
|
|
3910
|
+
return t[e].length - 1 - r;
|
|
3981
3911
|
}
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
let i = this.extractNth(e, t), a = i[0].match(/^(\d+)-(\d+)\/(\d+)$/);
|
|
3985
|
-
if (a === null) throw new TypeError("CronPattern: Syntax error, illegal range with stepping: '" + e + "'");
|
|
3986
|
-
let [, o, h, l] = a, y = parseInt(o, 10) + r, u = parseInt(h, 10) + r, d = parseInt(l, 10);
|
|
3987
|
-
this.validateNotNaN(y, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(u, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateNotNaN(d, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(y, u, d, t, e);
|
|
3988
|
-
for (let c = y; c <= u; c += d) this.setPart(t, c, i[1] || n);
|
|
3912
|
+
findPrevious(e, t, r, n) {
|
|
3913
|
+
return this._findMatch(e, t, r, n, -1);
|
|
3989
3914
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
if (r.includes("#")) {
|
|
3993
|
-
if (t !== "dayOfWeek") throw new Error("CronPattern: nth (#) only allowed in day-of-week field");
|
|
3994
|
-
n = r.split("#")[1], r = r.split("#")[0];
|
|
3995
|
-
} else if (r.toUpperCase().endsWith("L")) {
|
|
3996
|
-
if (t !== "dayOfWeek") throw new Error("CronPattern: L modifier only allowed in day-of-week field (use L alone for day-of-month)");
|
|
3997
|
-
n = "L", r = r.slice(0, -1);
|
|
3998
|
-
}
|
|
3999
|
-
return [r, n];
|
|
3915
|
+
getDate(e) {
|
|
3916
|
+
return e || this.tz === void 0 ? new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms) : typeof this.tz == "number" ? new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute - this.tz, this.second, this.ms)) : k(b(this.year, this.month + 1, this.day, this.hour, this.minute, this.second, this.tz), !1);
|
|
4000
3917
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
let i = this.extractNth(e, t), a = i[0].split("-");
|
|
4004
|
-
if (a.length !== 2) throw new TypeError("CronPattern: Syntax error, illegal range: '" + e + "'");
|
|
4005
|
-
let o = parseInt(a[0], 10) + r, h = parseInt(a[1], 10) + r;
|
|
4006
|
-
this.validateNotNaN(o, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(h, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateRange(o, h, void 0, t, e);
|
|
4007
|
-
for (let l = o; l <= h; l++) this.setPart(t, l, i[1] || n);
|
|
3918
|
+
getTime() {
|
|
3919
|
+
return this.getDate(!1).getTime();
|
|
4008
3920
|
}
|
|
4009
|
-
|
|
4010
|
-
if (e.
|
|
4011
|
-
let
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
if (
|
|
4016
|
-
|
|
3921
|
+
match(e, t) {
|
|
3922
|
+
if (!e.starYear && (this.year < 0 || this.year >= e.year.length || e.year[this.year] === 0)) return !1;
|
|
3923
|
+
for (let r = 0; r < f.length; r++) {
|
|
3924
|
+
let n = f[r][0], i = f[r][2], a = this[n];
|
|
3925
|
+
if (a + i < 0 || a + i >= e[n].length) return !1;
|
|
3926
|
+
let o = e[n][a + i];
|
|
3927
|
+
if (n === "day") {
|
|
3928
|
+
if (!o) {
|
|
3929
|
+
for (let h = 0; h < e.nearestWeekdays.length; h++) if (e.nearestWeekdays[h]) {
|
|
3930
|
+
let l = this.getNearestWeekday(this.year, this.month, h - i);
|
|
3931
|
+
if (l !== -1 && l === a) {
|
|
3932
|
+
o = 1;
|
|
3933
|
+
break;
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
if (e.lastWeekday) a === this.getLastWeekday(this.year, this.month) && (o = 1);
|
|
3938
|
+
if (e.lastDayOfMonth) a === this.getLastDayOfMonth(this.year, this.month) && (o = 1);
|
|
3939
|
+
if (!e.starDOW) {
|
|
3940
|
+
let h = new Date(Date.UTC(this.year, this.month, 1, 0, 0, 0, 0)).getUTCDay(), l = e.dayOfWeek[(h + (a - 1)) % 7];
|
|
3941
|
+
l && l & 63 && (l = this.isNthWeekdayOfMonth(this.year, this.month, a, l) ? 1 : 0), e.useAndLogic ? o = o && l : !t.domAndDow && !e.starDOM ? o = o || l : o = o && l;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
if (!o) return !1;
|
|
4017
3945
|
}
|
|
4018
|
-
|
|
4019
|
-
a[0] !== "*" && (o = parseInt(a[0], 10) + r);
|
|
4020
|
-
let h = parseInt(a[1], 10);
|
|
4021
|
-
this.validateNotNaN(h, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(0, this[t].length - 1, h, t, e);
|
|
4022
|
-
for (let l = o; l < this[t].length; l += h) this.setPart(t, l, i[1] || n);
|
|
3946
|
+
return !0;
|
|
4023
3947
|
}
|
|
4024
|
-
|
|
4025
|
-
|
|
3948
|
+
};
|
|
3949
|
+
function R(s) {
|
|
3950
|
+
if (s === void 0 && (s = {}), delete s.name, s.legacyMode !== void 0 && s.domAndDow === void 0 ? s.domAndDow = !s.legacyMode : s.domAndDow === void 0 && (s.domAndDow = !1), s.legacyMode = !s.domAndDow, s.paused = s.paused === void 0 ? !1 : s.paused, s.maxRuns = s.maxRuns === void 0 ? Infinity : s.maxRuns, s.catch = s.catch === void 0 ? !1 : s.catch, s.interval = s.interval === void 0 ? 0 : parseInt(s.interval.toString(), 10), s.utcOffset = s.utcOffset === void 0 ? void 0 : parseInt(s.utcOffset.toString(), 10), s.dayOffset = s.dayOffset === void 0 ? 0 : parseInt(s.dayOffset.toString(), 10), s.unref = s.unref === void 0 ? !1 : s.unref, s.mode = s.mode === void 0 ? "auto" : s.mode, s.alternativeWeekdays = s.alternativeWeekdays === void 0 ? !1 : s.alternativeWeekdays, s.sloppyRanges = s.sloppyRanges === void 0 ? !1 : s.sloppyRanges, ![
|
|
3951
|
+
"auto",
|
|
3952
|
+
"5-part",
|
|
3953
|
+
"6-part",
|
|
3954
|
+
"7-part",
|
|
3955
|
+
"5-or-6-parts",
|
|
3956
|
+
"6-or-7-parts"
|
|
3957
|
+
].includes(s.mode)) throw new Error("CronOptions: mode must be one of 'auto', '5-part', '6-part', '7-part', '5-or-6-parts', or '6-or-7-parts'.");
|
|
3958
|
+
if (s.startAt && (s.startAt = new m(s.startAt, s.timezone)), s.stopAt && (s.stopAt = new m(s.stopAt, s.timezone)), s.interval !== null) {
|
|
3959
|
+
if (isNaN(s.interval)) throw new Error("CronOptions: Supplied value for interval is not a number");
|
|
3960
|
+
if (s.interval < 0) throw new Error("CronOptions: Supplied value for interval can not be negative");
|
|
4026
3961
|
}
|
|
4027
|
-
|
|
4028
|
-
|
|
3962
|
+
if (s.utcOffset !== void 0) {
|
|
3963
|
+
if (isNaN(s.utcOffset)) throw new Error("CronOptions: Invalid value passed for utcOffset, should be number representing minutes offset from UTC.");
|
|
3964
|
+
if (s.utcOffset < -870 || s.utcOffset > 870) throw new Error("CronOptions: utcOffset out of bounds.");
|
|
3965
|
+
if (s.utcOffset !== void 0 && s.timezone) throw new Error("CronOptions: Combining 'utcOffset' with 'timezone' is not allowed.");
|
|
4029
3966
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
3967
|
+
if (s.unref !== !0 && s.unref !== !1) throw new Error("CronOptions: Unref should be either true, false or undefined(false).");
|
|
3968
|
+
if (s.dayOffset !== void 0 && s.dayOffset !== 0 && isNaN(s.dayOffset)) throw new Error("CronOptions: Invalid value passed for dayOffset, should be a number representing days to offset.");
|
|
3969
|
+
return s;
|
|
3970
|
+
}
|
|
3971
|
+
function p(s) {
|
|
3972
|
+
return Object.prototype.toString.call(s) === "[object Function]" || typeof s == "function" || s instanceof Function;
|
|
3973
|
+
}
|
|
3974
|
+
function _(s) {
|
|
3975
|
+
return p(s);
|
|
3976
|
+
}
|
|
3977
|
+
function x(s) {
|
|
3978
|
+
typeof Deno < "u" && typeof Deno.unrefTimer < "u" ? Deno.unrefTimer(s) : s && typeof s.unref < "u" && s.unref();
|
|
3979
|
+
}
|
|
3980
|
+
var W = 30 * 1e3, w = [], E = class {
|
|
3981
|
+
name;
|
|
3982
|
+
options;
|
|
3983
|
+
_states;
|
|
3984
|
+
fn;
|
|
3985
|
+
getTz() {
|
|
3986
|
+
return this.options.timezone || this.options.utcOffset;
|
|
4032
3987
|
}
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
if (t === "@daily" || t === "@midnight") return "0 0 * * *";
|
|
4039
|
-
if (t === "@hourly") return "0 * * * *";
|
|
4040
|
-
if (t === "@reboot") throw new TypeError("CronPattern: @reboot is not supported in this environment. This is an event-based trigger that requires system startup detection.");
|
|
3988
|
+
applyDayOffset(e) {
|
|
3989
|
+
if (this.options.dayOffset !== void 0 && this.options.dayOffset !== 0) {
|
|
3990
|
+
let t = this.options.dayOffset * 24 * 60 * 60 * 1e3;
|
|
3991
|
+
return new Date(e.getTime() + t);
|
|
3992
|
+
}
|
|
4041
3993
|
return e;
|
|
4042
3994
|
}
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
else if (
|
|
4047
|
-
else throw new
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
"day",
|
|
4071
|
-
"month",
|
|
4072
|
-
-1
|
|
4073
|
-
],
|
|
4074
|
-
[
|
|
4075
|
-
"hour",
|
|
4076
|
-
"day",
|
|
4077
|
-
0
|
|
4078
|
-
],
|
|
4079
|
-
[
|
|
4080
|
-
"minute",
|
|
4081
|
-
"hour",
|
|
4082
|
-
0
|
|
4083
|
-
],
|
|
4084
|
-
[
|
|
4085
|
-
"second",
|
|
4086
|
-
"minute",
|
|
4087
|
-
0
|
|
4088
|
-
]
|
|
4089
|
-
], m = class s {
|
|
4090
|
-
tz;
|
|
4091
|
-
ms;
|
|
4092
|
-
second;
|
|
4093
|
-
minute;
|
|
4094
|
-
hour;
|
|
4095
|
-
day;
|
|
4096
|
-
month;
|
|
4097
|
-
year;
|
|
4098
|
-
constructor(e, t) {
|
|
4099
|
-
if (this.tz = t, e && e instanceof Date) if (!isNaN(e)) this.fromDate(e);
|
|
4100
|
-
else throw new TypeError("CronDate: Invalid date passed to CronDate constructor");
|
|
4101
|
-
else if (e == null) this.fromDate(/* @__PURE__ */ new Date());
|
|
4102
|
-
else if (e && typeof e == "string") this.fromString(e);
|
|
4103
|
-
else if (e instanceof s) this.fromCronDate(e);
|
|
4104
|
-
else throw new TypeError("CronDate: Invalid type (" + typeof e + ") passed to CronDate constructor");
|
|
3995
|
+
constructor(e, t, r) {
|
|
3996
|
+
let n, i;
|
|
3997
|
+
if (p(t)) i = t;
|
|
3998
|
+
else if (typeof t == "object") n = t;
|
|
3999
|
+
else if (t !== void 0) throw new Error("Cron: Invalid argument passed for optionsIn. Should be one of function, or object (options).");
|
|
4000
|
+
if (p(r)) i = r;
|
|
4001
|
+
else if (typeof r == "object") n = r;
|
|
4002
|
+
else if (r !== void 0) throw new Error("Cron: Invalid argument passed for funcIn. Should be one of function, or object (options).");
|
|
4003
|
+
if (this.name = n?.name, this.options = R(n), this._states = {
|
|
4004
|
+
kill: !1,
|
|
4005
|
+
blocking: !1,
|
|
4006
|
+
previousRun: void 0,
|
|
4007
|
+
currentRun: void 0,
|
|
4008
|
+
once: void 0,
|
|
4009
|
+
currentTimeout: void 0,
|
|
4010
|
+
maxRuns: n ? n.maxRuns : void 0,
|
|
4011
|
+
paused: n ? n.paused : !1,
|
|
4012
|
+
pattern: new C("* * * * *", void 0, { mode: "auto" })
|
|
4013
|
+
}, e && (e instanceof Date || typeof e == "string" && e.indexOf(":") > 0) ? this._states.once = new m(e, this.getTz()) : this._states.pattern = new C(e, this.options.timezone, {
|
|
4014
|
+
mode: this.options.mode,
|
|
4015
|
+
alternativeWeekdays: this.options.alternativeWeekdays,
|
|
4016
|
+
sloppyRanges: this.options.sloppyRanges
|
|
4017
|
+
}), this.name) {
|
|
4018
|
+
if (w.find((o) => o.name === this.name)) throw new Error("Cron: Tried to initialize new named job '" + this.name + "', but name already taken.");
|
|
4019
|
+
w.push(this);
|
|
4020
|
+
}
|
|
4021
|
+
return i !== void 0 && _(i) && (this.fn = i, this.schedule()), this;
|
|
4105
4022
|
}
|
|
4106
|
-
|
|
4107
|
-
|
|
4023
|
+
nextRun(e) {
|
|
4024
|
+
let t = this._next(e);
|
|
4025
|
+
return t ? this.applyDayOffset(t.getDate(!1)) : null;
|
|
4108
4026
|
}
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4027
|
+
nextRuns(e, t) {
|
|
4028
|
+
this._states.maxRuns !== void 0 && e > this._states.maxRuns && (e = this._states.maxRuns);
|
|
4029
|
+
let r = t || this._states.currentRun || void 0;
|
|
4030
|
+
return this._enumerateRuns(e, r, "next");
|
|
4112
4031
|
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
if (r > n) return -1;
|
|
4116
|
-
let a = new Date(Date.UTC(e, t, r)).getUTCDay();
|
|
4117
|
-
return a === 0 ? r === n ? r - 2 : r + 1 : a === 6 ? r === 1 ? r + 2 : r - 1 : r;
|
|
4032
|
+
previousRuns(e, t) {
|
|
4033
|
+
return this._enumerateRuns(e, t || void 0, "previous");
|
|
4118
4034
|
}
|
|
4119
|
-
|
|
4120
|
-
let
|
|
4121
|
-
for (
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
let h =
|
|
4125
|
-
|
|
4126
|
-
return !0;
|
|
4035
|
+
_enumerateRuns(e, t, r) {
|
|
4036
|
+
let n = [], i = t ? new m(t, this.getTz()) : null, a = r === "next" ? this._next : this._previous;
|
|
4037
|
+
for (; e--;) {
|
|
4038
|
+
let o = a.call(this, i);
|
|
4039
|
+
if (!o) break;
|
|
4040
|
+
let h = o.getDate(!1);
|
|
4041
|
+
n.push(this.applyDayOffset(h)), i = o;
|
|
4127
4042
|
}
|
|
4128
|
-
return
|
|
4043
|
+
return n;
|
|
4129
4044
|
}
|
|
4130
|
-
|
|
4131
|
-
if (this.
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
let r = t instanceof Error ? t.message : String(t);
|
|
4137
|
-
throw new TypeError(`CronDate: Failed to convert date to timezone '${this.tz}'. This may happen with invalid timezone names or dates. Original error: ${r}`);
|
|
4045
|
+
match(e) {
|
|
4046
|
+
if (this._states.once) {
|
|
4047
|
+
let r = new m(e, this.getTz());
|
|
4048
|
+
r.ms = 0;
|
|
4049
|
+
let n = new m(this._states.once, this.getTz());
|
|
4050
|
+
return n.ms = 0, r.getTime() === n.getTime();
|
|
4138
4051
|
}
|
|
4139
|
-
|
|
4052
|
+
let t = new m(e, this.getTz());
|
|
4053
|
+
return t.ms = 0, t.match(this._states.pattern, this.options);
|
|
4140
4054
|
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4055
|
+
getPattern() {
|
|
4056
|
+
if (!this._states.once) return this._states.pattern ? this._states.pattern.pattern : void 0;
|
|
4143
4057
|
}
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
let e = new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms));
|
|
4147
|
-
return this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes(), this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), !0;
|
|
4148
|
-
} else return !1;
|
|
4058
|
+
getOnce() {
|
|
4059
|
+
return this._states.once ? this._states.once.getDate() : null;
|
|
4149
4060
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
this.ms = t.getUTCMilliseconds(), this.second = t.getUTCSeconds(), this.minute = t.getUTCMinutes(), this.hour = t.getUTCHours(), this.day = t.getUTCDate(), this.month = t.getUTCMonth(), this.year = t.getUTCFullYear(), this.apply();
|
|
4154
|
-
} else return this.fromDate(v(e, this.tz));
|
|
4061
|
+
isRunning() {
|
|
4062
|
+
let e = this.nextRun(this._states.currentRun), t = !this._states.paused, r = this.fn !== void 0, n = !this._states.kill;
|
|
4063
|
+
return t && r && n && e !== null;
|
|
4155
4064
|
}
|
|
4156
|
-
|
|
4157
|
-
return this.
|
|
4065
|
+
isStopped() {
|
|
4066
|
+
return this._states.kill;
|
|
4158
4067
|
}
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4068
|
+
isBusy() {
|
|
4069
|
+
return this._states.blocking;
|
|
4070
|
+
}
|
|
4071
|
+
currentRun() {
|
|
4072
|
+
return this._states.currentRun ? this._states.currentRun.getDate() : null;
|
|
4073
|
+
}
|
|
4074
|
+
previousRun() {
|
|
4075
|
+
return this._states.previousRun ? this._states.previousRun.getDate() : null;
|
|
4076
|
+
}
|
|
4077
|
+
msToNext(e) {
|
|
4078
|
+
let t = this._next(e);
|
|
4079
|
+
return t ? e instanceof m || e instanceof Date ? t.getTime() - e.getTime() : t.getTime() - new m(e).getTime() : null;
|
|
4080
|
+
}
|
|
4081
|
+
stop() {
|
|
4082
|
+
this._states.kill = !0, this._states.currentTimeout && clearTimeout(this._states.currentTimeout);
|
|
4083
|
+
let e = w.indexOf(this);
|
|
4084
|
+
e >= 0 && w.splice(e, 1);
|
|
4085
|
+
}
|
|
4086
|
+
pause() {
|
|
4087
|
+
return this._states.paused = !0, !this._states.kill;
|
|
4088
|
+
}
|
|
4089
|
+
resume() {
|
|
4090
|
+
return this._states.paused = !1, !this._states.kill;
|
|
4091
|
+
}
|
|
4092
|
+
schedule(e) {
|
|
4093
|
+
if (e && this.fn) throw new Error("Cron: It is not allowed to schedule two functions using the same Croner instance.");
|
|
4094
|
+
e && (this.fn = e);
|
|
4095
|
+
let t = this.msToNext(), r = this.nextRun(this._states.currentRun);
|
|
4096
|
+
return t == null || isNaN(t) || r === null ? this : (t > W && (t = W), this._states.currentTimeout = setTimeout(() => this._checkTrigger(r), t), this._states.currentTimeout && this.options.unref && x(this._states.currentTimeout), this);
|
|
4097
|
+
}
|
|
4098
|
+
async _trigger(e) {
|
|
4099
|
+
this._states.blocking = !0, this._states.currentRun = new m(void 0, this.getTz());
|
|
4100
|
+
try {
|
|
4101
|
+
if (this.options.catch) try {
|
|
4102
|
+
this.fn !== void 0 && await this.fn(this, this.options.context);
|
|
4103
|
+
} catch (t) {
|
|
4104
|
+
if (p(this.options.catch)) try {
|
|
4105
|
+
this.options.catch(t, this);
|
|
4106
|
+
} catch {}
|
|
4184
4107
|
}
|
|
4185
|
-
|
|
4108
|
+
else this.fn !== void 0 && await this.fn(this, this.options.context);
|
|
4109
|
+
} finally {
|
|
4110
|
+
this._states.previousRun = new m(e, this.getTz()), this._states.blocking = !1;
|
|
4186
4111
|
}
|
|
4187
|
-
return 3;
|
|
4188
4112
|
}
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
let n = this.
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4113
|
+
async trigger() {
|
|
4114
|
+
await this._trigger();
|
|
4115
|
+
}
|
|
4116
|
+
runsLeft() {
|
|
4117
|
+
return this._states.maxRuns;
|
|
4118
|
+
}
|
|
4119
|
+
_checkTrigger(e) {
|
|
4120
|
+
let t = /* @__PURE__ */ new Date(), r = !this._states.paused && t.getTime() >= e.getTime(), n = this._states.blocking && this.options.protect;
|
|
4121
|
+
r && !n ? (this._states.maxRuns !== void 0 && this._states.maxRuns--, this._trigger()) : r && n && p(this.options.protect) && setTimeout(() => this.options.protect(this), 0), this.schedule();
|
|
4122
|
+
}
|
|
4123
|
+
_next(e) {
|
|
4124
|
+
let t = !!(e || this._states.currentRun), r = !1;
|
|
4125
|
+
!e && this.options.startAt && this.options.interval && ([e, t] = this._calculatePreviousRun(e, t), r = !e), e = new m(e, this.getTz()), this.options.startAt && e && e.getTime() < this.options.startAt.getTime() && (e = this.options.startAt);
|
|
4126
|
+
let n = this._states.once || new m(e, this.getTz());
|
|
4127
|
+
return !r && n !== this._states.once && (n = n.increment(this._states.pattern, this.options, t)), this._states.once && this._states.once.getTime() <= e.getTime() || n === null || this._states.maxRuns !== void 0 && this._states.maxRuns <= 0 || this._states.kill || this.options.stopAt && n.getTime() >= this.options.stopAt.getTime() ? null : n;
|
|
4128
|
+
}
|
|
4129
|
+
_previous(e) {
|
|
4130
|
+
let t = new m(e, this.getTz());
|
|
4131
|
+
this.options.stopAt && t.getTime() > this.options.stopAt.getTime() && (t = this.options.stopAt);
|
|
4132
|
+
let r = new m(t, this.getTz());
|
|
4133
|
+
return this._states.once ? this._states.once.getTime() < t.getTime() ? this._states.once : null : (r = r.decrement(this._states.pattern, this.options), r === null || this.options.startAt && r.getTime() < this.options.startAt.getTime() ? null : r);
|
|
4134
|
+
}
|
|
4135
|
+
_calculatePreviousRun(e, t) {
|
|
4136
|
+
let r = new m(void 0, this.getTz()), n = e;
|
|
4137
|
+
if (this.options.startAt.getTime() <= r.getTime()) {
|
|
4138
|
+
n = this.options.startAt;
|
|
4139
|
+
let i = n.getTime() + this.options.interval * 1e3;
|
|
4140
|
+
for (; i <= r.getTime();) n = new m(n, this.getTz()).increment(this._states.pattern, this.options, !0), i = n.getTime() + this.options.interval * 1e3;
|
|
4141
|
+
t = !0;
|
|
4142
|
+
}
|
|
4143
|
+
return n === null && (n = void 0), [n, t];
|
|
4144
|
+
}
|
|
4145
|
+
};
|
|
4146
|
+
//#endregion
|
|
4147
|
+
//#region src/builtins/backup-orchestrator/cron-helpers.ts
|
|
4148
|
+
/**
|
|
4149
|
+
* Cron helpers — wraps `croner` so the orchestrator + the `previewNextRuns`
|
|
4150
|
+
* cap method share one validation + next-run-computation surface.
|
|
4151
|
+
*
|
|
4152
|
+
* `croner` is the opinionated node cron library we standardize on
|
|
4153
|
+
* server-side: pure JS, native TypeScript types, supports the full
|
|
4154
|
+
* 5-field POSIX cron grammar plus seconds (6-field), DST-aware, and
|
|
4155
|
+
* exposes `nextRun()` / `nextRuns(N)` for peeking without scheduling.
|
|
4156
|
+
* The orchestrator uses the peeking API (it has its own polling
|
|
4157
|
+
* loop); future surfaces (e.g. event-driven retention sweeps) can
|
|
4158
|
+
* register an actual scheduled `Cron(pattern, fn)` and let croner
|
|
4159
|
+
* own the timer.
|
|
4160
|
+
*/
|
|
4161
|
+
/**
|
|
4162
|
+
* Validate a cron expression and return its next firing time strictly
|
|
4163
|
+
* after `after`, expressed as ms-epoch. Returns `null` when the
|
|
4164
|
+
* expression has no future runs (croner returns null e.g. for
|
|
4165
|
+
* fully-bounded patterns that have already elapsed). Throws when the
|
|
4166
|
+
* expression is syntactically invalid — callers are expected to
|
|
4167
|
+
* surface that to the operator.
|
|
4168
|
+
*/
|
|
4169
|
+
function computeNextDue(cron, after) {
|
|
4170
|
+
const next = new E(cron, { paused: true }).nextRun(new Date(after));
|
|
4171
|
+
return next ? next.getTime() : null;
|
|
4172
|
+
}
|
|
4173
|
+
/**
|
|
4174
|
+
* Validate without computing — returns true when `cron` parses, false
|
|
4175
|
+
* otherwise. Used by the cap-router input zod schema's `.refine`
|
|
4176
|
+
* callback to reject malformed expressions at the boundary instead
|
|
4177
|
+
* of at scheduler-tick time.
|
|
4178
|
+
*/
|
|
4179
|
+
function isValidCron(cron) {
|
|
4180
|
+
try {
|
|
4181
|
+
new E(cron, { paused: true });
|
|
4182
|
+
return true;
|
|
4183
|
+
} catch {
|
|
4184
|
+
return false;
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
/**
|
|
4188
|
+
* Peek the next N firing times after `after` (default: now). Used by
|
|
4189
|
+
* the admin UI's CronEditor preview pane — operators see the next
|
|
4190
|
+
* 3–5 runs as they edit the expression so a wrong pattern is
|
|
4191
|
+
* obvious before save.
|
|
4192
|
+
*/
|
|
4193
|
+
function previewNextRuns(cron, count, after) {
|
|
4194
|
+
const job = new E(cron, { paused: true });
|
|
4195
|
+
const baseline = after !== void 0 ? new Date(after) : /* @__PURE__ */ new Date();
|
|
4196
|
+
const out = [];
|
|
4197
|
+
let cursor = baseline;
|
|
4198
|
+
for (let i = 0; i < count; i++) {
|
|
4199
|
+
cursor = job.nextRun(cursor);
|
|
4200
|
+
if (!cursor) break;
|
|
4201
|
+
out.push(cursor.getTime());
|
|
4202
|
+
}
|
|
4203
|
+
return out;
|
|
4204
|
+
}
|
|
4205
|
+
//#endregion
|
|
4206
|
+
//#region src/builtins/backup-orchestrator/destination-policy.ts
|
|
4207
|
+
var TABLE_NAME$1 = "backup_destination_policies";
|
|
4208
|
+
var BACKUP_DESTINATION_POLICIES_SCHEMA = { columns: [
|
|
4209
|
+
{
|
|
4210
|
+
name: "location_id",
|
|
4211
|
+
type: "TEXT",
|
|
4212
|
+
primaryKey: true
|
|
4213
|
+
},
|
|
4214
|
+
{
|
|
4215
|
+
name: "enabled",
|
|
4216
|
+
type: "INTEGER",
|
|
4217
|
+
notNull: true,
|
|
4218
|
+
defaultValue: 1
|
|
4219
|
+
},
|
|
4220
|
+
{
|
|
4221
|
+
name: "retention_count",
|
|
4222
|
+
type: "INTEGER",
|
|
4223
|
+
notNull: true,
|
|
4224
|
+
defaultValue: 7
|
|
4225
|
+
},
|
|
4226
|
+
{
|
|
4227
|
+
name: "label",
|
|
4228
|
+
type: "TEXT"
|
|
4229
|
+
},
|
|
4230
|
+
{
|
|
4231
|
+
name: "cron",
|
|
4232
|
+
type: "TEXT"
|
|
4233
|
+
},
|
|
4234
|
+
{
|
|
4235
|
+
name: "last_run_at",
|
|
4236
|
+
type: "INTEGER"
|
|
4237
|
+
}
|
|
4238
|
+
] };
|
|
4239
|
+
var BackupDestinationPolicyService = class {
|
|
4240
|
+
backend;
|
|
4241
|
+
tableEnsured = false;
|
|
4242
|
+
constructor(backend) {
|
|
4243
|
+
this.backend = backend;
|
|
4215
4244
|
}
|
|
4216
|
-
|
|
4217
|
-
|
|
4245
|
+
/**
|
|
4246
|
+
* Lazy `ensureTable` — called from every read/write path. The
|
|
4247
|
+
* settings backend's own `ensureTable` is idempotent (additive
|
|
4248
|
+
* migration), so repeated calls collapse to a no-op after the first.
|
|
4249
|
+
* This keeps the constructor synchronous and avoids a separate
|
|
4250
|
+
* `initialize()` step that callers could forget.
|
|
4251
|
+
*/
|
|
4252
|
+
async initialize() {
|
|
4253
|
+
await this.ensureTable();
|
|
4218
4254
|
}
|
|
4219
|
-
|
|
4220
|
-
|
|
4255
|
+
async ensureTable() {
|
|
4256
|
+
if (this.tableEnsured) return;
|
|
4257
|
+
if (!this.backend.ensureTable) throw new Error("BackupDestinationPolicyService: settings backend does not implement ensureTable — expected the SQLite settings backend, got a backend without structured-table support");
|
|
4258
|
+
await this.backend.ensureTable(TABLE_NAME$1, BACKUP_DESTINATION_POLICIES_SCHEMA);
|
|
4259
|
+
this.tableEnsured = true;
|
|
4221
4260
|
}
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
if (
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
}
|
|
4234
|
-
if (this.year < 0) return null;
|
|
4235
|
-
}
|
|
4236
|
-
let i = this.findPrevious(t, f[r][0], e, f[r][2]);
|
|
4237
|
-
if (i > 1) {
|
|
4238
|
-
let a = r + 1;
|
|
4239
|
-
for (; a < f.length;) {
|
|
4240
|
-
let o = f[a][0], h = f[a][2], l = this.getMaxPatternValue(o, e, h);
|
|
4241
|
-
this[o] = l, a++;
|
|
4242
|
-
}
|
|
4243
|
-
if (i === 3) {
|
|
4244
|
-
if (this[f[r][1]]--, r === 0) {
|
|
4245
|
-
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
4246
|
-
this.day > y && (this.day = y);
|
|
4247
|
-
}
|
|
4248
|
-
if (r === 1) if (this.day <= 0) this.day = 1;
|
|
4249
|
-
else {
|
|
4250
|
-
let y = this.year, u = this.month;
|
|
4251
|
-
for (; u < 0;) u += 12, y--;
|
|
4252
|
-
for (; u > 11;) u -= 12, y++;
|
|
4253
|
-
let d = u !== 1 ? P[u] : new Date(Date.UTC(y, u + 1, 0)).getUTCDate();
|
|
4254
|
-
this.day > d && (this.day = d);
|
|
4255
|
-
}
|
|
4256
|
-
this.apply();
|
|
4257
|
-
let o = f[r][0], h = f[r][2], l = this.getMaxPatternValue(o, e, h);
|
|
4258
|
-
if (o === "day") {
|
|
4259
|
-
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
4260
|
-
this[o] = Math.min(l, y);
|
|
4261
|
-
} else this[o] = l;
|
|
4262
|
-
if (this.apply(), r === 0) {
|
|
4263
|
-
let y = f[1][2], u = this.getMaxPatternValue("day", e, y), d = this.getLastDayOfMonth(this.year, this.month), c = Math.min(u, d);
|
|
4264
|
-
c !== this.day && (this.day = c, this.hour = this.getMaxPatternValue("hour", e, f[2][2]), this.minute = this.getMaxPatternValue("minute", e, f[3][2]), this.second = this.getMaxPatternValue("second", e, f[4][2]));
|
|
4265
|
-
}
|
|
4266
|
-
if (r === 0 && !e.starYear) {
|
|
4267
|
-
for (; this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0;) this.year--;
|
|
4268
|
-
if (this.year < 0) return null;
|
|
4269
|
-
}
|
|
4270
|
-
return this.recurseBackward(e, t, 0, n + 1);
|
|
4271
|
-
} else if (this.apply()) return this.recurseBackward(e, t, r - 1, n + 1);
|
|
4261
|
+
async list() {
|
|
4262
|
+
await this.ensureTable();
|
|
4263
|
+
if (!this.backend.tableQuery) return [];
|
|
4264
|
+
const rows = await this.backend.tableQuery(TABLE_NAME$1, { orderBy: {
|
|
4265
|
+
field: "location_id",
|
|
4266
|
+
direction: "asc"
|
|
4267
|
+
} });
|
|
4268
|
+
const out = [];
|
|
4269
|
+
for (const row of rows) {
|
|
4270
|
+
const mapped = mapRowToPolicy(row);
|
|
4271
|
+
if (mapped) out.push(mapped);
|
|
4272
4272
|
}
|
|
4273
|
-
return
|
|
4274
|
-
}
|
|
4275
|
-
getMaxPatternValue(e, t, r) {
|
|
4276
|
-
if (e === "day" && t.lastDayOfMonth) return this.getLastDayOfMonth(this.year, this.month);
|
|
4277
|
-
if (e === "day" && !t.starDOW) return this.getLastDayOfMonth(this.year, this.month);
|
|
4278
|
-
for (let n = t[e].length - 1; n >= 0; n--) if (t[e][n]) return n - r;
|
|
4279
|
-
return t[e].length - 1 - r;
|
|
4273
|
+
return out;
|
|
4280
4274
|
}
|
|
4281
|
-
|
|
4282
|
-
|
|
4275
|
+
async get(locationId) {
|
|
4276
|
+
await this.ensureTable();
|
|
4277
|
+
if (!this.backend.tableGet) return null;
|
|
4278
|
+
const row = await this.backend.tableGet(TABLE_NAME$1, { location_id: locationId });
|
|
4279
|
+
return row ? mapRowToPolicy(row) : null;
|
|
4283
4280
|
}
|
|
4284
|
-
|
|
4285
|
-
|
|
4281
|
+
/**
|
|
4282
|
+
* Replace-or-insert. Implemented as `delete` + `insert` (mirrors the
|
|
4283
|
+
* `SqliteLocationStore` contract) so backends that don't surface
|
|
4284
|
+
* `tableUpdate` still work.
|
|
4285
|
+
*/
|
|
4286
|
+
async upsert(p) {
|
|
4287
|
+
await this.ensureTable();
|
|
4288
|
+
if (!this.backend.tableDelete || !this.backend.tableInsert) throw new Error("BackupDestinationPolicyService: backend missing tableDelete/tableInsert");
|
|
4289
|
+
await this.backend.tableDelete(TABLE_NAME$1, { location_id: p.locationId });
|
|
4290
|
+
await this.backend.tableInsert(TABLE_NAME$1, mapPolicyToRow(p));
|
|
4286
4291
|
}
|
|
4287
|
-
|
|
4288
|
-
|
|
4292
|
+
async delete(locationId) {
|
|
4293
|
+
await this.ensureTable();
|
|
4294
|
+
if (!this.backend.tableDelete) throw new Error("BackupDestinationPolicyService: backend missing tableDelete");
|
|
4295
|
+
await this.backend.tableDelete(TABLE_NAME$1, { location_id: locationId });
|
|
4289
4296
|
}
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
if (!o) return !1;
|
|
4314
|
-
}
|
|
4315
|
-
return !0;
|
|
4297
|
+
/**
|
|
4298
|
+
* Auto-create a default policy for a `backups` location seen for the
|
|
4299
|
+
* first time (returns the existing policy unchanged when present).
|
|
4300
|
+
* The orchestrator calls this lazily on the first `listDestinations`
|
|
4301
|
+
* pass so newly-added storage locations are surfaced as enabled
|
|
4302
|
+
* destinations without an explicit configuration step.
|
|
4303
|
+
*
|
|
4304
|
+
* Defaults are passed by the orchestrator (currently:
|
|
4305
|
+
* `retentionCount=7` + `cron='0 3 * * *'`) so a fresh destination
|
|
4306
|
+
* starts running daily at 03:00 with 7 archives kept. Operator can
|
|
4307
|
+
* tune both inline on the destinations table.
|
|
4308
|
+
*/
|
|
4309
|
+
async ensureDefault(locationId, defaults) {
|
|
4310
|
+
const existing = await this.get(locationId);
|
|
4311
|
+
if (existing) return existing;
|
|
4312
|
+
const fresh = {
|
|
4313
|
+
locationId,
|
|
4314
|
+
enabled: true,
|
|
4315
|
+
retentionCount: defaults.retentionCount,
|
|
4316
|
+
...defaults.cron !== void 0 ? { cron: defaults.cron } : {}
|
|
4317
|
+
};
|
|
4318
|
+
await this.upsert(fresh);
|
|
4319
|
+
return fresh;
|
|
4316
4320
|
}
|
|
4317
4321
|
};
|
|
4318
|
-
function
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
if (s.startAt && (s.startAt = new m(s.startAt, s.timezone)), s.stopAt && (s.stopAt = new m(s.stopAt, s.timezone)), s.interval !== null) {
|
|
4328
|
-
if (isNaN(s.interval)) throw new Error("CronOptions: Supplied value for interval is not a number");
|
|
4329
|
-
if (s.interval < 0) throw new Error("CronOptions: Supplied value for interval can not be negative");
|
|
4330
|
-
}
|
|
4331
|
-
if (s.utcOffset !== void 0) {
|
|
4332
|
-
if (isNaN(s.utcOffset)) throw new Error("CronOptions: Invalid value passed for utcOffset, should be number representing minutes offset from UTC.");
|
|
4333
|
-
if (s.utcOffset < -870 || s.utcOffset > 870) throw new Error("CronOptions: utcOffset out of bounds.");
|
|
4334
|
-
if (s.utcOffset !== void 0 && s.timezone) throw new Error("CronOptions: Combining 'utcOffset' with 'timezone' is not allowed.");
|
|
4335
|
-
}
|
|
4336
|
-
if (s.unref !== !0 && s.unref !== !1) throw new Error("CronOptions: Unref should be either true, false or undefined(false).");
|
|
4337
|
-
if (s.dayOffset !== void 0 && s.dayOffset !== 0 && isNaN(s.dayOffset)) throw new Error("CronOptions: Invalid value passed for dayOffset, should be a number representing days to offset.");
|
|
4338
|
-
return s;
|
|
4339
|
-
}
|
|
4340
|
-
function p(s) {
|
|
4341
|
-
return Object.prototype.toString.call(s) === "[object Function]" || typeof s == "function" || s instanceof Function;
|
|
4342
|
-
}
|
|
4343
|
-
function _(s) {
|
|
4344
|
-
return p(s);
|
|
4322
|
+
function mapPolicyToRow(p) {
|
|
4323
|
+
return {
|
|
4324
|
+
location_id: p.locationId,
|
|
4325
|
+
enabled: p.enabled ? 1 : 0,
|
|
4326
|
+
retention_count: p.retentionCount,
|
|
4327
|
+
label: p.label ?? null,
|
|
4328
|
+
cron: p.cron ?? null,
|
|
4329
|
+
last_run_at: p.lastRunAt ?? null
|
|
4330
|
+
};
|
|
4345
4331
|
}
|
|
4346
|
-
|
|
4347
|
-
|
|
4332
|
+
/**
|
|
4333
|
+
* Decode a row pulled from the structured table. Defensive against
|
|
4334
|
+
* schema drift / partial migrations: returns `null` on missing
|
|
4335
|
+
* `location_id` so corrupt rows skip the result list rather than
|
|
4336
|
+
* throwing the whole orchestrator down at boot.
|
|
4337
|
+
*/
|
|
4338
|
+
function mapRowToPolicy(row) {
|
|
4339
|
+
const locationId = String(row["location_id"] ?? "");
|
|
4340
|
+
if (!locationId) return null;
|
|
4341
|
+
const labelRaw = row["label"];
|
|
4342
|
+
const label = typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : void 0;
|
|
4343
|
+
const cronRaw = row["cron"];
|
|
4344
|
+
const cron = typeof cronRaw === "string" && cronRaw.length > 0 ? cronRaw : void 0;
|
|
4345
|
+
const lastRunRaw = row["last_run_at"];
|
|
4346
|
+
const lastRunAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
|
|
4347
|
+
return {
|
|
4348
|
+
locationId,
|
|
4349
|
+
enabled: row["enabled"] === 1 || row["enabled"] === true,
|
|
4350
|
+
retentionCount: Number(row["retention_count"] ?? 0),
|
|
4351
|
+
...label !== void 0 ? { label } : {},
|
|
4352
|
+
...cron !== void 0 ? { cron } : {},
|
|
4353
|
+
...lastRunAt !== void 0 ? { lastRunAt } : {}
|
|
4354
|
+
};
|
|
4348
4355
|
}
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4356
|
+
//#endregion
|
|
4357
|
+
//#region src/builtins/backup-orchestrator/download-helpers.ts
|
|
4358
|
+
/**
|
|
4359
|
+
* Chunked-download helper for the backup orchestrator (Task 12 of
|
|
4360
|
+
* the storage-unification refactor).
|
|
4361
|
+
*
|
|
4362
|
+
* Streams an archive from a destination `StorageLocation` into a
|
|
4363
|
+
* local filesystem path via the consumer-facing `storage` cap. Pulled
|
|
4364
|
+
* out of `backup-orchestrator.addon.ts` so it can be unit-tested with
|
|
4365
|
+
* an in-memory fake `AddonApi['storage']` slice — the addon is hard
|
|
4366
|
+
* to bootstrap in vitest because of the BaseAddon ceremony.
|
|
4367
|
+
*
|
|
4368
|
+
* Cleanup contract:
|
|
4369
|
+
* - Successful run leaves the cache file in place + calls
|
|
4370
|
+
* `endDownload`.
|
|
4371
|
+
* - Mid-stream failure destroys the writeStream, removes the
|
|
4372
|
+
* partial cache file, and best-effort calls `endDownload` so the
|
|
4373
|
+
* storage provider's session bookkeeping doesn't leak.
|
|
4374
|
+
*/
|
|
4375
|
+
async function downloadArchiveToCache(input) {
|
|
4376
|
+
const { downloadId, sizeBytes } = await input.api.beginDownload.mutate({
|
|
4377
|
+
location: input.locationId,
|
|
4378
|
+
relativePath: input.filename
|
|
4379
|
+
});
|
|
4380
|
+
const writeStream = node_fs.createWriteStream(input.cachePath);
|
|
4381
|
+
let offset = 0;
|
|
4382
|
+
try {
|
|
4383
|
+
while (offset < sizeBytes) {
|
|
4384
|
+
const remaining = sizeBytes - offset;
|
|
4385
|
+
const length = Math.min(input.chunkBytes, remaining);
|
|
4386
|
+
const chunk = await input.api.readChunk.query({
|
|
4387
|
+
downloadId,
|
|
4388
|
+
offset,
|
|
4389
|
+
length
|
|
4390
|
+
});
|
|
4391
|
+
if (chunk.byteLength === 0) throw new Error(`backup restore: empty chunk at offset ${offset}/${sizeBytes} from "${input.locationId}/${input.filename}"`);
|
|
4392
|
+
writeStream.write(chunk);
|
|
4393
|
+
offset += chunk.byteLength;
|
|
4361
4394
|
}
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
previousRun: void 0,
|
|
4376
|
-
currentRun: void 0,
|
|
4377
|
-
once: void 0,
|
|
4378
|
-
currentTimeout: void 0,
|
|
4379
|
-
maxRuns: n ? n.maxRuns : void 0,
|
|
4380
|
-
paused: n ? n.paused : !1,
|
|
4381
|
-
pattern: new C("* * * * *", void 0, { mode: "auto" })
|
|
4382
|
-
}, e && (e instanceof Date || typeof e == "string" && e.indexOf(":") > 0) ? this._states.once = new m(e, this.getTz()) : this._states.pattern = new C(e, this.options.timezone, {
|
|
4383
|
-
mode: this.options.mode,
|
|
4384
|
-
alternativeWeekdays: this.options.alternativeWeekdays,
|
|
4385
|
-
sloppyRanges: this.options.sloppyRanges
|
|
4386
|
-
}), this.name) {
|
|
4387
|
-
if (w.find((o) => o.name === this.name)) throw new Error("Cron: Tried to initialize new named job '" + this.name + "', but name already taken.");
|
|
4388
|
-
w.push(this);
|
|
4395
|
+
await new Promise((resolve, reject) => {
|
|
4396
|
+
writeStream.end((err) => {
|
|
4397
|
+
if (err) reject(err);
|
|
4398
|
+
else resolve();
|
|
4399
|
+
});
|
|
4400
|
+
});
|
|
4401
|
+
try {
|
|
4402
|
+
await input.api.endDownload.mutate({ downloadId });
|
|
4403
|
+
} catch (err) {
|
|
4404
|
+
input.logger.warn("backup restore: endDownload failed (ignoring)", { meta: {
|
|
4405
|
+
downloadId,
|
|
4406
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4407
|
+
} });
|
|
4389
4408
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4409
|
+
} catch (err) {
|
|
4410
|
+
await new Promise((resolve) => {
|
|
4411
|
+
writeStream.once("close", () => resolve());
|
|
4412
|
+
writeStream.destroy();
|
|
4413
|
+
});
|
|
4414
|
+
try {
|
|
4415
|
+
await node_fs_promises.rm(input.cachePath, { force: true });
|
|
4416
|
+
} catch {}
|
|
4417
|
+
try {
|
|
4418
|
+
await input.api.endDownload.mutate({ downloadId });
|
|
4419
|
+
} catch {}
|
|
4420
|
+
throw err;
|
|
4395
4421
|
}
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4422
|
+
}
|
|
4423
|
+
//#endregion
|
|
4424
|
+
//#region src/builtins/backup-orchestrator/manifest-store.ts
|
|
4425
|
+
/** File path (relative to the location root) where the index lives. */
|
|
4426
|
+
var MANIFESTS_FILENAME = "manifests.json";
|
|
4427
|
+
/**
|
|
4428
|
+
* Read the per-location manifest. Returns an empty manifest on first
|
|
4429
|
+
* use (when `manifests.json` doesn't exist) or when the file is
|
|
4430
|
+
* unreadable / corrupt — operators see destination state degrade
|
|
4431
|
+
* gracefully rather than the orchestrator crashing.
|
|
4432
|
+
*/
|
|
4433
|
+
async function readLocationManifest(api, locationId) {
|
|
4434
|
+
if (!await api.exists.query({
|
|
4435
|
+
location: locationId,
|
|
4436
|
+
relativePath: "manifests.json"
|
|
4437
|
+
}).catch(() => false)) return EMPTY_MANIFEST;
|
|
4438
|
+
let bytes;
|
|
4439
|
+
try {
|
|
4440
|
+
bytes = await api.read.query({
|
|
4441
|
+
location: locationId,
|
|
4442
|
+
relativePath: MANIFESTS_FILENAME
|
|
4443
|
+
});
|
|
4444
|
+
} catch {
|
|
4445
|
+
return EMPTY_MANIFEST;
|
|
4400
4446
|
}
|
|
4401
|
-
|
|
4402
|
-
|
|
4447
|
+
return parseManifestBytes(bytes);
|
|
4448
|
+
}
|
|
4449
|
+
async function writeLocationManifest(api, locationId, manifest) {
|
|
4450
|
+
const json = JSON.stringify(manifest, null, 2);
|
|
4451
|
+
const bytes = new TextEncoder().encode(json);
|
|
4452
|
+
await api.write.mutate({
|
|
4453
|
+
location: locationId,
|
|
4454
|
+
relativePath: MANIFESTS_FILENAME,
|
|
4455
|
+
data: bytes
|
|
4456
|
+
});
|
|
4457
|
+
}
|
|
4458
|
+
/**
|
|
4459
|
+
* Decode the raw bytes of `manifests.json`. Falls back to an empty
|
|
4460
|
+
* manifest on JSON-parse / shape-validation failure — same defensive
|
|
4461
|
+
* stance as the legacy `LocalBackupService.ensureManifestsLoaded`.
|
|
4462
|
+
*/
|
|
4463
|
+
function parseManifestBytes(bytes) {
|
|
4464
|
+
try {
|
|
4465
|
+
const text = new TextDecoder().decode(bytes);
|
|
4466
|
+
if (text.trim().length === 0) return EMPTY_MANIFEST;
|
|
4467
|
+
return normalizeManifest(JSON.parse(text));
|
|
4468
|
+
} catch {
|
|
4469
|
+
return EMPTY_MANIFEST;
|
|
4403
4470
|
}
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4471
|
+
}
|
|
4472
|
+
/**
|
|
4473
|
+
* Coerce an arbitrary parsed JSON shape into the canonical manifest
|
|
4474
|
+
* type. Bogus archive entries are silently dropped (a corrupt row
|
|
4475
|
+
* shouldn't lock the whole location out of operator visibility).
|
|
4476
|
+
*/
|
|
4477
|
+
function normalizeManifest(raw) {
|
|
4478
|
+
if (typeof raw !== "object" || raw == null) return EMPTY_MANIFEST;
|
|
4479
|
+
const obj = raw;
|
|
4480
|
+
const archives = [];
|
|
4481
|
+
if (Array.isArray(obj.archives)) for (const a of obj.archives) {
|
|
4482
|
+
if (typeof a !== "object" || a == null) continue;
|
|
4483
|
+
const cand = a;
|
|
4484
|
+
if (typeof cand["id"] !== "string" || typeof cand["filename"] !== "string") continue;
|
|
4485
|
+
if (typeof cand["createdAt"] !== "number" || typeof cand["sizeBytes"] !== "number") continue;
|
|
4486
|
+
const manifest = cand["manifest"];
|
|
4487
|
+
if (typeof manifest !== "object" || manifest == null) continue;
|
|
4488
|
+
const entry = {
|
|
4489
|
+
id: String(cand["id"]),
|
|
4490
|
+
filename: String(cand["filename"]),
|
|
4491
|
+
createdAt: Number(cand["createdAt"]),
|
|
4492
|
+
sizeBytes: Number(cand["sizeBytes"]),
|
|
4493
|
+
...typeof cand["label"] === "string" ? { label: cand["label"] } : {},
|
|
4494
|
+
manifest
|
|
4495
|
+
};
|
|
4496
|
+
archives.push(entry);
|
|
4413
4497
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4498
|
+
return {
|
|
4499
|
+
version: 1,
|
|
4500
|
+
archives
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
var EMPTY_MANIFEST = {
|
|
4504
|
+
version: 1,
|
|
4505
|
+
archives: []
|
|
4506
|
+
};
|
|
4507
|
+
/**
|
|
4508
|
+
* Build a `ManifestArchiveEntry` from the raw inputs collected during
|
|
4509
|
+
* a successful upload. Pure transform — no I/O — kept here so the
|
|
4510
|
+
* triggerBackup path stays terse.
|
|
4511
|
+
*/
|
|
4512
|
+
function buildArchiveEntry(input) {
|
|
4513
|
+
return {
|
|
4514
|
+
id: input.id,
|
|
4515
|
+
filename: input.filename,
|
|
4516
|
+
createdAt: input.createdAt,
|
|
4517
|
+
sizeBytes: input.sizeBytes,
|
|
4518
|
+
...input.label !== void 0 ? { label: input.label } : {},
|
|
4519
|
+
manifest: input.manifest
|
|
4520
|
+
};
|
|
4521
|
+
}
|
|
4522
|
+
/**
|
|
4523
|
+
* Pure retention split: given an archive list and the operator's
|
|
4524
|
+
* `retentionCount`, return `{ keep, drop }` where `keep` is the most-
|
|
4525
|
+
* recent N archives (newest first) and `drop` is the older overflow.
|
|
4526
|
+
*
|
|
4527
|
+
* `retentionCount <= 0` is treated as "no pruning" — defensive against
|
|
4528
|
+
* a config value that would otherwise wipe every archive on the next
|
|
4529
|
+
* write.
|
|
4530
|
+
*/
|
|
4531
|
+
function applyRetention(archives, retentionCount) {
|
|
4532
|
+
if (retentionCount <= 0) return {
|
|
4533
|
+
keep: archives,
|
|
4534
|
+
drop: []
|
|
4535
|
+
};
|
|
4536
|
+
const sorted = [...archives].toSorted((a, b) => b.createdAt - a.createdAt);
|
|
4537
|
+
if (sorted.length <= retentionCount) return {
|
|
4538
|
+
keep: sorted,
|
|
4539
|
+
drop: []
|
|
4540
|
+
};
|
|
4541
|
+
return {
|
|
4542
|
+
keep: sorted.slice(0, retentionCount),
|
|
4543
|
+
drop: sorted.slice(retentionCount)
|
|
4544
|
+
};
|
|
4545
|
+
}
|
|
4546
|
+
/**
|
|
4547
|
+
* Per-location async mutex. The orchestrator owns one instance and
|
|
4548
|
+
* funnels every read-modify-write of `manifests.json` through it so
|
|
4549
|
+
* parallel destination uploads (within the same location) commit in
|
|
4550
|
+
* arrival order. Across locations the operations run in parallel —
|
|
4551
|
+
* each location keys its own promise chain.
|
|
4552
|
+
*/
|
|
4553
|
+
var LocationManifestLock = class {
|
|
4554
|
+
chains = /* @__PURE__ */ new Map();
|
|
4555
|
+
async run(locationId, fn) {
|
|
4556
|
+
const previous = this.chains.get(locationId) ?? Promise.resolve();
|
|
4557
|
+
let resolveNext;
|
|
4558
|
+
const next = new Promise((res) => {
|
|
4559
|
+
resolveNext = res;
|
|
4560
|
+
});
|
|
4561
|
+
this.chains.set(locationId, previous.then(() => next));
|
|
4562
|
+
try {
|
|
4563
|
+
await previous;
|
|
4564
|
+
return await fn();
|
|
4565
|
+
} finally {
|
|
4566
|
+
resolveNext();
|
|
4567
|
+
const tail = this.chains.get(locationId);
|
|
4568
|
+
if (tail) Promise.resolve(tail).then(() => {
|
|
4569
|
+
if (this.chains.get(locationId) === tail) this.chains.delete(locationId);
|
|
4570
|
+
}).catch(() => {});
|
|
4420
4571
|
}
|
|
4421
|
-
let t = new m(e, this.getTz());
|
|
4422
|
-
return t.ms = 0, t.match(this._states.pattern, this.options);
|
|
4423
|
-
}
|
|
4424
|
-
getPattern() {
|
|
4425
|
-
if (!this._states.once) return this._states.pattern ? this._states.pattern.pattern : void 0;
|
|
4426
4572
|
}
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
}
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4573
|
+
};
|
|
4574
|
+
//#endregion
|
|
4575
|
+
//#region src/builtins/backup-orchestrator/schedule-store.ts
|
|
4576
|
+
var TABLE_NAME = "backup_schedules";
|
|
4577
|
+
var BACKUP_SCHEDULES_SCHEMA = { columns: [
|
|
4578
|
+
{
|
|
4579
|
+
name: "id",
|
|
4580
|
+
type: "TEXT",
|
|
4581
|
+
primaryKey: true
|
|
4582
|
+
},
|
|
4583
|
+
{
|
|
4584
|
+
name: "label",
|
|
4585
|
+
type: "TEXT",
|
|
4586
|
+
notNull: true,
|
|
4587
|
+
defaultValue: ""
|
|
4588
|
+
},
|
|
4589
|
+
{
|
|
4590
|
+
name: "cron",
|
|
4591
|
+
type: "TEXT",
|
|
4592
|
+
notNull: true,
|
|
4593
|
+
defaultValue: ""
|
|
4594
|
+
},
|
|
4595
|
+
{
|
|
4596
|
+
name: "enabled",
|
|
4597
|
+
type: "INTEGER",
|
|
4598
|
+
notNull: true,
|
|
4599
|
+
defaultValue: 1
|
|
4600
|
+
},
|
|
4601
|
+
{
|
|
4602
|
+
name: "location_ids",
|
|
4603
|
+
type: "TEXT",
|
|
4604
|
+
notNull: true,
|
|
4605
|
+
defaultValue: "[]"
|
|
4606
|
+
},
|
|
4607
|
+
{
|
|
4608
|
+
name: "retention_count",
|
|
4609
|
+
type: "INTEGER",
|
|
4610
|
+
notNull: true,
|
|
4611
|
+
defaultValue: 7
|
|
4612
|
+
},
|
|
4613
|
+
{
|
|
4614
|
+
name: "data_sources",
|
|
4615
|
+
type: "TEXT"
|
|
4616
|
+
},
|
|
4617
|
+
{
|
|
4618
|
+
name: "last_run_at",
|
|
4619
|
+
type: "INTEGER"
|
|
4454
4620
|
}
|
|
4455
|
-
|
|
4456
|
-
|
|
4621
|
+
] };
|
|
4622
|
+
var BackupScheduleService = class {
|
|
4623
|
+
backend;
|
|
4624
|
+
tableEnsured = false;
|
|
4625
|
+
constructor(backend) {
|
|
4626
|
+
this.backend = backend;
|
|
4457
4627
|
}
|
|
4458
|
-
|
|
4459
|
-
|
|
4628
|
+
async initialize() {
|
|
4629
|
+
await this.ensureTable();
|
|
4460
4630
|
}
|
|
4461
|
-
|
|
4462
|
-
if (
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4631
|
+
async ensureTable() {
|
|
4632
|
+
if (this.tableEnsured) return;
|
|
4633
|
+
if (!this.backend.ensureTable) throw new Error("BackupScheduleService: settings backend does not implement ensureTable — expected the SQLite settings backend, got a backend without structured-table support");
|
|
4634
|
+
await this.backend.ensureTable(TABLE_NAME, BACKUP_SCHEDULES_SCHEMA);
|
|
4635
|
+
this.tableEnsured = true;
|
|
4466
4636
|
}
|
|
4467
|
-
async
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
} finally {
|
|
4479
|
-
this._states.previousRun = new m(e, this.getTz()), this._states.blocking = !1;
|
|
4637
|
+
async list() {
|
|
4638
|
+
await this.ensureTable();
|
|
4639
|
+
if (!this.backend.tableQuery) return [];
|
|
4640
|
+
const rows = await this.backend.tableQuery(TABLE_NAME, { orderBy: {
|
|
4641
|
+
field: "id",
|
|
4642
|
+
direction: "asc"
|
|
4643
|
+
} });
|
|
4644
|
+
const out = [];
|
|
4645
|
+
for (const row of rows) {
|
|
4646
|
+
const mapped = mapRowToSchedule(row);
|
|
4647
|
+
if (mapped) out.push(mapped);
|
|
4480
4648
|
}
|
|
4649
|
+
return out;
|
|
4481
4650
|
}
|
|
4482
|
-
async
|
|
4483
|
-
await this.
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
return
|
|
4487
|
-
}
|
|
4488
|
-
_checkTrigger(e) {
|
|
4489
|
-
let t = /* @__PURE__ */ new Date(), r = !this._states.paused && t.getTime() >= e.getTime(), n = this._states.blocking && this.options.protect;
|
|
4490
|
-
r && !n ? (this._states.maxRuns !== void 0 && this._states.maxRuns--, this._trigger()) : r && n && p(this.options.protect) && setTimeout(() => this.options.protect(this), 0), this.schedule();
|
|
4491
|
-
}
|
|
4492
|
-
_next(e) {
|
|
4493
|
-
let t = !!(e || this._states.currentRun), r = !1;
|
|
4494
|
-
!e && this.options.startAt && this.options.interval && ([e, t] = this._calculatePreviousRun(e, t), r = !e), e = new m(e, this.getTz()), this.options.startAt && e && e.getTime() < this.options.startAt.getTime() && (e = this.options.startAt);
|
|
4495
|
-
let n = this._states.once || new m(e, this.getTz());
|
|
4496
|
-
return !r && n !== this._states.once && (n = n.increment(this._states.pattern, this.options, t)), this._states.once && this._states.once.getTime() <= e.getTime() || n === null || this._states.maxRuns !== void 0 && this._states.maxRuns <= 0 || this._states.kill || this.options.stopAt && n.getTime() >= this.options.stopAt.getTime() ? null : n;
|
|
4651
|
+
async get(id) {
|
|
4652
|
+
await this.ensureTable();
|
|
4653
|
+
if (!this.backend.tableGet) return null;
|
|
4654
|
+
const row = await this.backend.tableGet(TABLE_NAME, { id });
|
|
4655
|
+
return row ? mapRowToSchedule(row) : null;
|
|
4497
4656
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4657
|
+
/** Replace-or-insert (delete + insert — mirrors the policy store). */
|
|
4658
|
+
async upsert(s) {
|
|
4659
|
+
await this.ensureTable();
|
|
4660
|
+
if (!this.backend.tableDelete || !this.backend.tableInsert) throw new Error("BackupScheduleService: backend missing tableDelete/tableInsert");
|
|
4661
|
+
await this.backend.tableDelete(TABLE_NAME, { id: s.id });
|
|
4662
|
+
await this.backend.tableInsert(TABLE_NAME, mapScheduleToRow(s));
|
|
4503
4663
|
}
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
if (this.
|
|
4507
|
-
|
|
4508
|
-
let i = n.getTime() + this.options.interval * 1e3;
|
|
4509
|
-
for (; i <= r.getTime();) n = new m(n, this.getTz()).increment(this._states.pattern, this.options, !0), i = n.getTime() + this.options.interval * 1e3;
|
|
4510
|
-
t = !0;
|
|
4511
|
-
}
|
|
4512
|
-
return n === null && (n = void 0), [n, t];
|
|
4664
|
+
async delete(id) {
|
|
4665
|
+
await this.ensureTable();
|
|
4666
|
+
if (!this.backend.tableDelete) throw new Error("BackupScheduleService: backend missing tableDelete");
|
|
4667
|
+
await this.backend.tableDelete(TABLE_NAME, { id });
|
|
4513
4668
|
}
|
|
4514
4669
|
};
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
* loop); future surfaces (e.g. event-driven retention sweeps) can
|
|
4527
|
-
* register an actual scheduled `Cron(pattern, fn)` and let croner
|
|
4528
|
-
* own the timer.
|
|
4529
|
-
*/
|
|
4530
|
-
/**
|
|
4531
|
-
* Validate a cron expression and return its next firing time strictly
|
|
4532
|
-
* after `after`, expressed as ms-epoch. Returns `null` when the
|
|
4533
|
-
* expression has no future runs (croner returns null e.g. for
|
|
4534
|
-
* fully-bounded patterns that have already elapsed). Throws when the
|
|
4535
|
-
* expression is syntactically invalid — callers are expected to
|
|
4536
|
-
* surface that to the operator.
|
|
4537
|
-
*/
|
|
4538
|
-
function computeNextDue(cron, after) {
|
|
4539
|
-
const next = new E(cron, { paused: true }).nextRun(new Date(after));
|
|
4540
|
-
return next ? next.getTime() : null;
|
|
4670
|
+
function mapScheduleToRow(s) {
|
|
4671
|
+
return {
|
|
4672
|
+
id: s.id,
|
|
4673
|
+
label: s.label,
|
|
4674
|
+
cron: s.cron,
|
|
4675
|
+
enabled: s.enabled ? 1 : 0,
|
|
4676
|
+
location_ids: JSON.stringify([...s.locationIds]),
|
|
4677
|
+
retention_count: s.retentionCount,
|
|
4678
|
+
data_sources: s.dataSources ? JSON.stringify([...s.dataSources]) : null,
|
|
4679
|
+
last_run_at: s.lastRunAt ?? null
|
|
4680
|
+
};
|
|
4541
4681
|
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
* otherwise. Used by the cap-router input zod schema's `.refine`
|
|
4545
|
-
* callback to reject malformed expressions at the boundary instead
|
|
4546
|
-
* of at scheduler-tick time.
|
|
4547
|
-
*/
|
|
4548
|
-
function isValidCron(cron) {
|
|
4682
|
+
function parseStringArray(raw) {
|
|
4683
|
+
if (typeof raw !== "string" || raw.length === 0) return [];
|
|
4549
4684
|
try {
|
|
4550
|
-
|
|
4551
|
-
return
|
|
4685
|
+
const parsed = JSON.parse(raw);
|
|
4686
|
+
if (!Array.isArray(parsed)) return [];
|
|
4687
|
+
return parsed.filter((v) => typeof v === "string");
|
|
4552
4688
|
} catch {
|
|
4553
|
-
return
|
|
4689
|
+
return [];
|
|
4554
4690
|
}
|
|
4555
4691
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4692
|
+
function mapRowToSchedule(row) {
|
|
4693
|
+
const id = String(row["id"] ?? "");
|
|
4694
|
+
if (!id) return null;
|
|
4695
|
+
const dataSourcesRaw = row["data_sources"];
|
|
4696
|
+
const dataSources = typeof dataSourcesRaw === "string" && dataSourcesRaw.length > 0 ? parseStringArray(dataSourcesRaw) : void 0;
|
|
4697
|
+
const lastRunRaw = row["last_run_at"];
|
|
4698
|
+
const lastRunAt = typeof lastRunRaw === "number" && lastRunRaw > 0 ? lastRunRaw : void 0;
|
|
4699
|
+
return {
|
|
4700
|
+
id,
|
|
4701
|
+
label: typeof row["label"] === "string" ? String(row["label"]) : "",
|
|
4702
|
+
cron: typeof row["cron"] === "string" ? String(row["cron"]) : "",
|
|
4703
|
+
enabled: row["enabled"] === 1 || row["enabled"] === true,
|
|
4704
|
+
locationIds: parseStringArray(row["location_ids"]),
|
|
4705
|
+
retentionCount: Number(row["retention_count"] ?? 7),
|
|
4706
|
+
...dataSources !== void 0 ? { dataSources } : {},
|
|
4707
|
+
...lastRunAt !== void 0 ? { lastRunAt } : {}
|
|
4708
|
+
};
|
|
4573
4709
|
}
|
|
4574
4710
|
//#endregion
|
|
4575
4711
|
//#region src/builtins/backup-orchestrator/backup-orchestrator.addon.ts
|
|
@@ -4642,6 +4778,7 @@ var ORCHESTRATOR_ADDON_ID = "backup-orchestrator";
|
|
|
4642
4778
|
var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
4643
4779
|
systemBackup = null;
|
|
4644
4780
|
policies = null;
|
|
4781
|
+
schedules = null;
|
|
4645
4782
|
dataDir = "";
|
|
4646
4783
|
/**
|
|
4647
4784
|
* Transient build buffer for archive fan-out. The tar is built here
|
|
@@ -4675,6 +4812,13 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
4675
4812
|
this.ctx.logger.error("backup-orchestrator: policy table init failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
4676
4813
|
this.policies = null;
|
|
4677
4814
|
}
|
|
4815
|
+
this.schedules = this.resolveScheduleService();
|
|
4816
|
+
if (this.schedules) try {
|
|
4817
|
+
await this.schedules.initialize();
|
|
4818
|
+
} catch (err) {
|
|
4819
|
+
this.ctx.logger.error("backup-orchestrator: schedule table init failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
4820
|
+
this.schedules = null;
|
|
4821
|
+
}
|
|
4678
4822
|
const provider = {
|
|
4679
4823
|
listDestinations: () => this.listDestinations(),
|
|
4680
4824
|
list: () => this.aggregatedList(),
|
|
@@ -4685,7 +4829,10 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
4685
4829
|
delete: (input) => this.deleteArchive(input),
|
|
4686
4830
|
listArchives: (input) => this.listArchives(input),
|
|
4687
4831
|
upsertDestinationPolicy: (input) => this.upsertDestinationPolicy(input),
|
|
4688
|
-
previewSchedule: (input) => this.previewSchedule(input)
|
|
4832
|
+
previewSchedule: (input) => this.previewSchedule(input),
|
|
4833
|
+
listSchedules: () => this.listSchedules(),
|
|
4834
|
+
upsertSchedule: (input) => this.upsertSchedule(input),
|
|
4835
|
+
deleteSchedule: (input) => this.deleteSchedule(input)
|
|
4689
4836
|
};
|
|
4690
4837
|
this.startScheduleTimer();
|
|
4691
4838
|
this.ctx.logger.info("Backup orchestrator initialized", { meta: {
|
|
@@ -4701,6 +4848,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
4701
4848
|
this.stopScheduleTimer();
|
|
4702
4849
|
this.systemBackup = null;
|
|
4703
4850
|
this.policies = null;
|
|
4851
|
+
this.schedules = null;
|
|
4704
4852
|
}
|
|
4705
4853
|
/**
|
|
4706
4854
|
* Aggregate destinations from `api.storage.listLocations({ type: 'backups' })`
|
|
@@ -4882,7 +5030,7 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
4882
5030
|
cachePath,
|
|
4883
5031
|
sizeBytes: result.sizeBytes,
|
|
4884
5032
|
archiveManifest: result.manifest,
|
|
4885
|
-
retentionCount: policy.retentionCount,
|
|
5033
|
+
retentionCount: input?.retentionCount ?? policy.retentionCount,
|
|
4886
5034
|
createdAt,
|
|
4887
5035
|
label: formatArchiveLabel(dest.id, createdAt, input?.label)
|
|
4888
5036
|
});
|
|
@@ -5175,65 +5323,139 @@ var BackupOrchestratorAddon = class extends require_dist.BaseAddon {
|
|
|
5175
5323
|
this.scheduleTimer = null;
|
|
5176
5324
|
}
|
|
5177
5325
|
/**
|
|
5178
|
-
*
|
|
5179
|
-
*
|
|
5180
|
-
*
|
|
5181
|
-
*
|
|
5182
|
-
*
|
|
5183
|
-
*
|
|
5184
|
-
* any global setting.
|
|
5326
|
+
* Schedule-driven backup scheduling. Walks every enabled backup
|
|
5327
|
+
* SCHEDULE (the N:M entry), computes the next due time after
|
|
5328
|
+
* `lastRunAt` (or the orchestrator's first-tick baseline if never
|
|
5329
|
+
* run), and fires a SINGLE archive fanned out to ALL of the
|
|
5330
|
+
* schedule's `locationIds` when due — with the schedule's per-entry
|
|
5331
|
+
* retention applied to each. Each schedule keeps its own cadence.
|
|
5185
5332
|
*
|
|
5186
|
-
* Errors per
|
|
5187
|
-
*
|
|
5188
|
-
*
|
|
5189
|
-
*
|
|
5190
|
-
*
|
|
5333
|
+
* Errors per schedule are isolated; one misconfigured cron doesn't
|
|
5334
|
+
* block the others. The schedule's `lastRunAt` is updated BEFORE the
|
|
5335
|
+
* trigger to dedupe (the tick runs every SCHEDULE_TICK_MS — without
|
|
5336
|
+
* the up-front update a long-running upload could overlap with the
|
|
5337
|
+
* next tick's same-due check).
|
|
5191
5338
|
*/
|
|
5192
5339
|
async onScheduleTick() {
|
|
5193
|
-
if (!this.
|
|
5194
|
-
const
|
|
5340
|
+
if (!this.schedules) return;
|
|
5341
|
+
const schedules = await this.schedules.list();
|
|
5195
5342
|
const now = Date.now();
|
|
5196
|
-
for (const
|
|
5197
|
-
if (!
|
|
5343
|
+
for (const s of schedules) {
|
|
5344
|
+
if (!s.enabled || !s.cron || s.cron.trim().length === 0) continue;
|
|
5345
|
+
if (s.locationIds.length === 0) continue;
|
|
5198
5346
|
let due;
|
|
5199
5347
|
try {
|
|
5200
|
-
due = computeNextDue(
|
|
5348
|
+
due = computeNextDue(s.cron, s.lastRunAt ?? this.scheduleBaselineAt);
|
|
5201
5349
|
} catch (err) {
|
|
5202
|
-
this.ctx.logger.warn("Invalid cron — skipping
|
|
5203
|
-
|
|
5204
|
-
cron:
|
|
5350
|
+
this.ctx.logger.warn("Invalid cron — skipping schedule", { meta: {
|
|
5351
|
+
scheduleId: s.id,
|
|
5352
|
+
cron: s.cron,
|
|
5205
5353
|
error: err instanceof Error ? err.message : String(err)
|
|
5206
5354
|
} });
|
|
5207
5355
|
continue;
|
|
5208
5356
|
}
|
|
5209
5357
|
if (due === null || due > now) continue;
|
|
5210
5358
|
const stamped = {
|
|
5211
|
-
...
|
|
5359
|
+
...s,
|
|
5212
5360
|
lastRunAt: now
|
|
5213
5361
|
};
|
|
5214
5362
|
try {
|
|
5215
|
-
await this.
|
|
5363
|
+
await this.schedules.upsert(stamped);
|
|
5216
5364
|
} catch (err) {
|
|
5217
5365
|
this.ctx.logger.warn("Failed to persist scheduled-run timestamp", { meta: {
|
|
5218
|
-
|
|
5366
|
+
scheduleId: s.id,
|
|
5219
5367
|
error: err instanceof Error ? err.message : String(err)
|
|
5220
5368
|
} });
|
|
5221
5369
|
}
|
|
5222
5370
|
try {
|
|
5223
|
-
this.ctx.logger.info("Scheduled backup firing
|
|
5224
|
-
|
|
5225
|
-
|
|
5371
|
+
this.ctx.logger.info("Scheduled backup firing", { meta: {
|
|
5372
|
+
scheduleId: s.id,
|
|
5373
|
+
label: s.label,
|
|
5374
|
+
locationIds: s.locationIds,
|
|
5375
|
+
cron: s.cron,
|
|
5226
5376
|
dueAt: due
|
|
5227
5377
|
} });
|
|
5228
|
-
await this.triggerBackup({
|
|
5378
|
+
await this.triggerBackup({
|
|
5379
|
+
destinations: [...s.locationIds],
|
|
5380
|
+
...s.dataSources && s.dataSources.length > 0 ? { locations: [...s.dataSources] } : {},
|
|
5381
|
+
label: s.label,
|
|
5382
|
+
retentionCount: s.retentionCount
|
|
5383
|
+
});
|
|
5229
5384
|
} catch (err) {
|
|
5230
5385
|
this.ctx.logger.error("Scheduled backup failed", { meta: {
|
|
5231
|
-
|
|
5386
|
+
scheduleId: s.id,
|
|
5232
5387
|
error: err instanceof Error ? err.message : String(err)
|
|
5233
5388
|
} });
|
|
5234
5389
|
}
|
|
5235
5390
|
}
|
|
5236
5391
|
}
|
|
5392
|
+
async listSchedules() {
|
|
5393
|
+
if (!this.schedules) return [];
|
|
5394
|
+
return (await this.schedules.list()).map((s) => {
|
|
5395
|
+
let nextRunAt;
|
|
5396
|
+
if (s.enabled && s.cron.trim().length > 0) try {
|
|
5397
|
+
const next = computeNextDue(s.cron, Date.now());
|
|
5398
|
+
if (next !== null) nextRunAt = next;
|
|
5399
|
+
} catch {}
|
|
5400
|
+
return {
|
|
5401
|
+
id: s.id,
|
|
5402
|
+
label: s.label,
|
|
5403
|
+
cron: s.cron,
|
|
5404
|
+
enabled: s.enabled,
|
|
5405
|
+
locationIds: [...s.locationIds],
|
|
5406
|
+
retentionCount: s.retentionCount,
|
|
5407
|
+
...s.dataSources !== void 0 ? { dataSources: [...s.dataSources] } : {},
|
|
5408
|
+
...s.lastRunAt !== void 0 ? { lastRunAt: s.lastRunAt } : {},
|
|
5409
|
+
...nextRunAt !== void 0 ? { nextRunAt } : {}
|
|
5410
|
+
};
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
async upsertSchedule(input) {
|
|
5414
|
+
if (!this.schedules) throw new Error("backup upsertSchedule: no settings-store backend — schedule cannot be persisted");
|
|
5415
|
+
const cron = input.cron.trim();
|
|
5416
|
+
if (cron.length > 0 && !isValidCron(cron)) throw new Error(`Invalid cron expression: "${cron}"`);
|
|
5417
|
+
const id = input.id && input.id.length > 0 ? input.id : (0, node_crypto.randomUUID)();
|
|
5418
|
+
const existing = await this.schedules.get(id).catch(() => null);
|
|
5419
|
+
const next = {
|
|
5420
|
+
id,
|
|
5421
|
+
label: input.label,
|
|
5422
|
+
cron,
|
|
5423
|
+
enabled: input.enabled,
|
|
5424
|
+
locationIds: [...input.locationIds],
|
|
5425
|
+
retentionCount: input.retentionCount,
|
|
5426
|
+
...input.dataSources && input.dataSources.length > 0 ? { dataSources: [...input.dataSources] } : {},
|
|
5427
|
+
...existing?.lastRunAt !== void 0 ? { lastRunAt: existing.lastRunAt } : {}
|
|
5428
|
+
};
|
|
5429
|
+
await this.schedules.upsert(next);
|
|
5430
|
+
let nextRunAt;
|
|
5431
|
+
if (next.enabled && cron.length > 0) try {
|
|
5432
|
+
const due = computeNextDue(cron, Date.now());
|
|
5433
|
+
if (due !== null) nextRunAt = due;
|
|
5434
|
+
} catch {}
|
|
5435
|
+
return {
|
|
5436
|
+
id: next.id,
|
|
5437
|
+
label: next.label,
|
|
5438
|
+
cron: next.cron,
|
|
5439
|
+
enabled: next.enabled,
|
|
5440
|
+
locationIds: [...next.locationIds],
|
|
5441
|
+
retentionCount: next.retentionCount,
|
|
5442
|
+
...next.dataSources !== void 0 ? { dataSources: [...next.dataSources] } : {},
|
|
5443
|
+
...next.lastRunAt !== void 0 ? { lastRunAt: next.lastRunAt } : {},
|
|
5444
|
+
...nextRunAt !== void 0 ? { nextRunAt } : {}
|
|
5445
|
+
};
|
|
5446
|
+
}
|
|
5447
|
+
async deleteSchedule(input) {
|
|
5448
|
+
if (!this.schedules) return;
|
|
5449
|
+
await this.schedules.delete(input.id);
|
|
5450
|
+
}
|
|
5451
|
+
resolveScheduleService() {
|
|
5452
|
+
const reg = this.ctx.kernel.capabilityRegistry;
|
|
5453
|
+
if (!reg) return null;
|
|
5454
|
+
const backend = reg.getSingleton("settings-store");
|
|
5455
|
+
if (!backend) return null;
|
|
5456
|
+
if (!backend.ensureTable || !backend.tableInsert) return null;
|
|
5457
|
+
return new BackupScheduleService(backend);
|
|
5458
|
+
}
|
|
5237
5459
|
resolvePolicyService() {
|
|
5238
5460
|
const reg = this.ctx.kernel.capabilityRegistry;
|
|
5239
5461
|
if (!reg) return null;
|