@webtypen/webframez-core 0.3.56 → 0.3.58
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/README.md
CHANGED
|
@@ -463,12 +463,28 @@ builder.registerType({
|
|
|
463
463
|
collection: "users",
|
|
464
464
|
fields: {
|
|
465
465
|
email: { type: "text", required: true, unique: { match: {} } },
|
|
466
|
-
age: { type: "integer" }
|
|
466
|
+
age: { type: "integer" },
|
|
467
|
+
default_values: {
|
|
468
|
+
type: "array",
|
|
469
|
+
schema: {
|
|
470
|
+
key: { type: "string", required: true },
|
|
471
|
+
name: { type: "string", required: true }
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
target: {
|
|
475
|
+
type: "option",
|
|
476
|
+
optionsMapping: { from: "default_values", value: "key", label: "name" }
|
|
477
|
+
}
|
|
467
478
|
}
|
|
468
479
|
}
|
|
469
480
|
});
|
|
470
481
|
```
|
|
471
482
|
|
|
483
|
+
`option` fields can use static `options: [{ value, label }]` or a client-side
|
|
484
|
+
`optionsMapping: { from: string; value: string; label: string }`. The mapping is
|
|
485
|
+
resolved by the DataBuilder frontend against the current form data, so changes in
|
|
486
|
+
the referenced array appear in selects without a backend reload.
|
|
487
|
+
|
|
472
488
|
You can also use:
|
|
473
489
|
- `registerModelType(...)`
|
|
474
490
|
- `registerFieldType(...)`
|
|
@@ -38,6 +38,10 @@ export declare class BackupManager {
|
|
|
38
38
|
private writeContentManifest;
|
|
39
39
|
private backupDatabase;
|
|
40
40
|
private buildArtifact;
|
|
41
|
+
private statusLogPath;
|
|
42
|
+
private runStatus;
|
|
43
|
+
private statusLogOutputs;
|
|
44
|
+
private writeStatusLog;
|
|
41
45
|
run(key: string, options?: BackupRunOptions): Promise<BackupRunResult>;
|
|
42
46
|
private downloadRestoreArtifact;
|
|
43
47
|
private restoreChain;
|
|
@@ -448,6 +448,83 @@ class BackupManager {
|
|
|
448
448
|
type: "directory",
|
|
449
449
|
};
|
|
450
450
|
}
|
|
451
|
+
statusLogPath(backupType) {
|
|
452
|
+
const config = backupType.statusLog;
|
|
453
|
+
if (!config) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
if (typeof config === "string") {
|
|
457
|
+
return (0, BackupPathUtils_1.resolveProjectPath)(config);
|
|
458
|
+
}
|
|
459
|
+
if (config.enabled === false) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
return (0, BackupPathUtils_1.resolveProjectPath)(config.path);
|
|
463
|
+
}
|
|
464
|
+
runStatus(result, error) {
|
|
465
|
+
if (error) {
|
|
466
|
+
return "failed";
|
|
467
|
+
}
|
|
468
|
+
if (result.outputs.find((output) => output.status === "failed")) {
|
|
469
|
+
return "failed";
|
|
470
|
+
}
|
|
471
|
+
if (result.outputs.length > 0 && result.outputs.every((output) => output.status === "skipped")) {
|
|
472
|
+
return "skipped";
|
|
473
|
+
}
|
|
474
|
+
return "success";
|
|
475
|
+
}
|
|
476
|
+
statusLogOutputs(result) {
|
|
477
|
+
return result.outputs.map((output) => ({
|
|
478
|
+
driver: output.driver,
|
|
479
|
+
status: output.status,
|
|
480
|
+
path: output.path,
|
|
481
|
+
payload: output.payload,
|
|
482
|
+
error: output.error,
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
writeStatusLog(backupType, result, options, error) {
|
|
486
|
+
if (result.dryRun) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const filepath = this.statusLogPath(backupType);
|
|
490
|
+
if (!filepath) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
const previous = fs_1.default.existsSync(filepath) ? safeReadJson(filepath) || {} : {};
|
|
495
|
+
const status = this.runStatus(result, error);
|
|
496
|
+
const runAt = result.endedAt || new Date().toISOString();
|
|
497
|
+
const outputs = this.statusLogOutputs(result);
|
|
498
|
+
const lastRun = {
|
|
499
|
+
run_at: runAt,
|
|
500
|
+
run_status: status,
|
|
501
|
+
run_outputs: outputs,
|
|
502
|
+
};
|
|
503
|
+
if (error) {
|
|
504
|
+
lastRun.run_error = error instanceof Error ? error.message : String(error);
|
|
505
|
+
}
|
|
506
|
+
const statusLog = {
|
|
507
|
+
last_run_at: runAt,
|
|
508
|
+
last_run_status: status,
|
|
509
|
+
last_run_outputs: outputs,
|
|
510
|
+
last_success_run: previous.last_success_run || null,
|
|
511
|
+
};
|
|
512
|
+
if (error) {
|
|
513
|
+
statusLog.last_run_error = lastRun.run_error;
|
|
514
|
+
}
|
|
515
|
+
if (status === "success") {
|
|
516
|
+
statusLog.last_success_run = lastRun;
|
|
517
|
+
}
|
|
518
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filepath), { recursive: true });
|
|
519
|
+
fs_1.default.writeFileSync(filepath, JSON.stringify(statusLog, null, 4), "utf-8");
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
this.log(options, "Failed to write backup status log", {
|
|
523
|
+
path: filepath,
|
|
524
|
+
error: e instanceof Error ? e.message : String(e),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
451
528
|
run(key, options) {
|
|
452
529
|
var _a, _b, _c;
|
|
453
530
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -477,113 +554,121 @@ class BackupManager {
|
|
|
477
554
|
files: [],
|
|
478
555
|
databases: [],
|
|
479
556
|
};
|
|
480
|
-
this.log(options, "Collecting file sources");
|
|
481
|
-
const fileEntries = normalizeArray(backupType.files).flatMap((source) => this.collectFilesFromSource(source, options));
|
|
482
|
-
const fileSize = fileEntries.reduce((sum, entry) => sum + entry.size, 0);
|
|
483
|
-
this.log(options, `Collected ${fileEntries.length} file(s)`, { size: fileSize });
|
|
484
|
-
const incremental = yield this.buildBackupManifest(key, id, now, artifactFilename, backupType, fileEntries, options);
|
|
485
|
-
result.kind = incremental.manifest.kind;
|
|
486
|
-
result.chainId = incremental.manifest.chainId;
|
|
487
|
-
result.parentBackupId = incremental.manifest.parentBackupId;
|
|
488
|
-
result.backupManifest = incremental.manifest;
|
|
489
|
-
result.files = incremental.filesToCopy.map((entry) => ({
|
|
490
|
-
from: entry.source,
|
|
491
|
-
to: entry.target,
|
|
492
|
-
size: entry.size,
|
|
493
|
-
}));
|
|
494
|
-
if (this.incrementalEnabled(backupType)) {
|
|
495
|
-
this.log(options, `Prepared ${incremental.manifest.kind} backup`, {
|
|
496
|
-
upserted: incremental.manifest.files.upserted.length,
|
|
497
|
-
deleted: incremental.manifest.files.deleted.length,
|
|
498
|
-
parentBackupId: incremental.manifest.parentBackupId,
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
if (options === null || options === void 0 ? void 0 : options.dryRun) {
|
|
502
|
-
this.log(options, "Dry run enabled; skipping artifact and output writes");
|
|
503
|
-
result.outputs = outputs.map((output) => ({
|
|
504
|
-
driver: output.driver,
|
|
505
|
-
status: "skipped",
|
|
506
|
-
path: output.path,
|
|
507
|
-
payload: { dryRun: true },
|
|
508
|
-
}));
|
|
509
|
-
result.databases = normalizeArray(backupType.databases).map((source) => ({
|
|
510
|
-
connection: source.connection,
|
|
511
|
-
to: (0, BackupPathUtils_1.normalizeBackupPath)(source.to || `database/${source.connection || "default"}`),
|
|
512
|
-
}));
|
|
513
|
-
result.endedAt = new Date().toISOString();
|
|
514
|
-
return result;
|
|
515
|
-
}
|
|
516
|
-
fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
|
|
517
|
-
fs_1.default.mkdirSync(contentDir, { recursive: true });
|
|
518
557
|
try {
|
|
519
|
-
this.log(options,
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
this.log(options,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
558
|
+
this.log(options, "Collecting file sources");
|
|
559
|
+
const fileEntries = normalizeArray(backupType.files).flatMap((source) => this.collectFilesFromSource(source, options));
|
|
560
|
+
const fileSize = fileEntries.reduce((sum, entry) => sum + entry.size, 0);
|
|
561
|
+
this.log(options, `Collected ${fileEntries.length} file(s)`, { size: fileSize });
|
|
562
|
+
const incremental = yield this.buildBackupManifest(key, id, now, artifactFilename, backupType, fileEntries, options);
|
|
563
|
+
result.kind = incremental.manifest.kind;
|
|
564
|
+
result.chainId = incremental.manifest.chainId;
|
|
565
|
+
result.parentBackupId = incremental.manifest.parentBackupId;
|
|
566
|
+
result.backupManifest = incremental.manifest;
|
|
567
|
+
result.files = incremental.filesToCopy.map((entry) => ({
|
|
568
|
+
from: entry.source,
|
|
569
|
+
to: entry.target,
|
|
570
|
+
size: entry.size,
|
|
571
|
+
}));
|
|
572
|
+
if (this.incrementalEnabled(backupType)) {
|
|
573
|
+
this.log(options, `Prepared ${incremental.manifest.kind} backup`, {
|
|
574
|
+
upserted: incremental.manifest.files.upserted.length,
|
|
575
|
+
deleted: incremental.manifest.files.deleted.length,
|
|
576
|
+
parentBackupId: incremental.manifest.parentBackupId,
|
|
577
|
+
});
|
|
531
578
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const retention = mergeRetention((_a = this.getConfig().defaults) === null || _a === void 0 ? void 0 : _a.retention, backupType.retention, output.retention);
|
|
538
|
-
const driver = BackupOutputDrivers_1.BackupOutputDrivers.get(output.driver);
|
|
539
|
-
this.log(options, `Writing artifact to '${output.driver}' output`, {
|
|
579
|
+
if (options === null || options === void 0 ? void 0 : options.dryRun) {
|
|
580
|
+
this.log(options, "Dry run enabled; skipping artifact and output writes");
|
|
581
|
+
result.outputs = outputs.map((output) => ({
|
|
582
|
+
driver: output.driver,
|
|
583
|
+
status: "skipped",
|
|
540
584
|
path: output.path,
|
|
541
|
-
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
585
|
+
payload: { dryRun: true },
|
|
586
|
+
}));
|
|
587
|
+
result.databases = normalizeArray(backupType.databases).map((source) => ({
|
|
588
|
+
connection: source.connection,
|
|
589
|
+
to: (0, BackupPathUtils_1.normalizeBackupPath)(source.to || `database/${source.connection || "default"}`),
|
|
590
|
+
}));
|
|
591
|
+
result.endedAt = new Date().toISOString();
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
|
|
595
|
+
fs_1.default.mkdirSync(contentDir, { recursive: true });
|
|
596
|
+
try {
|
|
597
|
+
this.log(options, `Copying ${incremental.filesToCopy.length} file(s) into backup work directory`);
|
|
598
|
+
this.copyFiles(incremental.filesToCopy, contentDir, options);
|
|
599
|
+
this.writeContentManifest(contentDir, incremental.manifest);
|
|
600
|
+
this.log(options, "File copy finished");
|
|
601
|
+
for (const source of normalizeArray(backupType.databases)) {
|
|
602
|
+
this.log(options, `Backing up database '${source.connection || "default"}'`);
|
|
603
|
+
result.databases.push(yield this.backupDatabase(source, contentDir, {
|
|
604
|
+
backupKey: key,
|
|
605
|
+
backupId: id,
|
|
606
|
+
log: (message, payload) => this.log(options, message, payload),
|
|
607
|
+
}));
|
|
608
|
+
this.log(options, `Database '${source.connection || "default"}' backup finished`);
|
|
609
|
+
}
|
|
610
|
+
result.artifact = this.buildArtifact(key, backupType, workRunDir, contentDir, now, id, options);
|
|
611
|
+
result.artifact.manifest = incremental.manifest;
|
|
612
|
+
const manifestPath = path_1.default.join(workRunDir, `${result.artifact.filename}.run-manifest.json`);
|
|
613
|
+
result.manifestPath = manifestPath;
|
|
614
|
+
for (const output of outputs) {
|
|
615
|
+
const retention = mergeRetention((_a = this.getConfig().defaults) === null || _a === void 0 ? void 0 : _a.retention, backupType.retention, output.retention);
|
|
616
|
+
const driver = BackupOutputDrivers_1.BackupOutputDrivers.get(output.driver);
|
|
617
|
+
this.log(options, `Writing artifact to '${output.driver}' output`, {
|
|
618
|
+
path: output.path,
|
|
619
|
+
folderPath: output.folderPath,
|
|
620
|
+
});
|
|
621
|
+
const outputResult = yield driver.write(result.artifact, output, {
|
|
557
622
|
backupKey: key,
|
|
558
623
|
backupId: id,
|
|
559
624
|
retention: retention,
|
|
560
625
|
log: (message, payload) => this.log(options, message, payload),
|
|
561
|
-
}, { dryRun: false }));
|
|
562
|
-
this.log(options, `Cleanup for '${output.driver}' output finished`, {
|
|
563
|
-
deleted: ((_b = result.cleanup[result.cleanup.length - 1]) === null || _b === void 0 ? void 0 : _b.deleted.length) || 0,
|
|
564
626
|
});
|
|
627
|
+
result.outputs.push(outputResult);
|
|
628
|
+
this.log(options, `Output '${output.driver}' finished with status '${outputResult.status}'`, {
|
|
629
|
+
path: outputResult.path,
|
|
630
|
+
error: outputResult.error,
|
|
631
|
+
});
|
|
632
|
+
if (outputResult.status === "success" && (retention === null || retention === void 0 ? void 0 : retention.runAfterBackup)) {
|
|
633
|
+
this.log(options, `Running cleanup for '${output.driver}' output`);
|
|
634
|
+
result.cleanup.push(yield driver.cleanup(output, {
|
|
635
|
+
backupKey: key,
|
|
636
|
+
backupId: id,
|
|
637
|
+
retention: retention,
|
|
638
|
+
log: (message, payload) => this.log(options, message, payload),
|
|
639
|
+
}, { dryRun: false }));
|
|
640
|
+
this.log(options, `Cleanup for '${output.driver}' output finished`, {
|
|
641
|
+
deleted: ((_b = result.cleanup[result.cleanup.length - 1]) === null || _b === void 0 ? void 0 : _b.deleted.length) || 0,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
565
644
|
}
|
|
645
|
+
result.endedAt = new Date().toISOString();
|
|
646
|
+
const outputManifestPath = (_c = result.outputs.find((output) => output.payload && output.payload.manifestPath)) === null || _c === void 0 ? void 0 : _c.payload.manifestPath;
|
|
647
|
+
if (outputManifestPath) {
|
|
648
|
+
result.manifestPath = outputManifestPath;
|
|
649
|
+
}
|
|
650
|
+
const manifestJson = JSON.stringify(result, null, 4);
|
|
651
|
+
fs_1.default.writeFileSync(manifestPath, manifestJson, "utf-8");
|
|
652
|
+
for (const output of result.outputs) {
|
|
653
|
+
if (output.payload && output.payload.manifestPath) {
|
|
654
|
+
fs_1.default.writeFileSync(output.payload.manifestPath, manifestJson, "utf-8");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
this.writeStatusLog(backupType, result, options);
|
|
658
|
+
this.log(options, `Backup '${key}' finished`);
|
|
659
|
+
return result;
|
|
566
660
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
const manifestJson = JSON.stringify(result, null, 4);
|
|
573
|
-
fs_1.default.writeFileSync(manifestPath, manifestJson, "utf-8");
|
|
574
|
-
for (const output of result.outputs) {
|
|
575
|
-
if (output.payload && output.payload.manifestPath) {
|
|
576
|
-
fs_1.default.writeFileSync(output.payload.manifestPath, manifestJson, "utf-8");
|
|
661
|
+
finally {
|
|
662
|
+
if (backupType.cleanupWorkDir) {
|
|
663
|
+
this.log(options, "Cleaning backup work directory");
|
|
664
|
+
fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
|
|
577
665
|
}
|
|
578
666
|
}
|
|
579
|
-
this.log(options, `Backup '${key}' finished`);
|
|
580
|
-
return result;
|
|
581
667
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
result.endedAt = new Date().toISOString();
|
|
670
|
+
this.writeStatusLog(backupType, result, options, e);
|
|
671
|
+
throw e;
|
|
587
672
|
}
|
|
588
673
|
});
|
|
589
674
|
}
|
|
@@ -33,6 +33,10 @@ export type BackupAutomationConfig = {
|
|
|
33
33
|
priority?: number;
|
|
34
34
|
data?: any;
|
|
35
35
|
};
|
|
36
|
+
export type BackupStatusLogConfig = string | false | {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
path: string;
|
|
39
|
+
};
|
|
36
40
|
export type BackupDefaultsConfig = {
|
|
37
41
|
workDir?: string;
|
|
38
42
|
outputDir?: string;
|
|
@@ -50,6 +54,7 @@ export type BackupTypeConfig = BackupDefaultsConfig & {
|
|
|
50
54
|
databases?: BackupDatabaseSourceConfig[];
|
|
51
55
|
outputs?: BackupOutputConfig[];
|
|
52
56
|
automation?: BackupAutomationConfig;
|
|
57
|
+
statusLog?: BackupStatusLogConfig;
|
|
53
58
|
};
|
|
54
59
|
export type BackupConfig = {
|
|
55
60
|
defaults?: BackupDefaultsConfig;
|
|
@@ -18,6 +18,11 @@ export type DataBuilderSchema = {
|
|
|
18
18
|
newDataHandler?: Function;
|
|
19
19
|
canDelete?: any;
|
|
20
20
|
};
|
|
21
|
+
export type DataBuilderOptionMapping = {
|
|
22
|
+
from: string;
|
|
23
|
+
value: string;
|
|
24
|
+
label: string;
|
|
25
|
+
};
|
|
21
26
|
export type DataBuilderType = {
|
|
22
27
|
key: string;
|
|
23
28
|
singular: string;
|
package/package.json
CHANGED