s3db.js 8.0.2 → 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 +374 -8
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.d.ts +1 -0
- package/dist/s3db.es.js +374 -8
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +374 -8
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +1 -1
- package/src/client.class.js +1 -1
- package/src/database.class.js +441 -5
- package/src/resource.class.js +11 -5
- package/src/s3db.d.ts +1 -0
package/dist/s3db.iife.js
CHANGED
|
@@ -9651,7 +9651,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
9651
9651
|
}) {
|
|
9652
9652
|
super();
|
|
9653
9653
|
this.verbose = verbose;
|
|
9654
|
-
this.id = id ?? idGenerator();
|
|
9654
|
+
this.id = id ?? idGenerator(77);
|
|
9655
9655
|
this.parallelism = parallelism;
|
|
9656
9656
|
this.config = new ConnectionString(connectionString);
|
|
9657
9657
|
this.httpClientOptions = {
|
|
@@ -11238,10 +11238,18 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
11238
11238
|
*/
|
|
11239
11239
|
constructor(config = {}) {
|
|
11240
11240
|
super();
|
|
11241
|
-
this._instanceId =
|
|
11241
|
+
this._instanceId = idGenerator(7);
|
|
11242
11242
|
const validation = validateResourceConfig(config);
|
|
11243
11243
|
if (!validation.isValid) {
|
|
11244
|
-
|
|
11244
|
+
const errorDetails = validation.errors.map((err) => ` \u2022 ${err}`).join("\n");
|
|
11245
|
+
throw new ResourceError(
|
|
11246
|
+
`Invalid Resource ${config.name || "[unnamed]"} configuration:
|
|
11247
|
+
${errorDetails}`,
|
|
11248
|
+
{
|
|
11249
|
+
resourceName: config.name,
|
|
11250
|
+
validation: validation.errors
|
|
11251
|
+
}
|
|
11252
|
+
);
|
|
11245
11253
|
}
|
|
11246
11254
|
const {
|
|
11247
11255
|
name,
|
|
@@ -13412,9 +13420,10 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13412
13420
|
class Database extends EventEmitter {
|
|
13413
13421
|
constructor(options) {
|
|
13414
13422
|
super();
|
|
13423
|
+
this.id = idGenerator(7);
|
|
13415
13424
|
this.version = "1";
|
|
13416
13425
|
this.s3dbVersion = (() => {
|
|
13417
|
-
const [ok, err, version] = try_fn_default(() => true ? "8.0
|
|
13426
|
+
const [ok, err, version] = try_fn_default(() => true ? "8.1.0" : "latest");
|
|
13418
13427
|
return ok ? version : "latest";
|
|
13419
13428
|
})();
|
|
13420
13429
|
this.resources = {};
|
|
@@ -13427,6 +13436,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13427
13436
|
this.cache = options.cache;
|
|
13428
13437
|
this.passphrase = options.passphrase || "secret";
|
|
13429
13438
|
this.versioningEnabled = options.versioningEnabled || false;
|
|
13439
|
+
this.persistHooks = options.persistHooks || false;
|
|
13430
13440
|
this._initHooks();
|
|
13431
13441
|
let connectionString = options.connectionString;
|
|
13432
13442
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
@@ -13473,13 +13483,42 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13473
13483
|
async connect() {
|
|
13474
13484
|
await this.startPlugins();
|
|
13475
13485
|
let metadata = null;
|
|
13486
|
+
let needsHealing = false;
|
|
13487
|
+
let healingLog = [];
|
|
13476
13488
|
if (await this.client.exists(`s3db.json`)) {
|
|
13477
|
-
|
|
13478
|
-
|
|
13489
|
+
try {
|
|
13490
|
+
const request = await this.client.getObject(`s3db.json`);
|
|
13491
|
+
const rawContent = await streamToString(request?.Body);
|
|
13492
|
+
try {
|
|
13493
|
+
metadata = JSON.parse(rawContent);
|
|
13494
|
+
} catch (parseError) {
|
|
13495
|
+
healingLog.push("JSON parsing failed - attempting recovery");
|
|
13496
|
+
needsHealing = true;
|
|
13497
|
+
metadata = await this._attemptJsonRecovery(rawContent, healingLog);
|
|
13498
|
+
if (!metadata) {
|
|
13499
|
+
await this._createCorruptedBackup(rawContent);
|
|
13500
|
+
healingLog.push("Created backup of corrupted file - starting with blank metadata");
|
|
13501
|
+
metadata = this.blankMetadataStructure();
|
|
13502
|
+
}
|
|
13503
|
+
}
|
|
13504
|
+
const healedMetadata = await this._validateAndHealMetadata(metadata, healingLog);
|
|
13505
|
+
if (healedMetadata !== metadata) {
|
|
13506
|
+
metadata = healedMetadata;
|
|
13507
|
+
needsHealing = true;
|
|
13508
|
+
}
|
|
13509
|
+
} catch (error) {
|
|
13510
|
+
healingLog.push(`Critical error reading s3db.json: ${error.message}`);
|
|
13511
|
+
await this._createCorruptedBackup();
|
|
13512
|
+
metadata = this.blankMetadataStructure();
|
|
13513
|
+
needsHealing = true;
|
|
13514
|
+
}
|
|
13479
13515
|
} else {
|
|
13480
13516
|
metadata = this.blankMetadataStructure();
|
|
13481
13517
|
await this.uploadMetadataFile();
|
|
13482
13518
|
}
|
|
13519
|
+
if (needsHealing) {
|
|
13520
|
+
await this._uploadHealedMetadata(metadata, healingLog);
|
|
13521
|
+
}
|
|
13483
13522
|
this.savedMetadata = metadata;
|
|
13484
13523
|
const definitionChanges = this.detectDefinitionChanges(metadata);
|
|
13485
13524
|
for (const [name, resourceMetadata] of Object.entries(metadata.resources || {})) {
|
|
@@ -13515,7 +13554,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13515
13554
|
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
13516
13555
|
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
13517
13556
|
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
13518
|
-
hooks: versionData.hooks || {},
|
|
13557
|
+
hooks: this.persistHooks ? this._deserializeHooks(versionData.hooks || {}) : versionData.hooks || {},
|
|
13519
13558
|
versioningEnabled: this.versioningEnabled,
|
|
13520
13559
|
map: versionData.map,
|
|
13521
13560
|
idGenerator: restoredIdGenerator,
|
|
@@ -13610,6 +13649,73 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13610
13649
|
const maxVersion = versionNumbers.length > 0 ? Math.max(...versionNumbers) : -1;
|
|
13611
13650
|
return `v${maxVersion + 1}`;
|
|
13612
13651
|
}
|
|
13652
|
+
/**
|
|
13653
|
+
* Serialize hooks to strings for JSON persistence
|
|
13654
|
+
* @param {Object} hooks - Hooks object with event names as keys and function arrays as values
|
|
13655
|
+
* @returns {Object} Serialized hooks object
|
|
13656
|
+
* @private
|
|
13657
|
+
*/
|
|
13658
|
+
_serializeHooks(hooks) {
|
|
13659
|
+
if (!hooks || typeof hooks !== "object") return hooks;
|
|
13660
|
+
const serialized = {};
|
|
13661
|
+
for (const [event, hookArray] of Object.entries(hooks)) {
|
|
13662
|
+
if (Array.isArray(hookArray)) {
|
|
13663
|
+
serialized[event] = hookArray.map((hook) => {
|
|
13664
|
+
if (typeof hook === "function") {
|
|
13665
|
+
try {
|
|
13666
|
+
return {
|
|
13667
|
+
__s3db_serialized_function: true,
|
|
13668
|
+
code: hook.toString(),
|
|
13669
|
+
name: hook.name || "anonymous"
|
|
13670
|
+
};
|
|
13671
|
+
} catch (err) {
|
|
13672
|
+
if (this.verbose) {
|
|
13673
|
+
console.warn(`Failed to serialize hook for event '${event}':`, err.message);
|
|
13674
|
+
}
|
|
13675
|
+
return null;
|
|
13676
|
+
}
|
|
13677
|
+
}
|
|
13678
|
+
return hook;
|
|
13679
|
+
});
|
|
13680
|
+
} else {
|
|
13681
|
+
serialized[event] = hookArray;
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
return serialized;
|
|
13685
|
+
}
|
|
13686
|
+
/**
|
|
13687
|
+
* Deserialize hooks from strings back to functions
|
|
13688
|
+
* @param {Object} serializedHooks - Serialized hooks object
|
|
13689
|
+
* @returns {Object} Deserialized hooks object
|
|
13690
|
+
* @private
|
|
13691
|
+
*/
|
|
13692
|
+
_deserializeHooks(serializedHooks) {
|
|
13693
|
+
if (!serializedHooks || typeof serializedHooks !== "object") return serializedHooks;
|
|
13694
|
+
const deserialized = {};
|
|
13695
|
+
for (const [event, hookArray] of Object.entries(serializedHooks)) {
|
|
13696
|
+
if (Array.isArray(hookArray)) {
|
|
13697
|
+
deserialized[event] = hookArray.map((hook) => {
|
|
13698
|
+
if (hook && typeof hook === "object" && hook.__s3db_serialized_function) {
|
|
13699
|
+
try {
|
|
13700
|
+
const fn = new Function("return " + hook.code)();
|
|
13701
|
+
if (typeof fn === "function") {
|
|
13702
|
+
return fn;
|
|
13703
|
+
}
|
|
13704
|
+
} catch (err) {
|
|
13705
|
+
if (this.verbose) {
|
|
13706
|
+
console.warn(`Failed to deserialize hook '${hook.name}' for event '${event}':`, err.message);
|
|
13707
|
+
}
|
|
13708
|
+
}
|
|
13709
|
+
return null;
|
|
13710
|
+
}
|
|
13711
|
+
return hook;
|
|
13712
|
+
}).filter((hook) => hook !== null);
|
|
13713
|
+
} else {
|
|
13714
|
+
deserialized[event] = hookArray;
|
|
13715
|
+
}
|
|
13716
|
+
}
|
|
13717
|
+
return deserialized;
|
|
13718
|
+
}
|
|
13613
13719
|
async startPlugins() {
|
|
13614
13720
|
const db = this;
|
|
13615
13721
|
if (!lodashEs.isEmpty(this.pluginList)) {
|
|
@@ -13679,7 +13785,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13679
13785
|
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
13680
13786
|
autoDecrypt: resource.config.autoDecrypt,
|
|
13681
13787
|
cache: resource.config.cache,
|
|
13682
|
-
hooks: resource.config.hooks,
|
|
13788
|
+
hooks: this.persistHooks ? this._serializeHooks(resource.config.hooks) : resource.config.hooks,
|
|
13683
13789
|
idSize: resource.idSize,
|
|
13684
13790
|
idGenerator: resource.idGeneratorType,
|
|
13685
13791
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
@@ -13703,9 +13809,269 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13703
13809
|
return {
|
|
13704
13810
|
version: `1`,
|
|
13705
13811
|
s3dbVersion: this.s3dbVersion,
|
|
13812
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13706
13813
|
resources: {}
|
|
13707
13814
|
};
|
|
13708
13815
|
}
|
|
13816
|
+
/**
|
|
13817
|
+
* Attempt to recover JSON from corrupted content
|
|
13818
|
+
*/
|
|
13819
|
+
async _attemptJsonRecovery(content, healingLog) {
|
|
13820
|
+
if (!content || typeof content !== "string") {
|
|
13821
|
+
healingLog.push("Content is empty or not a string");
|
|
13822
|
+
return null;
|
|
13823
|
+
}
|
|
13824
|
+
const fixes = [
|
|
13825
|
+
// Remove trailing commas
|
|
13826
|
+
() => content.replace(/,(\s*[}\]])/g, "$1"),
|
|
13827
|
+
// Add missing quotes to keys
|
|
13828
|
+
() => content.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":'),
|
|
13829
|
+
// Fix incomplete objects by adding closing braces
|
|
13830
|
+
() => {
|
|
13831
|
+
let openBraces = 0;
|
|
13832
|
+
let openBrackets = 0;
|
|
13833
|
+
let inString = false;
|
|
13834
|
+
let escaped = false;
|
|
13835
|
+
for (let i = 0; i < content.length; i++) {
|
|
13836
|
+
const char = content[i];
|
|
13837
|
+
if (escaped) {
|
|
13838
|
+
escaped = false;
|
|
13839
|
+
continue;
|
|
13840
|
+
}
|
|
13841
|
+
if (char === "\\") {
|
|
13842
|
+
escaped = true;
|
|
13843
|
+
continue;
|
|
13844
|
+
}
|
|
13845
|
+
if (char === '"') {
|
|
13846
|
+
inString = !inString;
|
|
13847
|
+
continue;
|
|
13848
|
+
}
|
|
13849
|
+
if (!inString) {
|
|
13850
|
+
if (char === "{") openBraces++;
|
|
13851
|
+
else if (char === "}") openBraces--;
|
|
13852
|
+
else if (char === "[") openBrackets++;
|
|
13853
|
+
else if (char === "]") openBrackets--;
|
|
13854
|
+
}
|
|
13855
|
+
}
|
|
13856
|
+
let fixed = content;
|
|
13857
|
+
while (openBrackets > 0) {
|
|
13858
|
+
fixed += "]";
|
|
13859
|
+
openBrackets--;
|
|
13860
|
+
}
|
|
13861
|
+
while (openBraces > 0) {
|
|
13862
|
+
fixed += "}";
|
|
13863
|
+
openBraces--;
|
|
13864
|
+
}
|
|
13865
|
+
return fixed;
|
|
13866
|
+
}
|
|
13867
|
+
];
|
|
13868
|
+
for (const [index, fix] of fixes.entries()) {
|
|
13869
|
+
try {
|
|
13870
|
+
const fixedContent = fix();
|
|
13871
|
+
const parsed = JSON.parse(fixedContent);
|
|
13872
|
+
healingLog.push(`JSON recovery successful using fix #${index + 1}`);
|
|
13873
|
+
return parsed;
|
|
13874
|
+
} catch (error) {
|
|
13875
|
+
}
|
|
13876
|
+
}
|
|
13877
|
+
healingLog.push("All JSON recovery attempts failed");
|
|
13878
|
+
return null;
|
|
13879
|
+
}
|
|
13880
|
+
/**
|
|
13881
|
+
* Validate and heal metadata structure
|
|
13882
|
+
*/
|
|
13883
|
+
async _validateAndHealMetadata(metadata, healingLog) {
|
|
13884
|
+
if (!metadata || typeof metadata !== "object") {
|
|
13885
|
+
healingLog.push("Metadata is not an object - using blank structure");
|
|
13886
|
+
return this.blankMetadataStructure();
|
|
13887
|
+
}
|
|
13888
|
+
let healed = { ...metadata };
|
|
13889
|
+
let changed = false;
|
|
13890
|
+
if (!healed.version || typeof healed.version !== "string") {
|
|
13891
|
+
if (healed.version && typeof healed.version === "number") {
|
|
13892
|
+
healed.version = String(healed.version);
|
|
13893
|
+
healingLog.push("Converted version from number to string");
|
|
13894
|
+
changed = true;
|
|
13895
|
+
} else {
|
|
13896
|
+
healed.version = "1";
|
|
13897
|
+
healingLog.push("Added missing or invalid version field");
|
|
13898
|
+
changed = true;
|
|
13899
|
+
}
|
|
13900
|
+
}
|
|
13901
|
+
if (!healed.s3dbVersion || typeof healed.s3dbVersion !== "string") {
|
|
13902
|
+
if (healed.s3dbVersion && typeof healed.s3dbVersion !== "string") {
|
|
13903
|
+
healed.s3dbVersion = String(healed.s3dbVersion);
|
|
13904
|
+
healingLog.push("Converted s3dbVersion to string");
|
|
13905
|
+
changed = true;
|
|
13906
|
+
} else {
|
|
13907
|
+
healed.s3dbVersion = this.s3dbVersion;
|
|
13908
|
+
healingLog.push("Added missing s3dbVersion field");
|
|
13909
|
+
changed = true;
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
if (!healed.resources || typeof healed.resources !== "object" || Array.isArray(healed.resources)) {
|
|
13913
|
+
healed.resources = {};
|
|
13914
|
+
healingLog.push("Fixed invalid resources field");
|
|
13915
|
+
changed = true;
|
|
13916
|
+
}
|
|
13917
|
+
if (!healed.lastUpdated) {
|
|
13918
|
+
healed.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
13919
|
+
healingLog.push("Added missing lastUpdated field");
|
|
13920
|
+
changed = true;
|
|
13921
|
+
}
|
|
13922
|
+
const validResources = {};
|
|
13923
|
+
for (const [name, resource] of Object.entries(healed.resources)) {
|
|
13924
|
+
const healedResource = this._healResourceStructure(name, resource, healingLog);
|
|
13925
|
+
if (healedResource) {
|
|
13926
|
+
validResources[name] = healedResource;
|
|
13927
|
+
if (healedResource !== resource) {
|
|
13928
|
+
changed = true;
|
|
13929
|
+
}
|
|
13930
|
+
} else {
|
|
13931
|
+
healingLog.push(`Removed invalid resource: ${name}`);
|
|
13932
|
+
changed = true;
|
|
13933
|
+
}
|
|
13934
|
+
}
|
|
13935
|
+
healed.resources = validResources;
|
|
13936
|
+
return changed ? healed : metadata;
|
|
13937
|
+
}
|
|
13938
|
+
/**
|
|
13939
|
+
* Heal individual resource structure
|
|
13940
|
+
*/
|
|
13941
|
+
_healResourceStructure(name, resource, healingLog) {
|
|
13942
|
+
if (!resource || typeof resource !== "object") {
|
|
13943
|
+
healingLog.push(`Resource ${name}: invalid structure`);
|
|
13944
|
+
return null;
|
|
13945
|
+
}
|
|
13946
|
+
let healed = { ...resource };
|
|
13947
|
+
let changed = false;
|
|
13948
|
+
if (!healed.currentVersion) {
|
|
13949
|
+
healed.currentVersion = "v0";
|
|
13950
|
+
healingLog.push(`Resource ${name}: added missing currentVersion`);
|
|
13951
|
+
changed = true;
|
|
13952
|
+
}
|
|
13953
|
+
if (!healed.versions || typeof healed.versions !== "object" || Array.isArray(healed.versions)) {
|
|
13954
|
+
healed.versions = {};
|
|
13955
|
+
healingLog.push(`Resource ${name}: fixed invalid versions object`);
|
|
13956
|
+
changed = true;
|
|
13957
|
+
}
|
|
13958
|
+
if (!healed.partitions || typeof healed.partitions !== "object" || Array.isArray(healed.partitions)) {
|
|
13959
|
+
healed.partitions = {};
|
|
13960
|
+
healingLog.push(`Resource ${name}: fixed invalid partitions object`);
|
|
13961
|
+
changed = true;
|
|
13962
|
+
}
|
|
13963
|
+
const currentVersion = healed.currentVersion;
|
|
13964
|
+
if (!healed.versions[currentVersion]) {
|
|
13965
|
+
const availableVersions = Object.keys(healed.versions);
|
|
13966
|
+
if (availableVersions.length > 0) {
|
|
13967
|
+
healed.currentVersion = availableVersions[0];
|
|
13968
|
+
healingLog.push(`Resource ${name}: changed currentVersion from ${currentVersion} to ${healed.currentVersion}`);
|
|
13969
|
+
changed = true;
|
|
13970
|
+
} else {
|
|
13971
|
+
healingLog.push(`Resource ${name}: no valid versions found - removing resource`);
|
|
13972
|
+
return null;
|
|
13973
|
+
}
|
|
13974
|
+
}
|
|
13975
|
+
const versionData = healed.versions[healed.currentVersion];
|
|
13976
|
+
if (!versionData || typeof versionData !== "object") {
|
|
13977
|
+
healingLog.push(`Resource ${name}: invalid version data - removing resource`);
|
|
13978
|
+
return null;
|
|
13979
|
+
}
|
|
13980
|
+
if (!versionData.attributes || typeof versionData.attributes !== "object") {
|
|
13981
|
+
healingLog.push(`Resource ${name}: missing or invalid attributes - removing resource`);
|
|
13982
|
+
return null;
|
|
13983
|
+
}
|
|
13984
|
+
if (versionData.hooks) {
|
|
13985
|
+
const healedHooks = this._healHooksStructure(versionData.hooks, name, healingLog);
|
|
13986
|
+
if (healedHooks !== versionData.hooks) {
|
|
13987
|
+
healed.versions[healed.currentVersion].hooks = healedHooks;
|
|
13988
|
+
changed = true;
|
|
13989
|
+
}
|
|
13990
|
+
}
|
|
13991
|
+
return changed ? healed : resource;
|
|
13992
|
+
}
|
|
13993
|
+
/**
|
|
13994
|
+
* Heal hooks structure
|
|
13995
|
+
*/
|
|
13996
|
+
_healHooksStructure(hooks, resourceName, healingLog) {
|
|
13997
|
+
if (!hooks || typeof hooks !== "object") {
|
|
13998
|
+
healingLog.push(`Resource ${resourceName}: invalid hooks structure - using empty hooks`);
|
|
13999
|
+
return {};
|
|
14000
|
+
}
|
|
14001
|
+
const healed = {};
|
|
14002
|
+
let changed = false;
|
|
14003
|
+
for (const [event, hookArray] of Object.entries(hooks)) {
|
|
14004
|
+
if (Array.isArray(hookArray)) {
|
|
14005
|
+
const validHooks = hookArray.filter(
|
|
14006
|
+
(hook) => hook !== null && hook !== void 0 && hook !== ""
|
|
14007
|
+
);
|
|
14008
|
+
healed[event] = validHooks;
|
|
14009
|
+
if (validHooks.length !== hookArray.length) {
|
|
14010
|
+
healingLog.push(`Resource ${resourceName}: cleaned invalid hooks for event ${event}`);
|
|
14011
|
+
changed = true;
|
|
14012
|
+
}
|
|
14013
|
+
} else {
|
|
14014
|
+
healingLog.push(`Resource ${resourceName}: hooks for event ${event} is not an array - removing`);
|
|
14015
|
+
changed = true;
|
|
14016
|
+
}
|
|
14017
|
+
}
|
|
14018
|
+
return changed ? healed : hooks;
|
|
14019
|
+
}
|
|
14020
|
+
/**
|
|
14021
|
+
* Create backup of corrupted file
|
|
14022
|
+
*/
|
|
14023
|
+
async _createCorruptedBackup(content = null) {
|
|
14024
|
+
try {
|
|
14025
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
14026
|
+
const backupKey = `s3db.json.corrupted.${timestamp}.backup`;
|
|
14027
|
+
if (!content) {
|
|
14028
|
+
try {
|
|
14029
|
+
const request = await this.client.getObject(`s3db.json`);
|
|
14030
|
+
content = await streamToString(request?.Body);
|
|
14031
|
+
} catch (error) {
|
|
14032
|
+
content = "Unable to read corrupted file content";
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
await this.client.putObject({
|
|
14036
|
+
key: backupKey,
|
|
14037
|
+
body: content,
|
|
14038
|
+
contentType: "application/json"
|
|
14039
|
+
});
|
|
14040
|
+
if (this.verbose) {
|
|
14041
|
+
console.warn(`S3DB: Created backup of corrupted s3db.json as ${backupKey}`);
|
|
14042
|
+
}
|
|
14043
|
+
} catch (error) {
|
|
14044
|
+
if (this.verbose) {
|
|
14045
|
+
console.warn(`S3DB: Failed to create backup: ${error.message}`);
|
|
14046
|
+
}
|
|
14047
|
+
}
|
|
14048
|
+
}
|
|
14049
|
+
/**
|
|
14050
|
+
* Upload healed metadata with logging
|
|
14051
|
+
*/
|
|
14052
|
+
async _uploadHealedMetadata(metadata, healingLog) {
|
|
14053
|
+
try {
|
|
14054
|
+
if (this.verbose && healingLog.length > 0) {
|
|
14055
|
+
console.warn("S3DB Self-Healing Operations:");
|
|
14056
|
+
healingLog.forEach((log) => console.warn(` - ${log}`));
|
|
14057
|
+
}
|
|
14058
|
+
metadata.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
14059
|
+
await this.client.putObject({
|
|
14060
|
+
key: "s3db.json",
|
|
14061
|
+
body: JSON.stringify(metadata, null, 2),
|
|
14062
|
+
contentType: "application/json"
|
|
14063
|
+
});
|
|
14064
|
+
this.emit("metadataHealed", { healingLog, metadata });
|
|
14065
|
+
if (this.verbose) {
|
|
14066
|
+
console.warn("S3DB: Successfully uploaded healed metadata");
|
|
14067
|
+
}
|
|
14068
|
+
} catch (error) {
|
|
14069
|
+
if (this.verbose) {
|
|
14070
|
+
console.error(`S3DB: Failed to upload healed metadata: ${error.message}`);
|
|
14071
|
+
}
|
|
14072
|
+
throw error;
|
|
14073
|
+
}
|
|
14074
|
+
}
|
|
13709
14075
|
/**
|
|
13710
14076
|
* Check if a resource exists by name
|
|
13711
14077
|
* @param {string} name - Resource name
|