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.es.js
CHANGED
|
@@ -9234,36 +9234,97 @@ class Resource extends EventEmitter {
|
|
|
9234
9234
|
*/
|
|
9235
9235
|
async get(id) {
|
|
9236
9236
|
const key = this.getResourceKey(id);
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9237
|
+
try {
|
|
9238
|
+
const request = await this.client.headObject(key);
|
|
9239
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9240
|
+
const schema = await this.getSchemaForVersion(objectVersion);
|
|
9241
|
+
let metadata = await schema.unmapper(request.Metadata);
|
|
9242
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9243
|
+
let body = "";
|
|
9244
|
+
if (request.ContentLength > 0) {
|
|
9245
|
+
try {
|
|
9246
|
+
const fullObject = await this.client.getObject(key);
|
|
9247
|
+
body = await streamToString(fullObject.Body);
|
|
9248
|
+
} catch (error) {
|
|
9249
|
+
console.warn(`Failed to read body for resource ${id}:`, error.message);
|
|
9250
|
+
body = "";
|
|
9251
|
+
}
|
|
9249
9252
|
}
|
|
9253
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9254
|
+
resource: this,
|
|
9255
|
+
metadata,
|
|
9256
|
+
body
|
|
9257
|
+
});
|
|
9258
|
+
let data = processedMetadata;
|
|
9259
|
+
data.id = id;
|
|
9260
|
+
data._contentLength = request.ContentLength;
|
|
9261
|
+
data._lastModified = request.LastModified;
|
|
9262
|
+
data._hasContent = request.ContentLength > 0;
|
|
9263
|
+
data._mimeType = request.ContentType || null;
|
|
9264
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9265
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9266
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9267
|
+
this.emit("get", data);
|
|
9268
|
+
return data;
|
|
9269
|
+
} catch (error) {
|
|
9270
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError") || error.originalError?.message?.includes("Cipher job failed")) {
|
|
9271
|
+
try {
|
|
9272
|
+
console.warn(`Decryption failed for resource ${id}, attempting to get raw metadata`);
|
|
9273
|
+
const request = await this.client.headObject(key);
|
|
9274
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9275
|
+
const tempSchema = new Schema({
|
|
9276
|
+
name: this.name,
|
|
9277
|
+
attributes: this.attributes,
|
|
9278
|
+
passphrase: this.passphrase,
|
|
9279
|
+
version: objectVersion,
|
|
9280
|
+
options: {
|
|
9281
|
+
...this.options,
|
|
9282
|
+
autoDecrypt: false,
|
|
9283
|
+
// Disable decryption
|
|
9284
|
+
autoEncrypt: false
|
|
9285
|
+
// Disable encryption
|
|
9286
|
+
}
|
|
9287
|
+
});
|
|
9288
|
+
let metadata = await tempSchema.unmapper(request.Metadata);
|
|
9289
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9290
|
+
let body = "";
|
|
9291
|
+
if (request.ContentLength > 0) {
|
|
9292
|
+
try {
|
|
9293
|
+
const fullObject = await this.client.getObject(key);
|
|
9294
|
+
body = await streamToString(fullObject.Body);
|
|
9295
|
+
} catch (bodyError) {
|
|
9296
|
+
console.warn(`Failed to read body for resource ${id}:`, bodyError.message);
|
|
9297
|
+
body = "";
|
|
9298
|
+
}
|
|
9299
|
+
}
|
|
9300
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9301
|
+
resource: this,
|
|
9302
|
+
metadata,
|
|
9303
|
+
body
|
|
9304
|
+
});
|
|
9305
|
+
let data = processedMetadata;
|
|
9306
|
+
data.id = id;
|
|
9307
|
+
data._contentLength = request.ContentLength;
|
|
9308
|
+
data._lastModified = request.LastModified;
|
|
9309
|
+
data._hasContent = request.ContentLength > 0;
|
|
9310
|
+
data._mimeType = request.ContentType || null;
|
|
9311
|
+
data._version = objectVersion;
|
|
9312
|
+
data._decryptionFailed = true;
|
|
9313
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9314
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9315
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9316
|
+
this.emit("get", data);
|
|
9317
|
+
return data;
|
|
9318
|
+
} catch (fallbackError) {
|
|
9319
|
+
console.error(`Fallback attempt also failed for resource ${id}:`, fallbackError.message);
|
|
9320
|
+
}
|
|
9321
|
+
}
|
|
9322
|
+
const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
|
|
9323
|
+
enhancedError.originalError = error;
|
|
9324
|
+
enhancedError.resourceId = id;
|
|
9325
|
+
enhancedError.resourceKey = key;
|
|
9326
|
+
throw enhancedError;
|
|
9250
9327
|
}
|
|
9251
|
-
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9252
|
-
resource: this,
|
|
9253
|
-
metadata,
|
|
9254
|
-
body
|
|
9255
|
-
});
|
|
9256
|
-
let data = processedMetadata;
|
|
9257
|
-
data.id = id;
|
|
9258
|
-
data._contentLength = request.ContentLength;
|
|
9259
|
-
data._lastModified = request.LastModified;
|
|
9260
|
-
data._hasContent = request.ContentLength > 0;
|
|
9261
|
-
data._mimeType = request.ContentType || null;
|
|
9262
|
-
if (request.VersionId) data._versionId = request.VersionId;
|
|
9263
|
-
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9264
|
-
data._definitionHash = this.getDefinitionHash();
|
|
9265
|
-
this.emit("get", data);
|
|
9266
|
-
return data;
|
|
9267
9328
|
}
|
|
9268
9329
|
/**
|
|
9269
9330
|
* Check if a resource exists by ID
|
|
@@ -9545,15 +9606,21 @@ class Resource extends EventEmitter {
|
|
|
9545
9606
|
return { deletedCount, resource: this.name };
|
|
9546
9607
|
}
|
|
9547
9608
|
/**
|
|
9548
|
-
* List resource IDs with optional partition filtering
|
|
9609
|
+
* List resource IDs with optional partition filtering and pagination
|
|
9549
9610
|
* @param {Object} [params] - List parameters
|
|
9550
9611
|
* @param {string} [params.partition] - Partition name to list from
|
|
9551
9612
|
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9613
|
+
* @param {number} [params.limit] - Maximum number of results to return
|
|
9614
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9552
9615
|
* @returns {Promise<string[]>} Array of resource IDs (strings)
|
|
9553
9616
|
* @example
|
|
9554
9617
|
* // List all IDs
|
|
9555
9618
|
* const allIds = await resource.listIds();
|
|
9556
9619
|
*
|
|
9620
|
+
* // List IDs with pagination
|
|
9621
|
+
* const firstPageIds = await resource.listIds({ limit: 10, offset: 0 });
|
|
9622
|
+
* const secondPageIds = await resource.listIds({ limit: 10, offset: 10 });
|
|
9623
|
+
*
|
|
9557
9624
|
* // List IDs from specific partition
|
|
9558
9625
|
* const googleUserIds = await resource.listIds({
|
|
9559
9626
|
* partition: 'byUtmSource',
|
|
@@ -9566,7 +9633,7 @@ class Resource extends EventEmitter {
|
|
|
9566
9633
|
* partitionValues: { category: 'electronics', region: 'US' }
|
|
9567
9634
|
* });
|
|
9568
9635
|
*/
|
|
9569
|
-
async listIds({ partition = null, partitionValues = {} } = {}) {
|
|
9636
|
+
async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9570
9637
|
let prefix;
|
|
9571
9638
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9572
9639
|
const partitionDef = this.options.partitions[partition];
|
|
@@ -9590,8 +9657,11 @@ class Resource extends EventEmitter {
|
|
|
9590
9657
|
} else {
|
|
9591
9658
|
prefix = `resource=${this.name}/v=${this.version}`;
|
|
9592
9659
|
}
|
|
9593
|
-
const keys = await this.client.
|
|
9594
|
-
prefix
|
|
9660
|
+
const keys = await this.client.getKeysPage({
|
|
9661
|
+
prefix,
|
|
9662
|
+
offset,
|
|
9663
|
+
amount: limit || 1e3
|
|
9664
|
+
// Default to 1000 if no limit specified
|
|
9595
9665
|
});
|
|
9596
9666
|
const ids = keys.map((key) => {
|
|
9597
9667
|
const parts = key.split("/");
|
|
@@ -9638,11 +9708,27 @@ class Resource extends EventEmitter {
|
|
|
9638
9708
|
if (limit) {
|
|
9639
9709
|
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9640
9710
|
}
|
|
9641
|
-
const { results: results2 } = await PromisePool.for(filteredIds2).withConcurrency(this.parallelism).
|
|
9642
|
-
|
|
9711
|
+
const { results: results2, errors: errors2 } = await PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9712
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9713
|
+
return null;
|
|
9714
|
+
}).process(async (id) => {
|
|
9715
|
+
try {
|
|
9716
|
+
return await this.get(id);
|
|
9717
|
+
} catch (error) {
|
|
9718
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9719
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9720
|
+
return {
|
|
9721
|
+
id,
|
|
9722
|
+
_decryptionFailed: true,
|
|
9723
|
+
_error: error.message
|
|
9724
|
+
};
|
|
9725
|
+
}
|
|
9726
|
+
throw error;
|
|
9727
|
+
}
|
|
9643
9728
|
});
|
|
9644
|
-
|
|
9645
|
-
|
|
9729
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9730
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9731
|
+
return validResults2;
|
|
9646
9732
|
}
|
|
9647
9733
|
const partitionDef = this.options.partitions[partition];
|
|
9648
9734
|
if (!partitionDef) {
|
|
@@ -9673,11 +9759,29 @@ class Resource extends EventEmitter {
|
|
|
9673
9759
|
if (limit) {
|
|
9674
9760
|
filteredIds = filteredIds.slice(0, limit);
|
|
9675
9761
|
}
|
|
9676
|
-
const { results } = await PromisePool.for(filteredIds).withConcurrency(this.parallelism).
|
|
9677
|
-
|
|
9762
|
+
const { results, errors } = await PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9763
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9764
|
+
return null;
|
|
9765
|
+
}).process(async (id) => {
|
|
9766
|
+
try {
|
|
9767
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9768
|
+
} catch (error) {
|
|
9769
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9770
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9771
|
+
return {
|
|
9772
|
+
id,
|
|
9773
|
+
_partition: partition,
|
|
9774
|
+
_partitionValues: partitionValues,
|
|
9775
|
+
_decryptionFailed: true,
|
|
9776
|
+
_error: error.message
|
|
9777
|
+
};
|
|
9778
|
+
}
|
|
9779
|
+
throw error;
|
|
9780
|
+
}
|
|
9678
9781
|
});
|
|
9679
|
-
|
|
9680
|
-
|
|
9782
|
+
const validResults = results.filter((item) => item !== null);
|
|
9783
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9784
|
+
return validResults;
|
|
9681
9785
|
}
|
|
9682
9786
|
/**
|
|
9683
9787
|
* Get multiple resources by their IDs
|
|
@@ -9688,11 +9792,30 @@ class Resource extends EventEmitter {
|
|
|
9688
9792
|
* users.forEach(user => console.log(user.name));
|
|
9689
9793
|
*/
|
|
9690
9794
|
async getMany(ids) {
|
|
9691
|
-
const { results } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9795
|
+
const { results, errors } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9796
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9797
|
+
return {
|
|
9798
|
+
id,
|
|
9799
|
+
_error: error.message,
|
|
9800
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9801
|
+
};
|
|
9802
|
+
}).process(async (id) => {
|
|
9692
9803
|
this.emit("id", id);
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9804
|
+
try {
|
|
9805
|
+
const data = await this.get(id);
|
|
9806
|
+
this.emit("data", data);
|
|
9807
|
+
return data;
|
|
9808
|
+
} catch (error) {
|
|
9809
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9810
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9811
|
+
return {
|
|
9812
|
+
id,
|
|
9813
|
+
_decryptionFailed: true,
|
|
9814
|
+
_error: error.message
|
|
9815
|
+
};
|
|
9816
|
+
}
|
|
9817
|
+
throw error;
|
|
9818
|
+
}
|
|
9696
9819
|
});
|
|
9697
9820
|
this.emit("getMany", ids.length);
|
|
9698
9821
|
return results;
|
|
@@ -9707,9 +9830,28 @@ class Resource extends EventEmitter {
|
|
|
9707
9830
|
async getAll() {
|
|
9708
9831
|
let ids = await this.listIds();
|
|
9709
9832
|
if (ids.length === 0) return [];
|
|
9710
|
-
const { results } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9711
|
-
|
|
9712
|
-
return
|
|
9833
|
+
const { results, errors } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9834
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9835
|
+
return {
|
|
9836
|
+
id,
|
|
9837
|
+
_error: error.message,
|
|
9838
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9839
|
+
};
|
|
9840
|
+
}).process(async (id) => {
|
|
9841
|
+
try {
|
|
9842
|
+
const data = await this.get(id);
|
|
9843
|
+
return data;
|
|
9844
|
+
} catch (error) {
|
|
9845
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9846
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9847
|
+
return {
|
|
9848
|
+
id,
|
|
9849
|
+
_decryptionFailed: true,
|
|
9850
|
+
_error: error.message
|
|
9851
|
+
};
|
|
9852
|
+
}
|
|
9853
|
+
throw error;
|
|
9854
|
+
}
|
|
9713
9855
|
});
|
|
9714
9856
|
this.emit("getAll", results.length);
|
|
9715
9857
|
return results;
|
|
@@ -9721,6 +9863,7 @@ class Resource extends EventEmitter {
|
|
|
9721
9863
|
* @param {number} [params.size=100] - Page size
|
|
9722
9864
|
* @param {string} [params.partition] - Partition name to page from
|
|
9723
9865
|
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9866
|
+
* @param {boolean} [params.skipCount=false] - Skip total count for performance (useful for large collections)
|
|
9724
9867
|
* @returns {Promise<Object>} Page result with items and pagination info
|
|
9725
9868
|
* @example
|
|
9726
9869
|
* // Get first page of all resources
|
|
@@ -9735,22 +9878,43 @@ class Resource extends EventEmitter {
|
|
|
9735
9878
|
* offset: 0,
|
|
9736
9879
|
* size: 5
|
|
9737
9880
|
* });
|
|
9881
|
+
*
|
|
9882
|
+
* // Skip count for performance in large collections
|
|
9883
|
+
* const fastPage = await resource.page({
|
|
9884
|
+
* offset: 0,
|
|
9885
|
+
* size: 100,
|
|
9886
|
+
* skipCount: true
|
|
9887
|
+
* });
|
|
9888
|
+
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
9738
9889
|
*/
|
|
9739
|
-
async page({ offset = 0, size = 100, partition = null, partitionValues = {} } = {}) {
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9890
|
+
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
9891
|
+
let totalItems = null;
|
|
9892
|
+
let totalPages = null;
|
|
9893
|
+
if (!skipCount) {
|
|
9894
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
9895
|
+
totalPages = Math.ceil(totalItems / size);
|
|
9896
|
+
}
|
|
9743
9897
|
const page = Math.floor(offset / size);
|
|
9744
|
-
const
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9898
|
+
const items = await this.list({
|
|
9899
|
+
partition,
|
|
9900
|
+
partitionValues,
|
|
9901
|
+
limit: size,
|
|
9902
|
+
offset
|
|
9903
|
+
});
|
|
9748
9904
|
const result = {
|
|
9749
9905
|
items,
|
|
9750
9906
|
totalItems,
|
|
9751
9907
|
page,
|
|
9752
9908
|
pageSize: size,
|
|
9753
|
-
totalPages
|
|
9909
|
+
totalPages,
|
|
9910
|
+
// Add additional metadata for debugging
|
|
9911
|
+
_debug: {
|
|
9912
|
+
requestedSize: size,
|
|
9913
|
+
requestedOffset: offset,
|
|
9914
|
+
actualItemsReturned: items.length,
|
|
9915
|
+
skipCount,
|
|
9916
|
+
hasTotalItems: totalItems !== null
|
|
9917
|
+
}
|
|
9754
9918
|
};
|
|
9755
9919
|
this.emit("page", result);
|
|
9756
9920
|
return result;
|
|
@@ -9902,7 +10066,27 @@ class Resource extends EventEmitter {
|
|
|
9902
10066
|
* @returns {Object} Schema object for the version
|
|
9903
10067
|
*/
|
|
9904
10068
|
async getSchemaForVersion(version) {
|
|
9905
|
-
|
|
10069
|
+
if (version === this.version) {
|
|
10070
|
+
return this.schema;
|
|
10071
|
+
}
|
|
10072
|
+
try {
|
|
10073
|
+
const compatibleSchema = new Schema({
|
|
10074
|
+
name: this.name,
|
|
10075
|
+
attributes: this.attributes,
|
|
10076
|
+
passphrase: this.passphrase,
|
|
10077
|
+
version,
|
|
10078
|
+
options: {
|
|
10079
|
+
...this.options,
|
|
10080
|
+
// For older versions, be more lenient with decryption
|
|
10081
|
+
autoDecrypt: true,
|
|
10082
|
+
autoEncrypt: true
|
|
10083
|
+
}
|
|
10084
|
+
});
|
|
10085
|
+
return compatibleSchema;
|
|
10086
|
+
} catch (error) {
|
|
10087
|
+
console.warn(`Failed to create compatible schema for version ${version}, using current schema:`, error.message);
|
|
10088
|
+
return this.schema;
|
|
10089
|
+
}
|
|
9906
10090
|
}
|
|
9907
10091
|
/**
|
|
9908
10092
|
* Create partition references after insert
|
|
@@ -10146,7 +10330,7 @@ class Database extends EventEmitter {
|
|
|
10146
10330
|
this.version = "1";
|
|
10147
10331
|
this.s3dbVersion = (() => {
|
|
10148
10332
|
try {
|
|
10149
|
-
return true ? "4.1.
|
|
10333
|
+
return true ? "4.1.9" : "latest";
|
|
10150
10334
|
} catch (e) {
|
|
10151
10335
|
return "latest";
|
|
10152
10336
|
}
|