@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, `Copying ${incremental.filesToCopy.length} file(s) into backup work directory`);
520
- this.copyFiles(incremental.filesToCopy, contentDir, options);
521
- this.writeContentManifest(contentDir, incremental.manifest);
522
- this.log(options, "File copy finished");
523
- for (const source of normalizeArray(backupType.databases)) {
524
- this.log(options, `Backing up database '${source.connection || "default"}'`);
525
- result.databases.push(yield this.backupDatabase(source, contentDir, {
526
- backupKey: key,
527
- backupId: id,
528
- log: (message, payload) => this.log(options, message, payload),
529
- }));
530
- this.log(options, `Database '${source.connection || "default"}' backup finished`);
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
- result.artifact = this.buildArtifact(key, backupType, workRunDir, contentDir, now, id, options);
533
- result.artifact.manifest = incremental.manifest;
534
- const manifestPath = path_1.default.join(workRunDir, `${result.artifact.filename}.run-manifest.json`);
535
- result.manifestPath = manifestPath;
536
- for (const output of outputs) {
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
- folderPath: output.folderPath,
542
- });
543
- const outputResult = yield driver.write(result.artifact, output, {
544
- backupKey: key,
545
- backupId: id,
546
- retention: retention,
547
- log: (message, payload) => this.log(options, message, payload),
548
- });
549
- result.outputs.push(outputResult);
550
- this.log(options, `Output '${output.driver}' finished with status '${outputResult.status}'`, {
551
- path: outputResult.path,
552
- error: outputResult.error,
553
- });
554
- if (outputResult.status === "success" && (retention === null || retention === void 0 ? void 0 : retention.runAfterBackup)) {
555
- this.log(options, `Running cleanup for '${output.driver}' output`);
556
- result.cleanup.push(yield driver.cleanup(output, {
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
- result.endedAt = new Date().toISOString();
568
- const outputManifestPath = (_c = result.outputs.find((output) => output.payload && output.payload.manifestPath)) === null || _c === void 0 ? void 0 : _c.payload.manifestPath;
569
- if (outputManifestPath) {
570
- result.manifestPath = outputManifestPath;
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
- finally {
583
- if (backupType.cleanupWorkDir) {
584
- this.log(options, "Cleaning backup work directory");
585
- fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.56",
3
+ "version": "0.3.58",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",