s3db.js 8.0.3 → 8.1.0
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/s3db.cjs.js +362 -5
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.d.ts +1 -0
- package/dist/s3db.es.js +362 -5
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +362 -5
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +1 -1
- package/src/database.class.js +438 -4
- package/src/s3db.d.ts +1 -0
package/dist/s3db.d.ts
CHANGED
package/dist/s3db.es.js
CHANGED
|
@@ -13433,7 +13433,7 @@ class Database extends EventEmitter {
|
|
|
13433
13433
|
this.id = idGenerator(7);
|
|
13434
13434
|
this.version = "1";
|
|
13435
13435
|
this.s3dbVersion = (() => {
|
|
13436
|
-
const [ok, err, version] = try_fn_default(() => true ? "8.0
|
|
13436
|
+
const [ok, err, version] = try_fn_default(() => true ? "8.1.0" : "latest");
|
|
13437
13437
|
return ok ? version : "latest";
|
|
13438
13438
|
})();
|
|
13439
13439
|
this.resources = {};
|
|
@@ -13446,6 +13446,7 @@ class Database extends EventEmitter {
|
|
|
13446
13446
|
this.cache = options.cache;
|
|
13447
13447
|
this.passphrase = options.passphrase || "secret";
|
|
13448
13448
|
this.versioningEnabled = options.versioningEnabled || false;
|
|
13449
|
+
this.persistHooks = options.persistHooks || false;
|
|
13449
13450
|
this._initHooks();
|
|
13450
13451
|
let connectionString = options.connectionString;
|
|
13451
13452
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
@@ -13492,13 +13493,42 @@ class Database extends EventEmitter {
|
|
|
13492
13493
|
async connect() {
|
|
13493
13494
|
await this.startPlugins();
|
|
13494
13495
|
let metadata = null;
|
|
13496
|
+
let needsHealing = false;
|
|
13497
|
+
let healingLog = [];
|
|
13495
13498
|
if (await this.client.exists(`s3db.json`)) {
|
|
13496
|
-
|
|
13497
|
-
|
|
13499
|
+
try {
|
|
13500
|
+
const request = await this.client.getObject(`s3db.json`);
|
|
13501
|
+
const rawContent = await streamToString(request?.Body);
|
|
13502
|
+
try {
|
|
13503
|
+
metadata = JSON.parse(rawContent);
|
|
13504
|
+
} catch (parseError) {
|
|
13505
|
+
healingLog.push("JSON parsing failed - attempting recovery");
|
|
13506
|
+
needsHealing = true;
|
|
13507
|
+
metadata = await this._attemptJsonRecovery(rawContent, healingLog);
|
|
13508
|
+
if (!metadata) {
|
|
13509
|
+
await this._createCorruptedBackup(rawContent);
|
|
13510
|
+
healingLog.push("Created backup of corrupted file - starting with blank metadata");
|
|
13511
|
+
metadata = this.blankMetadataStructure();
|
|
13512
|
+
}
|
|
13513
|
+
}
|
|
13514
|
+
const healedMetadata = await this._validateAndHealMetadata(metadata, healingLog);
|
|
13515
|
+
if (healedMetadata !== metadata) {
|
|
13516
|
+
metadata = healedMetadata;
|
|
13517
|
+
needsHealing = true;
|
|
13518
|
+
}
|
|
13519
|
+
} catch (error) {
|
|
13520
|
+
healingLog.push(`Critical error reading s3db.json: ${error.message}`);
|
|
13521
|
+
await this._createCorruptedBackup();
|
|
13522
|
+
metadata = this.blankMetadataStructure();
|
|
13523
|
+
needsHealing = true;
|
|
13524
|
+
}
|
|
13498
13525
|
} else {
|
|
13499
13526
|
metadata = this.blankMetadataStructure();
|
|
13500
13527
|
await this.uploadMetadataFile();
|
|
13501
13528
|
}
|
|
13529
|
+
if (needsHealing) {
|
|
13530
|
+
await this._uploadHealedMetadata(metadata, healingLog);
|
|
13531
|
+
}
|
|
13502
13532
|
this.savedMetadata = metadata;
|
|
13503
13533
|
const definitionChanges = this.detectDefinitionChanges(metadata);
|
|
13504
13534
|
for (const [name, resourceMetadata] of Object.entries(metadata.resources || {})) {
|
|
@@ -13534,7 +13564,7 @@ class Database extends EventEmitter {
|
|
|
13534
13564
|
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
13535
13565
|
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
13536
13566
|
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
13537
|
-
hooks: versionData.hooks || {},
|
|
13567
|
+
hooks: this.persistHooks ? this._deserializeHooks(versionData.hooks || {}) : versionData.hooks || {},
|
|
13538
13568
|
versioningEnabled: this.versioningEnabled,
|
|
13539
13569
|
map: versionData.map,
|
|
13540
13570
|
idGenerator: restoredIdGenerator,
|
|
@@ -13629,6 +13659,73 @@ class Database extends EventEmitter {
|
|
|
13629
13659
|
const maxVersion = versionNumbers.length > 0 ? Math.max(...versionNumbers) : -1;
|
|
13630
13660
|
return `v${maxVersion + 1}`;
|
|
13631
13661
|
}
|
|
13662
|
+
/**
|
|
13663
|
+
* Serialize hooks to strings for JSON persistence
|
|
13664
|
+
* @param {Object} hooks - Hooks object with event names as keys and function arrays as values
|
|
13665
|
+
* @returns {Object} Serialized hooks object
|
|
13666
|
+
* @private
|
|
13667
|
+
*/
|
|
13668
|
+
_serializeHooks(hooks) {
|
|
13669
|
+
if (!hooks || typeof hooks !== "object") return hooks;
|
|
13670
|
+
const serialized = {};
|
|
13671
|
+
for (const [event, hookArray] of Object.entries(hooks)) {
|
|
13672
|
+
if (Array.isArray(hookArray)) {
|
|
13673
|
+
serialized[event] = hookArray.map((hook) => {
|
|
13674
|
+
if (typeof hook === "function") {
|
|
13675
|
+
try {
|
|
13676
|
+
return {
|
|
13677
|
+
__s3db_serialized_function: true,
|
|
13678
|
+
code: hook.toString(),
|
|
13679
|
+
name: hook.name || "anonymous"
|
|
13680
|
+
};
|
|
13681
|
+
} catch (err) {
|
|
13682
|
+
if (this.verbose) {
|
|
13683
|
+
console.warn(`Failed to serialize hook for event '${event}':`, err.message);
|
|
13684
|
+
}
|
|
13685
|
+
return null;
|
|
13686
|
+
}
|
|
13687
|
+
}
|
|
13688
|
+
return hook;
|
|
13689
|
+
});
|
|
13690
|
+
} else {
|
|
13691
|
+
serialized[event] = hookArray;
|
|
13692
|
+
}
|
|
13693
|
+
}
|
|
13694
|
+
return serialized;
|
|
13695
|
+
}
|
|
13696
|
+
/**
|
|
13697
|
+
* Deserialize hooks from strings back to functions
|
|
13698
|
+
* @param {Object} serializedHooks - Serialized hooks object
|
|
13699
|
+
* @returns {Object} Deserialized hooks object
|
|
13700
|
+
* @private
|
|
13701
|
+
*/
|
|
13702
|
+
_deserializeHooks(serializedHooks) {
|
|
13703
|
+
if (!serializedHooks || typeof serializedHooks !== "object") return serializedHooks;
|
|
13704
|
+
const deserialized = {};
|
|
13705
|
+
for (const [event, hookArray] of Object.entries(serializedHooks)) {
|
|
13706
|
+
if (Array.isArray(hookArray)) {
|
|
13707
|
+
deserialized[event] = hookArray.map((hook) => {
|
|
13708
|
+
if (hook && typeof hook === "object" && hook.__s3db_serialized_function) {
|
|
13709
|
+
try {
|
|
13710
|
+
const fn = new Function("return " + hook.code)();
|
|
13711
|
+
if (typeof fn === "function") {
|
|
13712
|
+
return fn;
|
|
13713
|
+
}
|
|
13714
|
+
} catch (err) {
|
|
13715
|
+
if (this.verbose) {
|
|
13716
|
+
console.warn(`Failed to deserialize hook '${hook.name}' for event '${event}':`, err.message);
|
|
13717
|
+
}
|
|
13718
|
+
}
|
|
13719
|
+
return null;
|
|
13720
|
+
}
|
|
13721
|
+
return hook;
|
|
13722
|
+
}).filter((hook) => hook !== null);
|
|
13723
|
+
} else {
|
|
13724
|
+
deserialized[event] = hookArray;
|
|
13725
|
+
}
|
|
13726
|
+
}
|
|
13727
|
+
return deserialized;
|
|
13728
|
+
}
|
|
13632
13729
|
async startPlugins() {
|
|
13633
13730
|
const db = this;
|
|
13634
13731
|
if (!isEmpty(this.pluginList)) {
|
|
@@ -13698,7 +13795,7 @@ class Database extends EventEmitter {
|
|
|
13698
13795
|
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
13699
13796
|
autoDecrypt: resource.config.autoDecrypt,
|
|
13700
13797
|
cache: resource.config.cache,
|
|
13701
|
-
hooks: resource.config.hooks,
|
|
13798
|
+
hooks: this.persistHooks ? this._serializeHooks(resource.config.hooks) : resource.config.hooks,
|
|
13702
13799
|
idSize: resource.idSize,
|
|
13703
13800
|
idGenerator: resource.idGeneratorType,
|
|
13704
13801
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
@@ -13722,9 +13819,269 @@ class Database extends EventEmitter {
|
|
|
13722
13819
|
return {
|
|
13723
13820
|
version: `1`,
|
|
13724
13821
|
s3dbVersion: this.s3dbVersion,
|
|
13822
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13725
13823
|
resources: {}
|
|
13726
13824
|
};
|
|
13727
13825
|
}
|
|
13826
|
+
/**
|
|
13827
|
+
* Attempt to recover JSON from corrupted content
|
|
13828
|
+
*/
|
|
13829
|
+
async _attemptJsonRecovery(content, healingLog) {
|
|
13830
|
+
if (!content || typeof content !== "string") {
|
|
13831
|
+
healingLog.push("Content is empty or not a string");
|
|
13832
|
+
return null;
|
|
13833
|
+
}
|
|
13834
|
+
const fixes = [
|
|
13835
|
+
// Remove trailing commas
|
|
13836
|
+
() => content.replace(/,(\s*[}\]])/g, "$1"),
|
|
13837
|
+
// Add missing quotes to keys
|
|
13838
|
+
() => content.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":'),
|
|
13839
|
+
// Fix incomplete objects by adding closing braces
|
|
13840
|
+
() => {
|
|
13841
|
+
let openBraces = 0;
|
|
13842
|
+
let openBrackets = 0;
|
|
13843
|
+
let inString = false;
|
|
13844
|
+
let escaped = false;
|
|
13845
|
+
for (let i = 0; i < content.length; i++) {
|
|
13846
|
+
const char = content[i];
|
|
13847
|
+
if (escaped) {
|
|
13848
|
+
escaped = false;
|
|
13849
|
+
continue;
|
|
13850
|
+
}
|
|
13851
|
+
if (char === "\\") {
|
|
13852
|
+
escaped = true;
|
|
13853
|
+
continue;
|
|
13854
|
+
}
|
|
13855
|
+
if (char === '"') {
|
|
13856
|
+
inString = !inString;
|
|
13857
|
+
continue;
|
|
13858
|
+
}
|
|
13859
|
+
if (!inString) {
|
|
13860
|
+
if (char === "{") openBraces++;
|
|
13861
|
+
else if (char === "}") openBraces--;
|
|
13862
|
+
else if (char === "[") openBrackets++;
|
|
13863
|
+
else if (char === "]") openBrackets--;
|
|
13864
|
+
}
|
|
13865
|
+
}
|
|
13866
|
+
let fixed = content;
|
|
13867
|
+
while (openBrackets > 0) {
|
|
13868
|
+
fixed += "]";
|
|
13869
|
+
openBrackets--;
|
|
13870
|
+
}
|
|
13871
|
+
while (openBraces > 0) {
|
|
13872
|
+
fixed += "}";
|
|
13873
|
+
openBraces--;
|
|
13874
|
+
}
|
|
13875
|
+
return fixed;
|
|
13876
|
+
}
|
|
13877
|
+
];
|
|
13878
|
+
for (const [index, fix] of fixes.entries()) {
|
|
13879
|
+
try {
|
|
13880
|
+
const fixedContent = fix();
|
|
13881
|
+
const parsed = JSON.parse(fixedContent);
|
|
13882
|
+
healingLog.push(`JSON recovery successful using fix #${index + 1}`);
|
|
13883
|
+
return parsed;
|
|
13884
|
+
} catch (error) {
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13887
|
+
healingLog.push("All JSON recovery attempts failed");
|
|
13888
|
+
return null;
|
|
13889
|
+
}
|
|
13890
|
+
/**
|
|
13891
|
+
* Validate and heal metadata structure
|
|
13892
|
+
*/
|
|
13893
|
+
async _validateAndHealMetadata(metadata, healingLog) {
|
|
13894
|
+
if (!metadata || typeof metadata !== "object") {
|
|
13895
|
+
healingLog.push("Metadata is not an object - using blank structure");
|
|
13896
|
+
return this.blankMetadataStructure();
|
|
13897
|
+
}
|
|
13898
|
+
let healed = { ...metadata };
|
|
13899
|
+
let changed = false;
|
|
13900
|
+
if (!healed.version || typeof healed.version !== "string") {
|
|
13901
|
+
if (healed.version && typeof healed.version === "number") {
|
|
13902
|
+
healed.version = String(healed.version);
|
|
13903
|
+
healingLog.push("Converted version from number to string");
|
|
13904
|
+
changed = true;
|
|
13905
|
+
} else {
|
|
13906
|
+
healed.version = "1";
|
|
13907
|
+
healingLog.push("Added missing or invalid version field");
|
|
13908
|
+
changed = true;
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
if (!healed.s3dbVersion || typeof healed.s3dbVersion !== "string") {
|
|
13912
|
+
if (healed.s3dbVersion && typeof healed.s3dbVersion !== "string") {
|
|
13913
|
+
healed.s3dbVersion = String(healed.s3dbVersion);
|
|
13914
|
+
healingLog.push("Converted s3dbVersion to string");
|
|
13915
|
+
changed = true;
|
|
13916
|
+
} else {
|
|
13917
|
+
healed.s3dbVersion = this.s3dbVersion;
|
|
13918
|
+
healingLog.push("Added missing s3dbVersion field");
|
|
13919
|
+
changed = true;
|
|
13920
|
+
}
|
|
13921
|
+
}
|
|
13922
|
+
if (!healed.resources || typeof healed.resources !== "object" || Array.isArray(healed.resources)) {
|
|
13923
|
+
healed.resources = {};
|
|
13924
|
+
healingLog.push("Fixed invalid resources field");
|
|
13925
|
+
changed = true;
|
|
13926
|
+
}
|
|
13927
|
+
if (!healed.lastUpdated) {
|
|
13928
|
+
healed.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
13929
|
+
healingLog.push("Added missing lastUpdated field");
|
|
13930
|
+
changed = true;
|
|
13931
|
+
}
|
|
13932
|
+
const validResources = {};
|
|
13933
|
+
for (const [name, resource] of Object.entries(healed.resources)) {
|
|
13934
|
+
const healedResource = this._healResourceStructure(name, resource, healingLog);
|
|
13935
|
+
if (healedResource) {
|
|
13936
|
+
validResources[name] = healedResource;
|
|
13937
|
+
if (healedResource !== resource) {
|
|
13938
|
+
changed = true;
|
|
13939
|
+
}
|
|
13940
|
+
} else {
|
|
13941
|
+
healingLog.push(`Removed invalid resource: ${name}`);
|
|
13942
|
+
changed = true;
|
|
13943
|
+
}
|
|
13944
|
+
}
|
|
13945
|
+
healed.resources = validResources;
|
|
13946
|
+
return changed ? healed : metadata;
|
|
13947
|
+
}
|
|
13948
|
+
/**
|
|
13949
|
+
* Heal individual resource structure
|
|
13950
|
+
*/
|
|
13951
|
+
_healResourceStructure(name, resource, healingLog) {
|
|
13952
|
+
if (!resource || typeof resource !== "object") {
|
|
13953
|
+
healingLog.push(`Resource ${name}: invalid structure`);
|
|
13954
|
+
return null;
|
|
13955
|
+
}
|
|
13956
|
+
let healed = { ...resource };
|
|
13957
|
+
let changed = false;
|
|
13958
|
+
if (!healed.currentVersion) {
|
|
13959
|
+
healed.currentVersion = "v0";
|
|
13960
|
+
healingLog.push(`Resource ${name}: added missing currentVersion`);
|
|
13961
|
+
changed = true;
|
|
13962
|
+
}
|
|
13963
|
+
if (!healed.versions || typeof healed.versions !== "object" || Array.isArray(healed.versions)) {
|
|
13964
|
+
healed.versions = {};
|
|
13965
|
+
healingLog.push(`Resource ${name}: fixed invalid versions object`);
|
|
13966
|
+
changed = true;
|
|
13967
|
+
}
|
|
13968
|
+
if (!healed.partitions || typeof healed.partitions !== "object" || Array.isArray(healed.partitions)) {
|
|
13969
|
+
healed.partitions = {};
|
|
13970
|
+
healingLog.push(`Resource ${name}: fixed invalid partitions object`);
|
|
13971
|
+
changed = true;
|
|
13972
|
+
}
|
|
13973
|
+
const currentVersion = healed.currentVersion;
|
|
13974
|
+
if (!healed.versions[currentVersion]) {
|
|
13975
|
+
const availableVersions = Object.keys(healed.versions);
|
|
13976
|
+
if (availableVersions.length > 0) {
|
|
13977
|
+
healed.currentVersion = availableVersions[0];
|
|
13978
|
+
healingLog.push(`Resource ${name}: changed currentVersion from ${currentVersion} to ${healed.currentVersion}`);
|
|
13979
|
+
changed = true;
|
|
13980
|
+
} else {
|
|
13981
|
+
healingLog.push(`Resource ${name}: no valid versions found - removing resource`);
|
|
13982
|
+
return null;
|
|
13983
|
+
}
|
|
13984
|
+
}
|
|
13985
|
+
const versionData = healed.versions[healed.currentVersion];
|
|
13986
|
+
if (!versionData || typeof versionData !== "object") {
|
|
13987
|
+
healingLog.push(`Resource ${name}: invalid version data - removing resource`);
|
|
13988
|
+
return null;
|
|
13989
|
+
}
|
|
13990
|
+
if (!versionData.attributes || typeof versionData.attributes !== "object") {
|
|
13991
|
+
healingLog.push(`Resource ${name}: missing or invalid attributes - removing resource`);
|
|
13992
|
+
return null;
|
|
13993
|
+
}
|
|
13994
|
+
if (versionData.hooks) {
|
|
13995
|
+
const healedHooks = this._healHooksStructure(versionData.hooks, name, healingLog);
|
|
13996
|
+
if (healedHooks !== versionData.hooks) {
|
|
13997
|
+
healed.versions[healed.currentVersion].hooks = healedHooks;
|
|
13998
|
+
changed = true;
|
|
13999
|
+
}
|
|
14000
|
+
}
|
|
14001
|
+
return changed ? healed : resource;
|
|
14002
|
+
}
|
|
14003
|
+
/**
|
|
14004
|
+
* Heal hooks structure
|
|
14005
|
+
*/
|
|
14006
|
+
_healHooksStructure(hooks, resourceName, healingLog) {
|
|
14007
|
+
if (!hooks || typeof hooks !== "object") {
|
|
14008
|
+
healingLog.push(`Resource ${resourceName}: invalid hooks structure - using empty hooks`);
|
|
14009
|
+
return {};
|
|
14010
|
+
}
|
|
14011
|
+
const healed = {};
|
|
14012
|
+
let changed = false;
|
|
14013
|
+
for (const [event, hookArray] of Object.entries(hooks)) {
|
|
14014
|
+
if (Array.isArray(hookArray)) {
|
|
14015
|
+
const validHooks = hookArray.filter(
|
|
14016
|
+
(hook) => hook !== null && hook !== void 0 && hook !== ""
|
|
14017
|
+
);
|
|
14018
|
+
healed[event] = validHooks;
|
|
14019
|
+
if (validHooks.length !== hookArray.length) {
|
|
14020
|
+
healingLog.push(`Resource ${resourceName}: cleaned invalid hooks for event ${event}`);
|
|
14021
|
+
changed = true;
|
|
14022
|
+
}
|
|
14023
|
+
} else {
|
|
14024
|
+
healingLog.push(`Resource ${resourceName}: hooks for event ${event} is not an array - removing`);
|
|
14025
|
+
changed = true;
|
|
14026
|
+
}
|
|
14027
|
+
}
|
|
14028
|
+
return changed ? healed : hooks;
|
|
14029
|
+
}
|
|
14030
|
+
/**
|
|
14031
|
+
* Create backup of corrupted file
|
|
14032
|
+
*/
|
|
14033
|
+
async _createCorruptedBackup(content = null) {
|
|
14034
|
+
try {
|
|
14035
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
14036
|
+
const backupKey = `s3db.json.corrupted.${timestamp}.backup`;
|
|
14037
|
+
if (!content) {
|
|
14038
|
+
try {
|
|
14039
|
+
const request = await this.client.getObject(`s3db.json`);
|
|
14040
|
+
content = await streamToString(request?.Body);
|
|
14041
|
+
} catch (error) {
|
|
14042
|
+
content = "Unable to read corrupted file content";
|
|
14043
|
+
}
|
|
14044
|
+
}
|
|
14045
|
+
await this.client.putObject({
|
|
14046
|
+
key: backupKey,
|
|
14047
|
+
body: content,
|
|
14048
|
+
contentType: "application/json"
|
|
14049
|
+
});
|
|
14050
|
+
if (this.verbose) {
|
|
14051
|
+
console.warn(`S3DB: Created backup of corrupted s3db.json as ${backupKey}`);
|
|
14052
|
+
}
|
|
14053
|
+
} catch (error) {
|
|
14054
|
+
if (this.verbose) {
|
|
14055
|
+
console.warn(`S3DB: Failed to create backup: ${error.message}`);
|
|
14056
|
+
}
|
|
14057
|
+
}
|
|
14058
|
+
}
|
|
14059
|
+
/**
|
|
14060
|
+
* Upload healed metadata with logging
|
|
14061
|
+
*/
|
|
14062
|
+
async _uploadHealedMetadata(metadata, healingLog) {
|
|
14063
|
+
try {
|
|
14064
|
+
if (this.verbose && healingLog.length > 0) {
|
|
14065
|
+
console.warn("S3DB Self-Healing Operations:");
|
|
14066
|
+
healingLog.forEach((log) => console.warn(` - ${log}`));
|
|
14067
|
+
}
|
|
14068
|
+
metadata.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
14069
|
+
await this.client.putObject({
|
|
14070
|
+
key: "s3db.json",
|
|
14071
|
+
body: JSON.stringify(metadata, null, 2),
|
|
14072
|
+
contentType: "application/json"
|
|
14073
|
+
});
|
|
14074
|
+
this.emit("metadataHealed", { healingLog, metadata });
|
|
14075
|
+
if (this.verbose) {
|
|
14076
|
+
console.warn("S3DB: Successfully uploaded healed metadata");
|
|
14077
|
+
}
|
|
14078
|
+
} catch (error) {
|
|
14079
|
+
if (this.verbose) {
|
|
14080
|
+
console.error(`S3DB: Failed to upload healed metadata: ${error.message}`);
|
|
14081
|
+
}
|
|
14082
|
+
throw error;
|
|
14083
|
+
}
|
|
14084
|
+
}
|
|
13728
14085
|
/**
|
|
13729
14086
|
* Check if a resource exists by name
|
|
13730
14087
|
* @param {string} name - Resource name
|