s3db.js 4.1.7 → 4.1.10
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 +83 -16
- package/dist/s3db.cjs.js +242 -58
- package/dist/s3db.cjs.min.js +9 -9
- package/dist/s3db.es.js +242 -58
- package/dist/s3db.es.min.js +10 -10
- package/dist/s3db.iife.js +242 -58
- package/dist/s3db.iife.min.js +10 -10
- package/package.json +1 -1
package/dist/s3db.iife.js
CHANGED
|
@@ -9228,36 +9228,97 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9228
9228
|
*/
|
|
9229
9229
|
async get(id) {
|
|
9230
9230
|
const key = this.getResourceKey(id);
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9231
|
+
try {
|
|
9232
|
+
const request = await this.client.headObject(key);
|
|
9233
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9234
|
+
const schema = await this.getSchemaForVersion(objectVersion);
|
|
9235
|
+
let metadata = await schema.unmapper(request.Metadata);
|
|
9236
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9237
|
+
let body = "";
|
|
9238
|
+
if (request.ContentLength > 0) {
|
|
9239
|
+
try {
|
|
9240
|
+
const fullObject = await this.client.getObject(key);
|
|
9241
|
+
body = await streamToString(fullObject.Body);
|
|
9242
|
+
} catch (error) {
|
|
9243
|
+
console.warn(`Failed to read body for resource ${id}:`, error.message);
|
|
9244
|
+
body = "";
|
|
9245
|
+
}
|
|
9243
9246
|
}
|
|
9247
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9248
|
+
resource: this,
|
|
9249
|
+
metadata,
|
|
9250
|
+
body
|
|
9251
|
+
});
|
|
9252
|
+
let data = processedMetadata;
|
|
9253
|
+
data.id = id;
|
|
9254
|
+
data._contentLength = request.ContentLength;
|
|
9255
|
+
data._lastModified = request.LastModified;
|
|
9256
|
+
data._hasContent = request.ContentLength > 0;
|
|
9257
|
+
data._mimeType = request.ContentType || null;
|
|
9258
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9259
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9260
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9261
|
+
this.emit("get", data);
|
|
9262
|
+
return data;
|
|
9263
|
+
} catch (error) {
|
|
9264
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError") || error.originalError?.message?.includes("Cipher job failed")) {
|
|
9265
|
+
try {
|
|
9266
|
+
console.warn(`Decryption failed for resource ${id}, attempting to get raw metadata`);
|
|
9267
|
+
const request = await this.client.headObject(key);
|
|
9268
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9269
|
+
const tempSchema = new Schema({
|
|
9270
|
+
name: this.name,
|
|
9271
|
+
attributes: this.attributes,
|
|
9272
|
+
passphrase: this.passphrase,
|
|
9273
|
+
version: objectVersion,
|
|
9274
|
+
options: {
|
|
9275
|
+
...this.options,
|
|
9276
|
+
autoDecrypt: false,
|
|
9277
|
+
// Disable decryption
|
|
9278
|
+
autoEncrypt: false
|
|
9279
|
+
// Disable encryption
|
|
9280
|
+
}
|
|
9281
|
+
});
|
|
9282
|
+
let metadata = await tempSchema.unmapper(request.Metadata);
|
|
9283
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9284
|
+
let body = "";
|
|
9285
|
+
if (request.ContentLength > 0) {
|
|
9286
|
+
try {
|
|
9287
|
+
const fullObject = await this.client.getObject(key);
|
|
9288
|
+
body = await streamToString(fullObject.Body);
|
|
9289
|
+
} catch (bodyError) {
|
|
9290
|
+
console.warn(`Failed to read body for resource ${id}:`, bodyError.message);
|
|
9291
|
+
body = "";
|
|
9292
|
+
}
|
|
9293
|
+
}
|
|
9294
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9295
|
+
resource: this,
|
|
9296
|
+
metadata,
|
|
9297
|
+
body
|
|
9298
|
+
});
|
|
9299
|
+
let data = processedMetadata;
|
|
9300
|
+
data.id = id;
|
|
9301
|
+
data._contentLength = request.ContentLength;
|
|
9302
|
+
data._lastModified = request.LastModified;
|
|
9303
|
+
data._hasContent = request.ContentLength > 0;
|
|
9304
|
+
data._mimeType = request.ContentType || null;
|
|
9305
|
+
data._version = objectVersion;
|
|
9306
|
+
data._decryptionFailed = true;
|
|
9307
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9308
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9309
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9310
|
+
this.emit("get", data);
|
|
9311
|
+
return data;
|
|
9312
|
+
} catch (fallbackError) {
|
|
9313
|
+
console.error(`Fallback attempt also failed for resource ${id}:`, fallbackError.message);
|
|
9314
|
+
}
|
|
9315
|
+
}
|
|
9316
|
+
const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
|
|
9317
|
+
enhancedError.originalError = error;
|
|
9318
|
+
enhancedError.resourceId = id;
|
|
9319
|
+
enhancedError.resourceKey = key;
|
|
9320
|
+
throw enhancedError;
|
|
9244
9321
|
}
|
|
9245
|
-
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9246
|
-
resource: this,
|
|
9247
|
-
metadata,
|
|
9248
|
-
body
|
|
9249
|
-
});
|
|
9250
|
-
let data = processedMetadata;
|
|
9251
|
-
data.id = id;
|
|
9252
|
-
data._contentLength = request.ContentLength;
|
|
9253
|
-
data._lastModified = request.LastModified;
|
|
9254
|
-
data._hasContent = request.ContentLength > 0;
|
|
9255
|
-
data._mimeType = request.ContentType || null;
|
|
9256
|
-
if (request.VersionId) data._versionId = request.VersionId;
|
|
9257
|
-
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9258
|
-
data._definitionHash = this.getDefinitionHash();
|
|
9259
|
-
this.emit("get", data);
|
|
9260
|
-
return data;
|
|
9261
9322
|
}
|
|
9262
9323
|
/**
|
|
9263
9324
|
* Check if a resource exists by ID
|
|
@@ -9539,15 +9600,21 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9539
9600
|
return { deletedCount, resource: this.name };
|
|
9540
9601
|
}
|
|
9541
9602
|
/**
|
|
9542
|
-
* List resource IDs with optional partition filtering
|
|
9603
|
+
* List resource IDs with optional partition filtering and pagination
|
|
9543
9604
|
* @param {Object} [params] - List parameters
|
|
9544
9605
|
* @param {string} [params.partition] - Partition name to list from
|
|
9545
9606
|
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9607
|
+
* @param {number} [params.limit] - Maximum number of results to return
|
|
9608
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9546
9609
|
* @returns {Promise<string[]>} Array of resource IDs (strings)
|
|
9547
9610
|
* @example
|
|
9548
9611
|
* // List all IDs
|
|
9549
9612
|
* const allIds = await resource.listIds();
|
|
9550
9613
|
*
|
|
9614
|
+
* // List IDs with pagination
|
|
9615
|
+
* const firstPageIds = await resource.listIds({ limit: 10, offset: 0 });
|
|
9616
|
+
* const secondPageIds = await resource.listIds({ limit: 10, offset: 10 });
|
|
9617
|
+
*
|
|
9551
9618
|
* // List IDs from specific partition
|
|
9552
9619
|
* const googleUserIds = await resource.listIds({
|
|
9553
9620
|
* partition: 'byUtmSource',
|
|
@@ -9560,7 +9627,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9560
9627
|
* partitionValues: { category: 'electronics', region: 'US' }
|
|
9561
9628
|
* });
|
|
9562
9629
|
*/
|
|
9563
|
-
async listIds({ partition = null, partitionValues = {} } = {}) {
|
|
9630
|
+
async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9564
9631
|
let prefix;
|
|
9565
9632
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9566
9633
|
const partitionDef = this.options.partitions[partition];
|
|
@@ -9584,8 +9651,11 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9584
9651
|
} else {
|
|
9585
9652
|
prefix = `resource=${this.name}/v=${this.version}`;
|
|
9586
9653
|
}
|
|
9587
|
-
const keys = await this.client.
|
|
9588
|
-
prefix
|
|
9654
|
+
const keys = await this.client.getKeysPage({
|
|
9655
|
+
prefix,
|
|
9656
|
+
offset,
|
|
9657
|
+
amount: limit || 1e3
|
|
9658
|
+
// Default to 1000 if no limit specified
|
|
9589
9659
|
});
|
|
9590
9660
|
const ids = keys.map((key) => {
|
|
9591
9661
|
const parts = key.split("/");
|
|
@@ -9632,11 +9702,27 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9632
9702
|
if (limit) {
|
|
9633
9703
|
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9634
9704
|
}
|
|
9635
|
-
const { results: results2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).
|
|
9636
|
-
|
|
9705
|
+
const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9706
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9707
|
+
return null;
|
|
9708
|
+
}).process(async (id) => {
|
|
9709
|
+
try {
|
|
9710
|
+
return await this.get(id);
|
|
9711
|
+
} catch (error) {
|
|
9712
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9713
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9714
|
+
return {
|
|
9715
|
+
id,
|
|
9716
|
+
_decryptionFailed: true,
|
|
9717
|
+
_error: error.message
|
|
9718
|
+
};
|
|
9719
|
+
}
|
|
9720
|
+
throw error;
|
|
9721
|
+
}
|
|
9637
9722
|
});
|
|
9638
|
-
|
|
9639
|
-
|
|
9723
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9724
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9725
|
+
return validResults2;
|
|
9640
9726
|
}
|
|
9641
9727
|
const partitionDef = this.options.partitions[partition];
|
|
9642
9728
|
if (!partitionDef) {
|
|
@@ -9667,11 +9753,29 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9667
9753
|
if (limit) {
|
|
9668
9754
|
filteredIds = filteredIds.slice(0, limit);
|
|
9669
9755
|
}
|
|
9670
|
-
const { results } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).
|
|
9671
|
-
|
|
9756
|
+
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9757
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9758
|
+
return null;
|
|
9759
|
+
}).process(async (id) => {
|
|
9760
|
+
try {
|
|
9761
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9762
|
+
} catch (error) {
|
|
9763
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9764
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9765
|
+
return {
|
|
9766
|
+
id,
|
|
9767
|
+
_partition: partition,
|
|
9768
|
+
_partitionValues: partitionValues,
|
|
9769
|
+
_decryptionFailed: true,
|
|
9770
|
+
_error: error.message
|
|
9771
|
+
};
|
|
9772
|
+
}
|
|
9773
|
+
throw error;
|
|
9774
|
+
}
|
|
9672
9775
|
});
|
|
9673
|
-
|
|
9674
|
-
|
|
9776
|
+
const validResults = results.filter((item) => item !== null);
|
|
9777
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9778
|
+
return validResults;
|
|
9675
9779
|
}
|
|
9676
9780
|
/**
|
|
9677
9781
|
* Get multiple resources by their IDs
|
|
@@ -9682,11 +9786,30 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9682
9786
|
* users.forEach(user => console.log(user.name));
|
|
9683
9787
|
*/
|
|
9684
9788
|
async getMany(ids) {
|
|
9685
|
-
const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9789
|
+
const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9790
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9791
|
+
return {
|
|
9792
|
+
id,
|
|
9793
|
+
_error: error.message,
|
|
9794
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9795
|
+
};
|
|
9796
|
+
}).process(async (id) => {
|
|
9686
9797
|
this.emit("id", id);
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9798
|
+
try {
|
|
9799
|
+
const data = await this.get(id);
|
|
9800
|
+
this.emit("data", data);
|
|
9801
|
+
return data;
|
|
9802
|
+
} catch (error) {
|
|
9803
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9804
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9805
|
+
return {
|
|
9806
|
+
id,
|
|
9807
|
+
_decryptionFailed: true,
|
|
9808
|
+
_error: error.message
|
|
9809
|
+
};
|
|
9810
|
+
}
|
|
9811
|
+
throw error;
|
|
9812
|
+
}
|
|
9690
9813
|
});
|
|
9691
9814
|
this.emit("getMany", ids.length);
|
|
9692
9815
|
return results;
|
|
@@ -9701,9 +9824,28 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9701
9824
|
async getAll() {
|
|
9702
9825
|
let ids = await this.listIds();
|
|
9703
9826
|
if (ids.length === 0) return [];
|
|
9704
|
-
const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9705
|
-
|
|
9706
|
-
return
|
|
9827
|
+
const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9828
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9829
|
+
return {
|
|
9830
|
+
id,
|
|
9831
|
+
_error: error.message,
|
|
9832
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9833
|
+
};
|
|
9834
|
+
}).process(async (id) => {
|
|
9835
|
+
try {
|
|
9836
|
+
const data = await this.get(id);
|
|
9837
|
+
return data;
|
|
9838
|
+
} catch (error) {
|
|
9839
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9840
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9841
|
+
return {
|
|
9842
|
+
id,
|
|
9843
|
+
_decryptionFailed: true,
|
|
9844
|
+
_error: error.message
|
|
9845
|
+
};
|
|
9846
|
+
}
|
|
9847
|
+
throw error;
|
|
9848
|
+
}
|
|
9707
9849
|
});
|
|
9708
9850
|
this.emit("getAll", results.length);
|
|
9709
9851
|
return results;
|
|
@@ -9715,6 +9857,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9715
9857
|
* @param {number} [params.size=100] - Page size
|
|
9716
9858
|
* @param {string} [params.partition] - Partition name to page from
|
|
9717
9859
|
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9860
|
+
* @param {boolean} [params.skipCount=false] - Skip total count for performance (useful for large collections)
|
|
9718
9861
|
* @returns {Promise<Object>} Page result with items and pagination info
|
|
9719
9862
|
* @example
|
|
9720
9863
|
* // Get first page of all resources
|
|
@@ -9729,22 +9872,43 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9729
9872
|
* offset: 0,
|
|
9730
9873
|
* size: 5
|
|
9731
9874
|
* });
|
|
9875
|
+
*
|
|
9876
|
+
* // Skip count for performance in large collections
|
|
9877
|
+
* const fastPage = await resource.page({
|
|
9878
|
+
* offset: 0,
|
|
9879
|
+
* size: 100,
|
|
9880
|
+
* skipCount: true
|
|
9881
|
+
* });
|
|
9882
|
+
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
9732
9883
|
*/
|
|
9733
|
-
async page({ offset = 0, size = 100, partition = null, partitionValues = {} } = {}) {
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9884
|
+
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
9885
|
+
let totalItems = null;
|
|
9886
|
+
let totalPages = null;
|
|
9887
|
+
if (!skipCount) {
|
|
9888
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
9889
|
+
totalPages = Math.ceil(totalItems / size);
|
|
9890
|
+
}
|
|
9737
9891
|
const page = Math.floor(offset / size);
|
|
9738
|
-
const
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9892
|
+
const items = await this.list({
|
|
9893
|
+
partition,
|
|
9894
|
+
partitionValues,
|
|
9895
|
+
limit: size,
|
|
9896
|
+
offset
|
|
9897
|
+
});
|
|
9742
9898
|
const result = {
|
|
9743
9899
|
items,
|
|
9744
9900
|
totalItems,
|
|
9745
9901
|
page,
|
|
9746
9902
|
pageSize: size,
|
|
9747
|
-
totalPages
|
|
9903
|
+
totalPages,
|
|
9904
|
+
// Add additional metadata for debugging
|
|
9905
|
+
_debug: {
|
|
9906
|
+
requestedSize: size,
|
|
9907
|
+
requestedOffset: offset,
|
|
9908
|
+
actualItemsReturned: items.length,
|
|
9909
|
+
skipCount,
|
|
9910
|
+
hasTotalItems: totalItems !== null
|
|
9911
|
+
}
|
|
9748
9912
|
};
|
|
9749
9913
|
this.emit("page", result);
|
|
9750
9914
|
return result;
|
|
@@ -9896,7 +10060,27 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9896
10060
|
* @returns {Object} Schema object for the version
|
|
9897
10061
|
*/
|
|
9898
10062
|
async getSchemaForVersion(version) {
|
|
9899
|
-
|
|
10063
|
+
if (version === this.version) {
|
|
10064
|
+
return this.schema;
|
|
10065
|
+
}
|
|
10066
|
+
try {
|
|
10067
|
+
const compatibleSchema = new Schema({
|
|
10068
|
+
name: this.name,
|
|
10069
|
+
attributes: this.attributes,
|
|
10070
|
+
passphrase: this.passphrase,
|
|
10071
|
+
version,
|
|
10072
|
+
options: {
|
|
10073
|
+
...this.options,
|
|
10074
|
+
// For older versions, be more lenient with decryption
|
|
10075
|
+
autoDecrypt: true,
|
|
10076
|
+
autoEncrypt: true
|
|
10077
|
+
}
|
|
10078
|
+
});
|
|
10079
|
+
return compatibleSchema;
|
|
10080
|
+
} catch (error) {
|
|
10081
|
+
console.warn(`Failed to create compatible schema for version ${version}, using current schema:`, error.message);
|
|
10082
|
+
return this.schema;
|
|
10083
|
+
}
|
|
9900
10084
|
}
|
|
9901
10085
|
/**
|
|
9902
10086
|
* Create partition references after insert
|
|
@@ -10140,7 +10324,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10140
10324
|
this.version = "1";
|
|
10141
10325
|
this.s3dbVersion = (() => {
|
|
10142
10326
|
try {
|
|
10143
|
-
return true ? "4.1.
|
|
10327
|
+
return true ? "4.1.9" : "latest";
|
|
10144
10328
|
} catch (e) {
|
|
10145
10329
|
return "latest";
|
|
10146
10330
|
}
|