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/package.json
CHANGED
package/src/database.class.js
CHANGED
|
@@ -33,6 +33,7 @@ export class Database extends EventEmitter {
|
|
|
33
33
|
this.cache = options.cache;
|
|
34
34
|
this.passphrase = options.passphrase || "secret";
|
|
35
35
|
this.versioningEnabled = options.versioningEnabled || false;
|
|
36
|
+
this.persistHooks = options.persistHooks || false; // New configuration for hook persistence
|
|
36
37
|
|
|
37
38
|
// Initialize hooks system
|
|
38
39
|
this._initHooks();
|
|
@@ -97,15 +98,55 @@ export class Database extends EventEmitter {
|
|
|
97
98
|
await this.startPlugins();
|
|
98
99
|
|
|
99
100
|
let metadata = null;
|
|
101
|
+
let needsHealing = false;
|
|
102
|
+
let healingLog = [];
|
|
100
103
|
|
|
101
104
|
if (await this.client.exists(`s3db.json`)) {
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
try {
|
|
106
|
+
const request = await this.client.getObject(`s3db.json`);
|
|
107
|
+
const rawContent = await streamToString(request?.Body);
|
|
108
|
+
|
|
109
|
+
// Try to parse JSON
|
|
110
|
+
try {
|
|
111
|
+
metadata = JSON.parse(rawContent);
|
|
112
|
+
} catch (parseError) {
|
|
113
|
+
healingLog.push('JSON parsing failed - attempting recovery');
|
|
114
|
+
needsHealing = true;
|
|
115
|
+
|
|
116
|
+
// Attempt to fix common JSON issues
|
|
117
|
+
metadata = await this._attemptJsonRecovery(rawContent, healingLog);
|
|
118
|
+
|
|
119
|
+
if (!metadata) {
|
|
120
|
+
// Create backup and start fresh
|
|
121
|
+
await this._createCorruptedBackup(rawContent);
|
|
122
|
+
healingLog.push('Created backup of corrupted file - starting with blank metadata');
|
|
123
|
+
metadata = this.blankMetadataStructure();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Validate and heal metadata structure
|
|
128
|
+
const healedMetadata = await this._validateAndHealMetadata(metadata, healingLog);
|
|
129
|
+
if (healedMetadata !== metadata) {
|
|
130
|
+
metadata = healedMetadata;
|
|
131
|
+
needsHealing = true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
} catch (error) {
|
|
135
|
+
healingLog.push(`Critical error reading s3db.json: ${error.message}`);
|
|
136
|
+
await this._createCorruptedBackup();
|
|
137
|
+
metadata = this.blankMetadataStructure();
|
|
138
|
+
needsHealing = true;
|
|
139
|
+
}
|
|
104
140
|
} else {
|
|
105
141
|
metadata = this.blankMetadataStructure();
|
|
106
142
|
await this.uploadMetadataFile();
|
|
107
143
|
}
|
|
108
144
|
|
|
145
|
+
// Upload healed metadata if needed
|
|
146
|
+
if (needsHealing) {
|
|
147
|
+
await this._uploadHealedMetadata(metadata, healingLog);
|
|
148
|
+
}
|
|
149
|
+
|
|
109
150
|
this.savedMetadata = metadata;
|
|
110
151
|
|
|
111
152
|
// Check for definition changes (this happens before creating resources from createResource calls)
|
|
@@ -151,7 +192,7 @@ export class Database extends EventEmitter {
|
|
|
151
192
|
paranoid: versionData.paranoid !== undefined ? versionData.paranoid : true,
|
|
152
193
|
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== undefined ? versionData.allNestedObjectsOptional : true,
|
|
153
194
|
autoDecrypt: versionData.autoDecrypt !== undefined ? versionData.autoDecrypt : true,
|
|
154
|
-
hooks: versionData.hooks || {},
|
|
195
|
+
hooks: this.persistHooks ? this._deserializeHooks(versionData.hooks || {}) : (versionData.hooks || {}),
|
|
155
196
|
versioningEnabled: this.versioningEnabled,
|
|
156
197
|
map: versionData.map,
|
|
157
198
|
idGenerator: restoredIdGenerator,
|
|
@@ -269,6 +310,78 @@ export class Database extends EventEmitter {
|
|
|
269
310
|
return `v${maxVersion + 1}`;
|
|
270
311
|
}
|
|
271
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Serialize hooks to strings for JSON persistence
|
|
315
|
+
* @param {Object} hooks - Hooks object with event names as keys and function arrays as values
|
|
316
|
+
* @returns {Object} Serialized hooks object
|
|
317
|
+
* @private
|
|
318
|
+
*/
|
|
319
|
+
_serializeHooks(hooks) {
|
|
320
|
+
if (!hooks || typeof hooks !== 'object') return hooks;
|
|
321
|
+
|
|
322
|
+
const serialized = {};
|
|
323
|
+
for (const [event, hookArray] of Object.entries(hooks)) {
|
|
324
|
+
if (Array.isArray(hookArray)) {
|
|
325
|
+
serialized[event] = hookArray.map(hook => {
|
|
326
|
+
if (typeof hook === 'function') {
|
|
327
|
+
try {
|
|
328
|
+
return {
|
|
329
|
+
__s3db_serialized_function: true,
|
|
330
|
+
code: hook.toString(),
|
|
331
|
+
name: hook.name || 'anonymous'
|
|
332
|
+
};
|
|
333
|
+
} catch (err) {
|
|
334
|
+
if (this.verbose) {
|
|
335
|
+
console.warn(`Failed to serialize hook for event '${event}':`, err.message);
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return hook;
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
serialized[event] = hookArray;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return serialized;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Deserialize hooks from strings back to functions
|
|
351
|
+
* @param {Object} serializedHooks - Serialized hooks object
|
|
352
|
+
* @returns {Object} Deserialized hooks object
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
_deserializeHooks(serializedHooks) {
|
|
356
|
+
if (!serializedHooks || typeof serializedHooks !== 'object') return serializedHooks;
|
|
357
|
+
|
|
358
|
+
const deserialized = {};
|
|
359
|
+
for (const [event, hookArray] of Object.entries(serializedHooks)) {
|
|
360
|
+
if (Array.isArray(hookArray)) {
|
|
361
|
+
deserialized[event] = hookArray.map(hook => {
|
|
362
|
+
if (hook && typeof hook === 'object' && hook.__s3db_serialized_function) {
|
|
363
|
+
try {
|
|
364
|
+
// Use Function constructor instead of eval for better security
|
|
365
|
+
const fn = new Function('return ' + hook.code)();
|
|
366
|
+
if (typeof fn === 'function') {
|
|
367
|
+
return fn;
|
|
368
|
+
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
if (this.verbose) {
|
|
371
|
+
console.warn(`Failed to deserialize hook '${hook.name}' for event '${event}':`, err.message);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
return hook;
|
|
377
|
+
}).filter(hook => hook !== null); // Remove failed deserializations
|
|
378
|
+
} else {
|
|
379
|
+
deserialized[event] = hookArray;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return deserialized;
|
|
383
|
+
}
|
|
384
|
+
|
|
272
385
|
async startPlugins() {
|
|
273
386
|
const db = this
|
|
274
387
|
|
|
@@ -357,7 +470,7 @@ export class Database extends EventEmitter {
|
|
|
357
470
|
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
358
471
|
autoDecrypt: resource.config.autoDecrypt,
|
|
359
472
|
cache: resource.config.cache,
|
|
360
|
-
hooks: resource.config.hooks,
|
|
473
|
+
hooks: this.persistHooks ? this._serializeHooks(resource.config.hooks) : resource.config.hooks,
|
|
361
474
|
idSize: resource.idSize,
|
|
362
475
|
idGenerator: resource.idGeneratorType,
|
|
363
476
|
createdAt: isNewVersion ? new Date().toISOString() : existingVersionData?.createdAt
|
|
@@ -386,10 +499,331 @@ export class Database extends EventEmitter {
|
|
|
386
499
|
return {
|
|
387
500
|
version: `1`,
|
|
388
501
|
s3dbVersion: this.s3dbVersion,
|
|
502
|
+
lastUpdated: new Date().toISOString(),
|
|
389
503
|
resources: {},
|
|
390
504
|
};
|
|
391
505
|
}
|
|
392
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Attempt to recover JSON from corrupted content
|
|
509
|
+
*/
|
|
510
|
+
async _attemptJsonRecovery(content, healingLog) {
|
|
511
|
+
if (!content || typeof content !== 'string') {
|
|
512
|
+
healingLog.push('Content is empty or not a string');
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Try common JSON fixes
|
|
517
|
+
const fixes = [
|
|
518
|
+
// Remove trailing commas
|
|
519
|
+
() => content.replace(/,(\s*[}\]])/g, '$1'),
|
|
520
|
+
// Add missing quotes to keys
|
|
521
|
+
() => content.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":'),
|
|
522
|
+
// Fix incomplete objects by adding closing braces
|
|
523
|
+
() => {
|
|
524
|
+
let openBraces = 0;
|
|
525
|
+
let openBrackets = 0;
|
|
526
|
+
let inString = false;
|
|
527
|
+
let escaped = false;
|
|
528
|
+
|
|
529
|
+
for (let i = 0; i < content.length; i++) {
|
|
530
|
+
const char = content[i];
|
|
531
|
+
|
|
532
|
+
if (escaped) {
|
|
533
|
+
escaped = false;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (char === '\\') {
|
|
538
|
+
escaped = true;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (char === '"') {
|
|
543
|
+
inString = !inString;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!inString) {
|
|
548
|
+
if (char === '{') openBraces++;
|
|
549
|
+
else if (char === '}') openBraces--;
|
|
550
|
+
else if (char === '[') openBrackets++;
|
|
551
|
+
else if (char === ']') openBrackets--;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
let fixed = content;
|
|
556
|
+
while (openBrackets > 0) {
|
|
557
|
+
fixed += ']';
|
|
558
|
+
openBrackets--;
|
|
559
|
+
}
|
|
560
|
+
while (openBraces > 0) {
|
|
561
|
+
fixed += '}';
|
|
562
|
+
openBraces--;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return fixed;
|
|
566
|
+
}
|
|
567
|
+
];
|
|
568
|
+
|
|
569
|
+
for (const [index, fix] of fixes.entries()) {
|
|
570
|
+
try {
|
|
571
|
+
const fixedContent = fix();
|
|
572
|
+
const parsed = JSON.parse(fixedContent);
|
|
573
|
+
healingLog.push(`JSON recovery successful using fix #${index + 1}`);
|
|
574
|
+
return parsed;
|
|
575
|
+
} catch (error) {
|
|
576
|
+
// Try next fix
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
healingLog.push('All JSON recovery attempts failed');
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Validate and heal metadata structure
|
|
586
|
+
*/
|
|
587
|
+
async _validateAndHealMetadata(metadata, healingLog) {
|
|
588
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
589
|
+
healingLog.push('Metadata is not an object - using blank structure');
|
|
590
|
+
return this.blankMetadataStructure();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
let healed = { ...metadata };
|
|
594
|
+
let changed = false;
|
|
595
|
+
|
|
596
|
+
// Ensure required fields exist and have correct types
|
|
597
|
+
if (!healed.version || typeof healed.version !== 'string') {
|
|
598
|
+
if (healed.version && typeof healed.version === 'number') {
|
|
599
|
+
healed.version = String(healed.version);
|
|
600
|
+
healingLog.push('Converted version from number to string');
|
|
601
|
+
changed = true;
|
|
602
|
+
} else {
|
|
603
|
+
healed.version = '1';
|
|
604
|
+
healingLog.push('Added missing or invalid version field');
|
|
605
|
+
changed = true;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (!healed.s3dbVersion || typeof healed.s3dbVersion !== 'string') {
|
|
610
|
+
if (healed.s3dbVersion && typeof healed.s3dbVersion !== 'string') {
|
|
611
|
+
healed.s3dbVersion = String(healed.s3dbVersion);
|
|
612
|
+
healingLog.push('Converted s3dbVersion to string');
|
|
613
|
+
changed = true;
|
|
614
|
+
} else {
|
|
615
|
+
healed.s3dbVersion = this.s3dbVersion;
|
|
616
|
+
healingLog.push('Added missing s3dbVersion field');
|
|
617
|
+
changed = true;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (!healed.resources || typeof healed.resources !== 'object' || Array.isArray(healed.resources)) {
|
|
622
|
+
healed.resources = {};
|
|
623
|
+
healingLog.push('Fixed invalid resources field');
|
|
624
|
+
changed = true;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (!healed.lastUpdated) {
|
|
628
|
+
healed.lastUpdated = new Date().toISOString();
|
|
629
|
+
healingLog.push('Added missing lastUpdated field');
|
|
630
|
+
changed = true;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Validate and heal resource structures
|
|
634
|
+
const validResources = {};
|
|
635
|
+
for (const [name, resource] of Object.entries(healed.resources)) {
|
|
636
|
+
const healedResource = this._healResourceStructure(name, resource, healingLog);
|
|
637
|
+
if (healedResource) {
|
|
638
|
+
validResources[name] = healedResource;
|
|
639
|
+
if (healedResource !== resource) {
|
|
640
|
+
changed = true;
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
healingLog.push(`Removed invalid resource: ${name}`);
|
|
644
|
+
changed = true;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
healed.resources = validResources;
|
|
649
|
+
|
|
650
|
+
return changed ? healed : metadata;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Heal individual resource structure
|
|
655
|
+
*/
|
|
656
|
+
_healResourceStructure(name, resource, healingLog) {
|
|
657
|
+
if (!resource || typeof resource !== 'object') {
|
|
658
|
+
healingLog.push(`Resource ${name}: invalid structure`);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
let healed = { ...resource };
|
|
663
|
+
let changed = false;
|
|
664
|
+
|
|
665
|
+
// Ensure currentVersion exists
|
|
666
|
+
if (!healed.currentVersion) {
|
|
667
|
+
healed.currentVersion = 'v0';
|
|
668
|
+
healingLog.push(`Resource ${name}: added missing currentVersion`);
|
|
669
|
+
changed = true;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Ensure versions object exists
|
|
673
|
+
if (!healed.versions || typeof healed.versions !== 'object' || Array.isArray(healed.versions)) {
|
|
674
|
+
healed.versions = {};
|
|
675
|
+
healingLog.push(`Resource ${name}: fixed invalid versions object`);
|
|
676
|
+
changed = true;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Ensure partitions object exists
|
|
680
|
+
if (!healed.partitions || typeof healed.partitions !== 'object' || Array.isArray(healed.partitions)) {
|
|
681
|
+
healed.partitions = {};
|
|
682
|
+
healingLog.push(`Resource ${name}: fixed invalid partitions object`);
|
|
683
|
+
changed = true;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Check if currentVersion exists in versions
|
|
687
|
+
const currentVersion = healed.currentVersion;
|
|
688
|
+
if (!healed.versions[currentVersion]) {
|
|
689
|
+
// Try to find a valid version or fall back to v0
|
|
690
|
+
const availableVersions = Object.keys(healed.versions);
|
|
691
|
+
if (availableVersions.length > 0) {
|
|
692
|
+
healed.currentVersion = availableVersions[0];
|
|
693
|
+
healingLog.push(`Resource ${name}: changed currentVersion from ${currentVersion} to ${healed.currentVersion}`);
|
|
694
|
+
changed = true;
|
|
695
|
+
} else {
|
|
696
|
+
// No valid versions exist - resource cannot be healed
|
|
697
|
+
healingLog.push(`Resource ${name}: no valid versions found - removing resource`);
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Validate version data
|
|
703
|
+
const versionData = healed.versions[healed.currentVersion];
|
|
704
|
+
if (!versionData || typeof versionData !== 'object') {
|
|
705
|
+
healingLog.push(`Resource ${name}: invalid version data - removing resource`);
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Ensure required version fields
|
|
710
|
+
if (!versionData.attributes || typeof versionData.attributes !== 'object') {
|
|
711
|
+
healingLog.push(`Resource ${name}: missing or invalid attributes - removing resource`);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Heal hooks structure
|
|
716
|
+
if (versionData.hooks) {
|
|
717
|
+
const healedHooks = this._healHooksStructure(versionData.hooks, name, healingLog);
|
|
718
|
+
if (healedHooks !== versionData.hooks) {
|
|
719
|
+
healed.versions[healed.currentVersion].hooks = healedHooks;
|
|
720
|
+
changed = true;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return changed ? healed : resource;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Heal hooks structure
|
|
729
|
+
*/
|
|
730
|
+
_healHooksStructure(hooks, resourceName, healingLog) {
|
|
731
|
+
if (!hooks || typeof hooks !== 'object') {
|
|
732
|
+
healingLog.push(`Resource ${resourceName}: invalid hooks structure - using empty hooks`);
|
|
733
|
+
return {};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const healed = {};
|
|
737
|
+
let changed = false;
|
|
738
|
+
|
|
739
|
+
for (const [event, hookArray] of Object.entries(hooks)) {
|
|
740
|
+
if (Array.isArray(hookArray)) {
|
|
741
|
+
// Filter out null, undefined, empty strings, and invalid hooks
|
|
742
|
+
const validHooks = hookArray.filter(hook =>
|
|
743
|
+
hook !== null &&
|
|
744
|
+
hook !== undefined &&
|
|
745
|
+
hook !== ""
|
|
746
|
+
);
|
|
747
|
+
healed[event] = validHooks;
|
|
748
|
+
|
|
749
|
+
if (validHooks.length !== hookArray.length) {
|
|
750
|
+
healingLog.push(`Resource ${resourceName}: cleaned invalid hooks for event ${event}`);
|
|
751
|
+
changed = true;
|
|
752
|
+
}
|
|
753
|
+
} else {
|
|
754
|
+
healingLog.push(`Resource ${resourceName}: hooks for event ${event} is not an array - removing`);
|
|
755
|
+
changed = true;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return changed ? healed : hooks;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Create backup of corrupted file
|
|
764
|
+
*/
|
|
765
|
+
async _createCorruptedBackup(content = null) {
|
|
766
|
+
try {
|
|
767
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
768
|
+
const backupKey = `s3db.json.corrupted.${timestamp}.backup`;
|
|
769
|
+
|
|
770
|
+
if (!content) {
|
|
771
|
+
try {
|
|
772
|
+
const request = await this.client.getObject(`s3db.json`);
|
|
773
|
+
content = await streamToString(request?.Body);
|
|
774
|
+
} catch (error) {
|
|
775
|
+
content = 'Unable to read corrupted file content';
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
await this.client.putObject({
|
|
780
|
+
key: backupKey,
|
|
781
|
+
body: content,
|
|
782
|
+
contentType: 'application/json'
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
if (this.verbose) {
|
|
786
|
+
console.warn(`S3DB: Created backup of corrupted s3db.json as ${backupKey}`);
|
|
787
|
+
}
|
|
788
|
+
} catch (error) {
|
|
789
|
+
if (this.verbose) {
|
|
790
|
+
console.warn(`S3DB: Failed to create backup: ${error.message}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Upload healed metadata with logging
|
|
797
|
+
*/
|
|
798
|
+
async _uploadHealedMetadata(metadata, healingLog) {
|
|
799
|
+
try {
|
|
800
|
+
if (this.verbose && healingLog.length > 0) {
|
|
801
|
+
console.warn('S3DB Self-Healing Operations:');
|
|
802
|
+
healingLog.forEach(log => console.warn(` - ${log}`));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Update lastUpdated timestamp
|
|
806
|
+
metadata.lastUpdated = new Date().toISOString();
|
|
807
|
+
|
|
808
|
+
await this.client.putObject({
|
|
809
|
+
key: 's3db.json',
|
|
810
|
+
body: JSON.stringify(metadata, null, 2),
|
|
811
|
+
contentType: 'application/json'
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
this.emit('metadataHealed', { healingLog, metadata });
|
|
815
|
+
|
|
816
|
+
if (this.verbose) {
|
|
817
|
+
console.warn('S3DB: Successfully uploaded healed metadata');
|
|
818
|
+
}
|
|
819
|
+
} catch (error) {
|
|
820
|
+
if (this.verbose) {
|
|
821
|
+
console.error(`S3DB: Failed to upload healed metadata: ${error.message}`);
|
|
822
|
+
}
|
|
823
|
+
throw error;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
393
827
|
/**
|
|
394
828
|
* Check if a resource exists by name
|
|
395
829
|
* @param {string} name - Resource name
|
package/src/s3db.d.ts
CHANGED